JavaWeb(四)Cookie和Session

上一章链接:JSP基础

在使用网站的登录界面时,点击输入栏时会发现输入栏有以前登录的账户的名称预选值,而这就是Cookie的作用。那么何为Cookie?Cookie里又有什么?Cookie又怎么用?这些问题将在下面的文章一一解答。

 

目录

    Cookie和Http协议

    Cookie的作用

    Cookie的组成

    JavaWeb中使用Cookie

    Cookie的其他属性

        maxAge(最大生命)

        path(路径)

        domain(域名)

Session

   Session概述

   Session域

   Session部分源码剖析

   Session登录小案例

   Session其他方法

   URL重写

    JSESSIONID

后话


Cookie

    Cookie和Http协议

说起Cookie,不得不说起Http协议。Cookie是Http协议制定的,并且Http协议规定:

        一个Cookie最大4KB

        一个服务器最多向一个浏览器保存20个Cookie

        一个浏览器最多可以保存300个Cookie

那么服务器和Cookie之间又如何联系?先由服务器保存Cookie到浏览器,在下次浏览器请求服务器时,把上一次请求得到的Cookie归还给服务器。

    Cookie的作用

Cookie可以保存申请的用户名并且显示出来,也可以用来保存购物车中的商品。这两个作用都是基于服务器使用Cookie来跟踪客户端的状态而体现出来的实例作用。

    Cookie的组成

以Google浏览器为例,依次打开设置——隐私设置和安全性——Cookie——查看所有Cookie和网站数据可以看到各服务器保存的所有Cookie。以CSDN为例,随意打开一个CSDN的Cookie,根据不同作用会有不同的结构,最主要需要了解的结构如下所示:

Cookie:Cookie名称、内容、域名、路径、为何发送、脚本可访问、创建时间、到期时间

    JavaWeb中使用Cookie

最原始的方式是使用response发送Set-Cookie响应头,使用request获取Cookie请求头。

而后续又增加了response.addCookie()方法和request.getCookies()方法来分别保存和获取Cookie。

