会话技术——Cookies和Session详解

会话技术

(一) 概述、用途以及分类

(1) 基本概述

概述:会话是浏览器和服务器之间的屡次请求和响应javascript

也就是说,从浏览器访问服务器开始,到访问服务器结束浏览器关闭为止的这段时间内容产生的屡次请求和响应,合起来叫作浏览器和服务器之间的一次会话html

(2) 为何要使用会话技术呢?

实际上会话问题解决的仍是客户端与服务器之间的通讯问题,经过一些会话技术,能够将每一个用户的数据以例如cookie/session的形式存储,方便之后用户访问web资源的时候使用java

假定场景:A和B两人在某个网上购物商场登录帐号后,A买了一个HHKB的键盘,而B则购买了一把民谣吉他,这些信息都会被保存下来web

用途是:保存帐户信息,登陆时询问往后是否自动登陆,或者根据以前浏览,购买过的商品,分析用户喜欢什么类型的商品,作出精准推送算法

那么能不能用咱们以前学过的 HttpServletRequest 对象和 ServletContext 对象来保存这些数据呢?答案是否认的数据库

不能用 HttpServletRequest的缘由:咱们的一次会话中,存在屡次请求和响应,而浏览器客户端的每一次请求都会产生一个 HttpServletRequest 对象,它只会保存这次请求的信息,例如放入购物车与购买付款是不一样的请求,很显然数据没有获得很好的保存处理跨域

不能用 ServletContext 的缘由:ServletContext对象是被整个web应用所共享的,将数据都存到这里,无疑会没法区分具体信息的归属数组

(3) 分类

客户端会话技术 —— Cookie浏览器

服务器会话技术 —— Sessiontomcat

Cookie 技术

(一) 基本认识

(1) 概述

Cookies 能够简单的理解为服务器暂存在你浏览器中的一些信息文件,它将你在网站上所输入的一些内容,或者一些选项记录下来,当下一次你访问同一个网站的时候,服务器就会主动去查询这个cookie资料,若是存在的话,将会根据其中的内容,提供一些特别的功能,例如记住帐号密码等

总结一下就是:

  • 网页之间的交互是经过HTTP协议传输数据的,而Http协议是无状态的协议 (数据提交后,浏览器和服务器的连接就会关闭,在此交互的时候 须要从新创建新的链接)
  • 服务器没法确认用户的信息,因而给每个用户发一个通行证,经过此确认用户的信息

(2) 具体流程

浏览器访问服务器,若是服务器须要记录该用户的状态,就用response向浏览器发送一个cookie,浏览器会把Cookie保存起来。当浏览器再次访问服务器的时候,浏览器会把请求的网址以及Cookie一同提交给服务器

(3) 规范

  • Cookie大小上限为4KB;

  • 一个服务器最多在客户端浏览器上保存20个Cookie;

  • 一个浏览器最多保存300个Cookie

    面的数据是HTTP对Cookie的规范,可是如今一些浏览器可能会对Cookie规范 作了一些扩展,例如每一个Cookie的大小为8KB,最多可保存500个Cookie等

不一样的浏览器之间是不共享Cookie的

(二) 经常使用 API

//用于在其响应头中增长一个相应的Set-Cookie头字段
addCookie

//用于获取客户端提交的Cookie
GetCookie public Cookie(String name,String value) //该方法设置与 cookie 关联的值。 setValue //该方法获取与 cookie 关联的值。 getValue //该方法设置 cookie 过时的时间(以秒为单位)。若是不这样设置,cookie只会在当前 session 会话中持续有效。 setMaxAge //该方法返回 cookie 的最大生存周期(以秒为单位),默认状况下,-1 表示 cookie 将持续下去,直到浏览器关闭 getMaxAge //该方法设置 cookie 适用的路径。若是您不指定路径,与当前页面相同目录下的(包括子目录下的)全部 URL 都会返回 cookie。 setPath //该方法获取 cookie 适用的路径。 getPath //该方法设置 cookie 适用的域 setDomain //该方法获取 cookie 适用的域 getDomain 复制代码