这里用代码演示一下:

    a.jsp

        
        <%
	        Cookie cookie = new Cookie("aaa", "AAA");
	        response.addCookie(cookie);
        %>

    b.jsp

        <%
	        Cookie[] cookies = request.getCookies();
	        if(cookies != null) {
	        	for(Cookie c : cookies) {
	        		out.print(c.getName() + "=" + c.getValue() + "---" + c.getMaxAge() + "
"); } } %>

分别打开b.jsp和设置查看Cookie,显示结果如下所示:

JavaWeb(四)Cookie和Session_第1张图片

不难发现,除了自己设置的Cookie以外,还有一个JSESSIONID。这里暂时不提JSESSIONID,在后面将会详细地剖析这个JSESSIONID有什么用。

    Cookie的其他属性

Cookie除去名称和内容,还有其他属性。如maxAgepathdomain,分别代表了Cookie的最大生命、Cookie的路径和Cookie的域名。接下来分别讲解这三个属性。

        maxAge(最大生命)

        所谓的最大生命,即Cookie可保存的最大时长,特别需要注意的是,在相关的方法中参数为int,说明以秒为单位。

        当maxAge>0时,浏览器会把Cookie保存到客户机硬盘上,有效时长为maxAge值决定。

        当maxAge<0时,Cookie只能在浏览器内存中存在,即当浏览器进程结束时,Cookie就销毁了。

        当maxAge=0时,浏览器会马上删除这个Cookie。

        以下为演示代码:

        // a.jsp
        <%
	        Cookie cookie = new Cookie("aaa", "AAA");
	        cookie.setMaxAge(60*60);
	        response.addCookie(cookie);
        %>

        // b.jsp
        <%
            Cookie[] cookies = request.getCookies();
            if(cookies != null) {
                for(Cookie c : cookies) {
                    out.print(c.getName() + "=" + c.getValue() + "---" + c.getMaxAge() + "
"); c.setMaxAge(0); response.addCookie(c); } } %>

        请求a.jsp时,打开浏览器设置查看相关的cookie信息。由于setMaxAge()方法中的参数值设置为了60*60(即1小时),则该aaa的到期时间只有一个小时。

JavaWeb(四)Cookie和Session_第2张图片

        请求b.jsp时,将cookie的最大存活时间设置为0,再次查看浏览器的Cookie信息,发现只剩下了JSESSIONID。

JavaWeb(四)Cookie和Session_第3张图片

        path(路径)

        首先必须得明确的一点是:尽管Cookie是保存在客户端的硬盘中的,但是Cookie的path并不是设置这个Cookie在客户端的保存路径。至于这个路径是由服务器创建Cookie时设置的,即有默认路径。

        浏览器访问服务器的路径,如果包含了某个Cookie的路径,那么就会归还这个Cookie给服务器。例如:

        // 有三个不同的Cookie,并且路径也不相同
        aCookie.path = /java_web/;
        bCookie.path = /java_web/cookie/;
        cCookie.path = /java_web/cookie/c/;

        /*
        *  当访问/java_web/a.jsp时,浏览器归还aCookie    
        *  当访问/java_web/cookie/b.jsp时,浏览器归还aCookie、bCookie
        *  当访问/java_web/cookie/c/c.jsp时,浏览器归还aCoookie、bCoookie、cCookie
        */

        路径这一块主要是理解到路径实际代表的是什么。

        domain(域名)

        正常的cookie只能在一个应用中共享,即一个cookie只能由创建它的应用获得。而要想跨域,就得用到domain。

        domain是用来指定Cookie的域名,当多个二级域中共享Cookie时才用。

        举个例子:

        '''
            www.baidu.com、zhidao.baidu.com、tieba.baidu.com之间共享Cookie
            就得设置Cookie的domain为:cookie.setDomain(".baidu.com");
            或者设置path为:cookie.setPath("/");
        '''

以上,就是Cookie的相关介绍,其中还有一个JSESSIONID将会放在最后的一部分讲解。


Session

   Session概述

HttpSession是由JavaWeb提供的,用来会话跟踪的类。有一点要特别记住,Session是服务器端的对象,保存在服务器端。

还有一点需要记住,HttpSession底层依赖Cookie,或者是URL重写

Session是一个会话对象,那么什么是会话呢?会话是指一个用户对服务器的多次连贯性请求。何为连贯性请求,就是同一个用户多次请求在这之间没有关闭过浏览器。那么这样也能给出会话范围的定义:某一个用户从首次访问服务器开始,到该用户关闭浏览器结束。

由这个会话范围可以看出服务器会为每个客户端创建一个Session对象。Session就好比客户在服务器端的账户,然后保存到一个Map中,这个Map被称之为Session。

   Session域

JavaWeb四大域对象,已经接触了两种:ServletContext、ServletRequest、pageContext。而剩下的最后一种就是HttpSession。也就是说,Session域中也有既定的三种方法:

        public abstract void setAttribute(String s, Object obj);
        public abstract Object getAttribute(String s);
        public abstract void removeAttribute(String s);

而在Servlet和JSP中又如何得到Session对象?

    Servlet:通过 HttpSession session = request.getSession()方法获得。

    JSP:session是九大内置对象之一,不需要创建即可使用。

   Session部分源码剖析

HttpSession只是一个接口类,那么通过request.getSession()方法寻找HttpServletRequest,还是个接口类。真正实现HttpServletRequest的是catalina包下的connector的RequestFacade(这里其实在第二章的时候提及过了)。

不出意外的在RequestFacade里找到了getSession()方法。先来看看这个getSession()方法。

        // org.apche.catalina.connector.RequestFacade.java
        public HttpSession getSession(boolean create)
        {
            if (request == null)
                throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
            if (SecurityUtil.isPackageProtectionEnabled())
                return (HttpSession)AccessController.doPrivileged(new
                    GetSessionPrivilegedAction(create));
            else
                return request.getSession(create);
        }

通过第一个判断条件,可以得知Session是基于客户端请求服务器时才会生成。第二个判断条件暂时先忽略,不是主要内容。当前面两个判断条件都不满足时则调用同一个包下的Request类对象的getSession(boolean create)方法,源码如下所示:

        // org.apache.catalina.connector.Request.java
        public HttpSession getSession(boolean create)
        {
            Session session = doGetSession(create);
            if (session == null)
                return null;
            else
                return session.getSession();
        }

        public HttpSession getSession()
        {
            Session session = doGetSession(true);
            if (session == null)
                return null;
            else
                return session.getSession();
	}

 从源码来看,首先调用doGetSession方法获取Session,然后再调用session的getSession方法返回HttpSession。那么就得查看以下doGetSession()方法里又是怎么样获取Session的,源码中每一部分负责不同的地方,这里将源码分块讲解。

首先是第一部分:

        // 这一部分是判断Session是否存在并且有效
        Context context = getContext();
        if (context == null)
            return null;
        if (session != null && !session.isValid())
            session = null;
        if (session != null)
            return session;
        Manager manager = context.getManager();
        if (manager == null)
            return null;

        // 这一部分是指根据JSESSIONID去查找Session,如果存在并且有效,则返回找到的Session。
        if (requestedSessionId != null) {
            try {
                session = manager.findSession(requestedSessionId);
            } catch (IOException e) {
                session = null;
            }
            if (session != null && !session.isValid())
                session = null;
            if (session != null) {
                session.access();
                return session;
            }
        }

        // 这一部分是在响应还未提交时下一步就创建一个新的Session
        if (!create)
            return null;
        if (response != null && 
        context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE) && 
        response.getResponse().isCommitted()) throw new
            IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));

        // 这里是根据SSL会话ID是否存在判断Session是否可使用
        // 代码过长就不予以放置了,有要求的可以自行查看
        
        // 这一部分是基于SessionID创建一个新的Session的Cookie,一般是第一次访问的情况
        session = manager.createSession(sessionId);
        if (session != null && getContext() != null &&
        getContext().getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE)) {
            Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(context,
        session.getIdInternal(), isSecure());
            response.addSessionCookieInternal(cookie);
        }
        if (session == null) {
            return null;
        } else {
            session.access();
            return session;
        }
        // session.access()是保存该session的最后访问时间

综合源码来看,可以得知整个获取Session的步骤如下描述:

  • 获取Session步骤:
    • 1.判断当前Request对象是否已存在有效的Session,如果存在则返回此Session,如果不存在则下一步;
    • 2.获取Session管理器,从管理器的Session缓存中获取Session,如果有则返回此Session,如果没有则下一步;
    • 3.创建Session,并且创建保存SessionID的Cookie;
    • 4.调用Session的access()方法更新Session的访问时间以及访问次数。(设置到期时间的便利性)

以上的步骤描述其实已经可以把获取Session的原理讲清楚了。那么为了更好的理解Session、SessionID、Cookie三者之间的关系,在这里举一个类似的例子:银行业务的例子。

进银行办理业务,业务人员问有没有银行卡?没银行卡,则创建账户并且办带有ID的银行卡。不小心银行卡丢了,但是在银行数据库有你卡上的ID,重补一张卡即可。突然有一天数据库崩了,ID没了,卡废了,说明只能重新办卡,再次循环。

在这里面,银行卡本身是个Cookie,卡里保存的是SessionID,而银行账户是Session。也就是说,通过一个Cookie保存的SessionID去找Session。这就是获取Session的原理。

   Session登录小案例

三个JSP和一个Servlet:login.jsp、succ1.jsp、LoginServlet。需要完成的功能如下:

login.jsp提供登录表单,提交表单请求LoginServlet;

LoginServlet获取请求参数,校验用户是否登录成功。若登录成功,则保存用户信息到session域中,重定向到succ1.jsp页面,显示session域中的用户信息。若登录失败,则保存错误信息到session域中,转发到login.jsp显示request域中的错误信息。

succ1.jsp则是从session域中获取用户信息,若用户信息存在,则显示用户信息;如果用户信息不存在则将“你还没有登录”信息转回给login.jsp。

代码如下所示:

    login.jsp


	

登录

<% //这里的作用就是从Cookie获取输入过的用户名数据 String uname = ""; Cookie[] cookies = request.getCookies(); if(cookies != null) { for(Cookie c : cookies) { if("uname".equals(c.getName())) { uname = c.getValue(); } } } %> <% String msg = (String)request.getAttribute("msg"); if(msg == null) msg = ""; %> <%=msg %>
用户名:
密码:

    succ1.jsp


	<%
		String uname = (String)session.getAttribute("user");
		if(uname == null) {
			String msg = "未登录,无法访问内部";
			request.setAttribute("msg", msg);
			request.getRequestDispatcher("login.jsp").forward(request, response);
		}
	%>
	
欢迎<%=uname %>用户登录

    LoginServlet

public class LoginServlet extends HttpServlet {
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                // 获取表单数据
		String uName = req.getParameter("username");
		String pwd = req.getParameter("password");
                // 这里一般是数据库查询 这里只是案例简化了
		if("test".equals(uName) && "123".equals(pwd)) {
			Cookie cookie = new Cookie("uname", uName);
			cookie.setMaxAge(60*60*24);
			resp.addCookie(cookie);
                        // ↑以上设置Cookie信息,为了在输入框中显示输入过的用户名
			HttpSession session = req.getSession();
			session.setAttribute("user", uName);
			resp.sendRedirect("session/succ1.jsp");
		} else {
			String msg = "登录失败,非系统内用户登录";
			req.setAttribute("msg", msg);
			req.getRequestDispatcher("session/login.jsp").forward(req, resp);
		}
	}
	
}

结果演示:

JavaWeb(四)Cookie和Session_第4张图片

登录后,在原有的浏览器没有关闭的条件下,再次打开一个新的浏览器访问succ1.jsp一样能够成功访问。只要用户没有关闭浏览器,session就一直存在。那么,保存在session中的用户信息就一直存在。

验证码部分是在后续加进去的。这里附上验证码类的代码和实现的相关Servlet:

/*
 * 		生成验证码图片
 * 			组成:图片宽高、文字字体、字体样式、字体大小、字体颜色、验证码文本
 * 	
 * */

public class VerifyCode {
	private int w = 70;
	private int h = 35;
	private Random r = new Random();
	private String[] fontNames = { "微软雅黑", "宋体", "黑体", "华文楷体", "华文新魏", "华文隶书", "楷体_GB2312" };
	private String codes = "23456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
	private Color bgColor = new Color(255, 255, 255);
	private String text;
	private final int textLen = 4;

	// 创建背景图片
	private BufferedImage createImage() {
		BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		Graphics2D g = image.createGraphics();
		g.setColor(bgColor);
		g.fillRect(0, 0, w, h);
		return image;
	}

	// 生成随机单个随机文字
	private String randomChar() {
		String str = codes.charAt(r.nextInt(codes.length())) + "";
		return str;
	}

	// 随机颜色
	private Color randomColor() {
		return new Color(r.nextInt(150), r.nextInt(150), r.nextInt(150));
	}

	// 随机字体
	private Font randomFont() {
		int index = r.nextInt(fontNames.length);
		String fontName = fontNames[index];
		int style = r.nextInt(4);
		int size = r.nextInt(5) + 24;
		return new Font(fontName, style, size);
	}

	// 绘格挡线
	private void drawLine(BufferedImage image) {
		int num = 3;
		Graphics2D g = (Graphics2D) image.getGraphics();
		for (int i = 0; i < num; i++) {
			int x1 = r.nextInt(w);
			int y1 = r.nextInt(h);
			int x2 = r.nextInt(w);
			int y2 = r.nextInt(h);
			g.setStroke(new BasicStroke(1.5F));
			g.setColor(Color.BLUE);
			g.drawLine(x1, y1, x2, y2);
		}
	}

	// 得到验证码图片
	public BufferedImage getImage() {
		BufferedImage image = createImage();
		Graphics2D g = image.createGraphics();
		StringBuilder sb = new StringBuilder();
		// 生成随机文字
		for (int i = 0; i < textLen; i++) {
			String s = randomChar();
			sb.append(s);
			float x = i * 1.0F * w / 4;
			g.setColor(randomColor());
			g.setFont(randomFont());
			g.drawString(s, x, h - 5);
		}
		drawLine(image);
		this.text = sb.toString();
		return image;
	}

	public String getText() {
		return text;
	}

	public static void output(BufferedImage image, OutputStream out) throws IOException {
		ImageIO.write(image, "JPEG", out);
	}
}

public class VerifyCodeServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//生成验证码图片,将验证码文本保存至Session
		VerifyCode vc = new VerifyCode();
		BufferedImage image = vc.getImage();
		req.getSession().setAttribute("session_vcode", vc.getText());
		VerifyCode.output(image, resp.getOutputStream());
	}
}

   Session其他方法

        // 获取SessionId
        public abstract String getId();
        // 获取Session的最大不活动时间(秒),默认值为30分钟
        public abstract int getMaxInactiveInterval();
        // 让Session失效,类似于银行卡中的账户被冻结
        public abstract void invalidate();
        // 查看session是否为新的,作用是判断getSession是创建session还是获取session
        public abstract boolean isNew();

这里的方法自行测试效果,不予以过于详细地介绍。

   URL重写

前面说过,HttpSession底层依赖Cookie,为的就是让客户端发出请求时归还sessionId,这样才能找到对应的session。

如果客户端禁用了Cookie,那么就无法得到sessionId,那么session也就没有任何用处了。那么就得需要另一个方法:URL重写。

URL重写可以让网站的所有超链接、表单中都添加一个特殊的请求参数,即SessionId,这样服务器就可以通过获取请求参数来获取SessionId,从而获取到session对象。

那么该如何实现URL重写呢?在response对象中有一个encodeURL(String url)方法会对url进行智能的重写。当请求中没有归还sessionid这个cookie时,那么该方法会重写url。否则不重写url。

演示代码如下:

    urlReWrite.jsp


	
	点击这里1
	点击这里2
	点击这里3
	
	<%
		//会查看Cookie是否存在,如果不存在,在指定的url后添加JSESSIONID参数
		//如果cookie存在,它就不会在url后添加任何东西
		out.println(response.encodeURL("../CheckSession"));
	%>
	

    checkSession

public class CheckSession extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setContentType("text/html;charset=utf-8");
		resp.getWriter().print("请查看是否有jsessionid这个cookie");
	}
	
}

结果如下显示:

JavaWeb(四)Cookie和Session_第5张图片

    JSESSIONID