(1) 注意:

  • Cookie具备不可跨域名性
  • ookie保存中文乱码问题:中文属于Unicode字符,英文数据Ascii字符,中文占4个字符或者3个字符,英文占2个字符,Cookie使用Unicode字符时须要对Unicode字符进行编码
Cookie cookie = new Cookie("xxx",URLEncoder.encode(name,"UTF-8"));
复制代码

(2) 有效期

经过setMaxAge()方法能够设置Cookie的有效期

  • 若是MaxAge为正数,浏览器会把Cookie写到硬盘中,只要还在MaxAge秒以前,登陆网站时该Cookie就有效【不论关闭了浏览器仍是电脑】 若是MaxAge为负数,Cookie是临时性的,尽在本浏览器内有效,关闭浏览器Cookie 就失效了,Cookie不会写到硬盘中。Cookie默认值就是 -1
  • 若是MaxAge为0,则表示删除该Cookie

(3) 删除和修改

Cookie存储的方式相似于Map集合,分为名字和值,只不过二者都是String类型的

修改

String name = "刮风这天";
Cookie cookie = new Cookie("country",URLEncoder.encode(name,"UTF-8"));
复制代码

删除

String name = "刮风这天";
Cookie cookie = new Cookie("country",URLEncoder.encode(name,"UTF-8"));

cookie.setMaxAge(0);
response.addCookie(cookie);

printWriter.writer("Cookie已经被删除了")
复制代码

(三) Cookie的域名

Cookie的domain属性决定运行访问Cookie的域名,Deomain的值规定为“.域名”

Cookie的隐私安全机制决定Cookie是不可跨域名的。及时是同一级域名,不一样的二级域名也不能交接,eg:www.ideal.com 和 www.image..com

若是我但愿一级域名相同的网页之间的Cookie之间能够互相访问,须要使用到domain方法

Cookie cookie = new Cookie("name","admin");
cookie.setMaxAge(1000);
cookie.setDomain(".ideal.com);
response.addCookie(cookie);
printWriter.writer("使用www.ideal.com域名添加了一个Cookie,只要一级域名是ideal.com便可访问")
复制代码

(四) Cookie的路径

Cookie的path属性决定容许访问Cookie的路径

通常来讲,Cookie发布出来,整个网页的资源均可以使用,可是若是只须要某一个Servlet能够获取到Cookie,其余的资源不能或不须要获取

Cookie cookie = new Cookie("name","admin");
cookie.setPath("/Servlet);
cookie.setMaxAge(1000);
response.addCookie(cookie);
printWriter.writer("该Cookie只能在Servlet1中能够访问到")
复制代码

(五) Cookie的安全属性

HTTP协议不只是无状态的,并且是不安全的!若是不但愿Cookie在非安全协议中传输,能够设置Cookie的secure属性为true,浏览器只会在HTTPS和SSL等安全协议中传输该Cookie

设置secure属性不会将Cookie的内容加密,若是想保证安全,最好使用md5算法加密

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置响应的消息体的数据格式以及编码
        resp.setContentType("text/html;charset=utf-8");

        //获取全部Cookie
        Cookie[] cookies = req.getCookies();
    	////没有cookie为lastTime
        boolean flag = false;
        //遍历cookie数组
        if(cookies != null && cookies.length > 0){
            for (Cookie cookie : cookies) {
                //获取cookie的名称
                String name = cookie.getName();
                //判断名称是不是:lastTime
                if("lastTime".equals(name)){
                    //非第一次访问

                    flag = true;//有访问记录的time

                    //设置Cookie的value
                    //获取当前时间的字符串,从新设置Cookie的值,从新发送cookie
                    Date date  = new Date();
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
                    String str_date = sdf.format(date);
                    System.out.println("编码前:"+str_date);
                    //URL编码
                    str_date = URLEncoder.encode(str_date,"utf-8");
                    System.out.println("编码后:"+str_date);
                    cookie.setValue(str_date);
                    //设置cookie的存活时间
                    cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
                    resp.addCookie(cookie);

                    //响应数据
                    //获取Cookie的value,时间
                    String value = cookie.getValue();
                    System.out.println("解码前:"+value);
                    //URL解码:
                    value = URLDecoder.decode(value,"utf-8");
                    System.out.println("解码后:"+value);
                    resp.getWriter().write("<h1>欢迎回来,您上次访问时间为:"+value+"</h1>");

                    break;
                }
            }
        }

        if(cookies == null || cookies.length == 0 || flag == false){
            //没有,第一次访问

            //设置Cookie的value
            //获取当前时间的字符串,从新设置Cookie的值,从新发送cookie
            Date date  = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
            String str_date = sdf.format(date);
            System.out.println("编码前:"+str_date);
            //URL编码
            str_date = URLEncoder.encode(str_date,"utf-8");
            System.out.println("编码后:"+str_date);

            Cookie cookie = new Cookie("lastTime",str_date);
            //设置cookie的存活时间
            cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
            resp.addCookie(cookie);

            resp.getWriter().write("<h1>您好,欢迎您首次访问</h1>");
        }
    }
复制代码

Character[32]在ASSCI码中表明空格 因此在日期表示格式中尽可能不要出现空格,但若想要要求出现空格,或者特殊字符,

此外呢,我么你还能够作一个模拟显示上次浏览过商品记录的Demo,自行练习

Session

(一) 概述

Session是另外一种记录浏览器状态的机制,Cookie保存在浏览器中,Session保存在服务器中。用户使用浏览器访问服务器的时候,服务把用户的信息,以某种形式记录在服务器,这就是Session

为什么使用Session由于Session能够存储对象,Cookie只能存储字符串能够解决不少Cookie解决不了的问题

(二) API

//获取Session被建立时间
long getCreationTime() //获取Session的id String getId() //返回Session最后活跃的时间 long getLastAccessedTime() //获取ServletContext对象 ServletContext getServletContext() //设置Session超时时间 void setMaxInactiveInterval(int var1) //获取Session超时时间 int getMaxInactiveInterval() //获取Session属性 Object getAttribute(String var1) //获取Session全部的属性名 Enumeration getAttributeNames() //设置Session属性 void setAttribute(String var1, Object var2) //移除Session属性 void removeAttribute(String var1) //销毁该Session void invalidate() //该Session是否为新的 boolean isNew() 复制代码

Session有着request和ServletContext相似的方法。其实Session也是一个域对象。Session做为一种记录浏览器状态的机制,只要Session对象没有被销毁,Servlet之间就能够经过Session对象实现通信

设置

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession httpSession = request.getSession();
		httpSession.setAttribute("name", "test");
	}
复制代码

获取

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession httpSession = request.getSession();
		String value = (String) httpSession.getAttribute("name");
		System.out.println(value);
	}
复制代码

(三) 生命周期和有效期

用户第一次访问服务器Servlet,jsp等动态资源就会自动建立Session,Session对象保存在内存里,这也就为何上面的例子能够直接使用request对象获取获得Session对象

若是访问HTML,Image等静态资源Session不会被建立

Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,不管是否对Session进行读写,服务器都会认为Session活跃了一次。

因为会有愈来愈多的用户访问服务器,所以Session也会愈来愈多。为了防止内存溢出,服务器会把长时间没有活跃的Session从内存中删除,这个时间也就是Session的超时时间

Session的超时时间默认是30分钟,有三种方式能够对Session的超时时间进行修改

第一种方式:在tomcat/conf/web.xml文件中设置,时间值为20分钟,全部的WEB应用都有效————<session-timeout>20<session-timeout>

第二种方式:在单个的web.xml文件中设置,对单个web应用有效,若是有冲突,以本身的web应用为准

第三种方式:经过setMaxInactiveInterval()方法设置

httpSession.setMaxInactiveInterval(60);
复制代码