首先说明JSESSIONID是一个Cookie,用来记录用户session的。创建会话的时候,即当request调用getSession()方法时,就会创建一个这样的Cookie。

按下F12查看,第一次访问服务器的时候,响应头中能看到一个Set-Cookie信息。浏览器会根据响应头中的Set-Cookie信息设置浏览器的cookie并且保存下来。由于没有设置Cookie的到期时间即有效时间,默认情况为-1,即关闭浏览器后Cookie会丢失。

在之后的多次访问服务器,浏览器都会将cookie中的JSESSIONID发送给服务器。为什么要每次都发送,为的就是判断当前访问服务器的用户对应于哪个Session,Session中的getId()方法就是获取JSESSIONID的。

查看JSESSIONID,可以发现,其是32位16进制的随机字符串。底层是用UUID来生成随机字符串,至于UUID如何生成的在这里不太多概述,并不是主要的地方,可以自行查询UUID。

JSESSIONID生成演示如下所示:

public class CommonUtils {
	public static String uuid() {
		return UUID.randomUUID().toString().replace("-", "").toUpperCase();
	}
}

    SESSION钝化和活化

Tomcat会在session不被使用时钝化session对象,所谓的钝化session对象,即把session通过序列化的方式保存到硬盘中。而当用户再次使用session时,Tomcat会把钝化的session对象活化。所谓的活化session对象,即把硬盘中序列化的session再反序列化回内存。当session被tomcat钝化时,session中的对象也被钝化;当session被活化时,也会把session中存储的对象活化。