(四) Session 和 Cookie的小区别

  • Session周期指的是不活动的时间,若是咱们设置Session是10s,在10s内,没有访问session,session中属性失效,若是在9s的时候,你访问了session,则会从新计时

  • 若是重启了tomcat,或者reload web应用,或者关机了,Session也会失效,咱们也能够经过函数让Session失效,invalidate()该方法是让Session中的全部属性失效,经常用于安全退出

  • 若是你但愿某个Session属性失效,可使用方法removeAttribute

  • Cookie的生命周期就是按累积的时间来算的,无论用户有没有访问过Session

(五) Session

问题:我再Aservlet中设置了Session属性,在Bservlet中获取A的属性

在浏览器中新建一个页面再次访问Bservlet 报空指针异常

如今问题来了:服务器是如何实现一个session为一个用户浏览器服务的?换个说法:为何服务器可以为不一样的用户浏览器提供不一样session?

HTTP协议是无状态的,Session不能依据HTTP链接来判断是否为同一个用户。因而乎:服务器向用户浏览器发送了一个名为JESSIONID的Cookie,它的值是Session的id值。其实Session依据Cookie来识别是不是同一个用户。

简单来讲:Session 之因此能够识别不一样的用户,依靠的就是Cookie

该Cookie是服务器自动颁发给浏览器的,不用咱们手工建立的。该Cookie的maxAge值默认是-1,也就是说仅当前浏览器使用,不将该Cookie存在硬盘中

流程概述:

  • 访问Aservlet时,服务器就会建立一个Session对象,执行咱们的程序代码,执行咱们的程序代码,并自动颁发一个Cookie给用户浏览器
  • 当我用同一个浏览器访问BServlet的时候,浏览器会把Cookie的值经过Http协议带过去给服务器,服务器就知道用哪个Session
  • 而当咱们使用新会话的浏览器访问BServlet的时候,该新浏览器并无Cookie,服务器没法辨认使用哪个Session,因此获取不到值。

(六) 浏览器禁用Cookie后Session的使用

遇到两种状况:1.用户浏览器禁用了Cookie绝大多数手机浏览器都不支持Cookie

Java Web提供了解决方法:URL地址重写

HttpServletResponse类提供了两个URL地址重写的方法:

encodeURL(String url)

encodeRedirectURL(String url)
复制代码

须要值得注意的是:这两个方法会自动判断该浏览器是否支持Cookie,若是支持Cookie,重写后的URL地址就不会带有jsessionid了【固然了,即便浏览器支持Cookie,第一次输出URL地址的时候仍是会出现jsessionid(由于没有任何Cookie可带)】

例子

String url = "/web-01/Servlet5";
response.sendRedirect(response.encodeURL(url));
复制代码

URL地址重写的原理:

将Session的id信息重写到URL地址汇总,服务器解析重写后URL获取Session的id,这样一来即便浏览器禁用掉了Cookie,可是Session的id经过服务端传递,仍是可使用Session来记录用户的状态。

(七) Session案例

案例一:使用Session完成用户简单登陆

先建立User类

public class User {
	private String username = null;
	private String password = null;
	
	public User() {
	}

	public User(String username, String password) {
		super();
		this.username = username;
		this.password = password;
	}

......各类set get方法
复制代码

使用简单的集合模拟一个数据库

public class UserDB {
	private static List<User> list =new ArrayList<>();
	
	static {
		list.add(new User("admin","888"));
		list.add(new User("aaa","111"));
		list.add(new User("bbb","222"));
	}
	
	//经过用户名密码查找用户
	public static User find(String username, String password) {
		
		for (User user:list) {
			if (user.getUsername().equals(username)&& user.getPassword().equals(password)) {
				return user;
			}
		}
		return null;
	}
}
复制代码

表单提交咱们写在jsp里面(模仿便可后期说jsp)

public class UserDB {
	private static List<User> list =new ArrayList<>();
	
	static {
		list.add(new User("admin","888"));
		list.add(new User("aaa","111"));
		list.add(new User("bbb","222"));
	}
	