分别创建两个jsp页面,一个页面向session中保存数据,一个页面从session中获取数据,如下所示:

    向session中保存数据
    <%
        session.setAttribute("key", "value");
    %>

    从session中获取数据
    <%
        session.getAttribute("key");
    %>

演示一下序列化,将服务器停止。如果是将项目文件发送到tomcat中,则根据以下路径找到文件夹:tomcat根目录/work/Catalina/localhost/项目名/。在这个文件夹里会发现一个叫sessions.ser的文件,如下所示:

在实现钝化效果前,需要在Tomcat的配置文件夹中的context.xml配置好以下参数:

    
        
    

接下来访问获取数据页面,等待一分钟,会在配置的文件夹内出现一个sessionID.session文件,这个就是钝化后的session。

而重新访问获取数据页面,会发现该文件消失,其是被读取到了内存中,成功显示数据,如图所示:

这就是Session的钝化和活化特性。

后话

        '''
            Cookie和Session其实都不是什么好的东西。Session容易劫持。这是另一部分的内容了。
            在中途讲了Cookie和Session,接下来还要继续讲jsp的指令部分。
            这部分的东西也挺多的,细细考虑后再写吧。
            最后想了想,jsp指令部分和九大内置对象都添加到jsp那一章。
            下一章的内容,JavaBean了解一下就好,其实在Java学习反射的时候就该了解到了
        '''

下一章链接:JavaBean

你可能感兴趣的:(JavaWeb,JavaWeb,Cookie,Session)