	//经过用户名密码查找用户
	public static User find(String username, String password) {
		
		for (User user:list) {
			if (user.getUsername().equals(username)&& user.getPassword().equals(password)) {
				return user;
			}
		}
		return null;
	}
}
复制代码

获取表单提交的数据,查找数据库是否有相对应的用户名和密码

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=UTF-8");
		
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		
		User user = UserDB.find(username, password);
		
		//若是找不到,就是用户名或者密码出错了
		if (user == null) {
			response.getWriter().write("用户名或者密码错误,登录失败 !");
			return;
		}
		
		//标志着用户已经登陆
		HttpSession httpSession = request.getSession();
		httpSession.setAttribute("user", user);
		
		//跳转到其余页面,告诉用户已经登陆成功
		response.sendRedirect(response.encodeURL("test.jsp"));
	}
复制代码

案例二:利用Session防止表单重复提交

重复提交的危害:

在投票的网页上不停地提交,实现了刷票的效果。

注册多个用户,不断发帖子,扰乱正常发帖秩序。

常见的两种重复提交

第一种:后退再提交

第二种:网络延迟,屡次点击提交按钮

略图

解决方案:

网络延迟问题:

对于第二种网络延而形成屡次提交数据给服务器,实际上是客户端的问题,咱们可使用javaScript来防止

→ 当用户第一次点击提交按钮是,把数据提交给服务器,当用户再次点击提交按钮时,就不把数据提交给服务器了

监听用监听事件。只能让用户提交一次表单:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>表单提交</title>
<script type="text/javascript">
	//定义一个全局标识量:是否已经提交过表单数据
	var isCommitted = false;

	function doSubmit() {
		//false表示的是没有提交过,因而就可让表单提交给Servlet
		if (isCommited == false){
			is Commited = true;
			return true;
		}else{
			return false;
		}	
	}
</script>
</head>
<body>
	<form action="/web-01/Lservlet" method="post" onsubmit="return doSubmit()">
		用户名:<input type="text" name="username"><br /> <input
			type="submit" value="提交">
	</form>
</body>
</html>
复制代码

刷新后退再提交问题:

咱们知道Session能够用来标识一个用户是否登录了。Session的原理也说了:不一样的用户浏览器会拥有不一样的Session。而request和ServletContext为何就不行呢?request的域对象只能是一次http请求,提交表单数据的时候request域对象的数据取不出来。ServletContext表明整个web应用,若是有几个用户浏览器同时访问,ServletContext域对象的数据会被屡次覆盖掉,也就是说域对象的数据就毫无心义了。

此时,咱们就想到了,在表单中还有一个隐藏域,能够经过隐藏域把数据交给服务器。

A:判断Session域对象的数据和jsp隐藏域提交的数据是否对应。

B:判断隐藏域的数据是否为空【若是为空,就是直接访问表单处理页面的Servlet】

C:判断Session的数据是否为空【servlet判断完是否重复提交,最好能立马移除Session的数据,否则尚未移除的时候,客户端那边儿的请求又来了,就又能匹配了,产生了重复提交。若是Session域对象数据为空,证实已经提交过数据了!】

D:咱们向Session域对象的存入数据到底是什么呢?简单的一个数字?好像也行啊。由于只要Session域对象的数据和jsp隐藏域带过去的数据对得上号就好了呀,反正在Servlet上判断完是否重复提交,会立马把Session的数据移除掉的。更专业的作法是:向Session域对象存入的数据是一个随机数【Token--令牌】

public class TokenProcessor {
	private TokenProcessor() {
	}

	private final static TokenProcessor TOKEN_PROCESSOR = new TokenProcessor();

	public static TokenProcessor getInstance() {
		return TOKEN_PROCESSOR;
	}

	public String makeToken() {
		// 这个随机生成出来的Token的长度是不肯定的
		String token = String.valueOf(System.currentTimeMillis() + new Random().nextInt(99999999));
		try {
			// 咱们想要随机数的长度一致,就要获取到数据指纹
			MessageDigest messageDigest = MessageDigest.getInstance("md5");
			byte[] md5 = messageDigest.digest(token.getBytes());

			// 若是咱们直接 return new String(md5)出去,获得的随机数会乱码
			// 由于随机数是任意的01010101010,在转换成字符串的时候,会差gb2312的码表
			// gb2312码表不必定支持该二进制数据,获得的就是乱码
			// 因而通过base64编码成了明文的数据
			BASE64Encoder base64Encoder = new BASE64Encoder();
			return base64Encoder.encode(md5);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return null;
	}
}
复制代码

建立Token随机数,利用getRequestDispatcher跳转到jsp页面(地址仍是Servlet的)

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 生出随机数
		TokenProcessor tokenProcessor = TokenProcessor.getInstance();
		String token = tokenProcessor.makeToken();

		// 将随机数存进Session中
		request.getSession().setAttribute("token", token);

		// 跳转到显示页面
		request.getRequestDispatcher("/login3.jsp").forward(request, response);
复制代码

Jsp隐藏域获取到Session的值

<form action="/web-01/Mservlet" >

    用户名:<input type="text" name="username">
    <input type="submit" value="提交" id="button">

    <%--使用EL表达式取出session中的Token--%>
    <input type="hidden" name="token" value="${token}" >

</form>
复制代码

在处理表单提交页面中判断:jsp隐藏域是否有带值过来,Session中的值是否为空,Session中的值和jsp隐藏域带过来的值是否相等

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter printWriter = response.getWriter();
		String serverValue = (String) request.getSession().getAttribute("token");
		String clienValue = request.getParameter("token");

		if (serverValue != null && clienValue != null && serverValue.equals(clienValue)) {
			printWriter.write("处理请求");
			// 清除Session域对象数据
			request.getSession().removeAttribute("token");
		} else {
			printWriter.write("请不要重复提交数据");
		}
	}
复制代码

实现原理是很是简单的

在session域中存储一个token

而后前台页面的隐藏域获取获得这个token

在第一次访问的时候,咱们就判断seesion有没有值,若是有就比对。对比正确后咱们就处理请求,接着就把session存储的数据给删除了

等到再次访问的时候,咱们session就没有值了,就不受理前台的请求了!

(八) Session和Cookie的区别

从存储方式上比较

Cookie只能存储字符串,若是要存储非ASCII字符串还要对其编码。

Session能够存储任何类型的数据,能够把Session当作是一个容器

从隐私安全上比较

Cookie存储在浏览器中,对客户端是可见的。信息容易泄露出去。若是使用Cookie,最好将Cookie加密

Session存储在服务器上,对客户端是透明的。不存在敏感信息泄露问题。

从有效期上比较

Cookie保存在硬盘中,只须要设置maxAge属性为比较大的正整数,即便关闭浏览器,Cookie仍是存在的

Session的保存在服务器中,设置maxInactiveInterval属性值来肯定Session的有效期。而且Session依赖于名为JSESSIONID的Cookie,该Cookie默认的maxAge属性为-1。若是关闭了浏览器,该Session虽然没有从服务器中消亡,但也就失效了。

从对服务器的负担比较

Session是保存在服务器的,每一个用户都会产生一个Session,若是是并发访问的用户很是多,是不能使用Session的,Session会消耗大量的内存。

Cookie是保存在客户端的。不占用服务器的资源。像baidu、Sina这样的大型网站,通常都是使用Cookie来进行会话跟踪。

从浏览器的支持上比较

若是浏览器禁用了Cookie,那么Cookie是无用的了!

若是浏览器禁用了Cookie,Session能够经过URL地址重写来进行会话跟踪。

从跨域名上比较

Cookie能够设置domain属性来实现跨域名

Session只在当前的域名内有效,不可跨域名

结尾:

若是内容中有什么不足,或者错误的地方,欢迎你们给我留言提出意见, 蟹蟹你们 !^_^

若是能帮到你的话,那就来关注我吧!(系列文章均会在公众号第一时间更新)

在这里的咱们素不相识,却都在为了本身的梦而努力 ❤

一个坚持推送原创Java技术的公众号:理想二旬不止

相关文章
相关标签/搜索