[转载]JavaEE学习篇之----Session&&Cookie

原文连接: http://blog.csdn.net/jiangwei0910410003/article/details/23337043

今天继续来看看JavaWeb的相关知识,这篇文章主要来讲一下Session和Cookie的相关知识,首先我们来看一下Cookie的相关知识:

一、Cookie

简介:

Cookie是客户端技术,服务器把每个用户的数据以cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。下面这张图就体现了Cookie技术原理:

javax.servlet.http.Cookie类用于创建一个Cookie,response接口也中定义了一个addCookie方法,它用于在其响应头中增加一个相应

的Set-Cookie头字段。 同样,request接口中也定义了一个getCookies方法,它用于获取客户端提交的Cookie。下面来看一下Cookie

的API:

1.构造函数:Cookie(String name,String value):通过这个构造函数可以指定一个键值对,将信息存入到Cookie中,相同的name的值是会被覆盖的

2.方法:

setMaxAge(int expiry):这个方法是设置Cookie的有效时间,也就是Cookie在浏览器中的缓存时间,单位是秒(s)

setPath(String path):这个方法是设置当访问路径为path的资源的时候携带Cookie信息,比如:setPath("/ServletDemo")这样设置之后就是说在访问应用/ServletDemo中的所有资源的时候都会携带Cookie信息数据,如果不进行设置的话,默认是访问路径会携带Cookie,比如:/ServletDemo/ServletCookieDemo,其中ServletCookieDemo是一个Servlet,ServletDemo是当前的应用的映射路径

setDomain(String domain):这个方法是设置我们在访问domain域的时候会携带Cookie信息数据,这个可以做到第三方Cookie和跨域Cookie的信息访问操作,如:在我们的应用中设置新浪域名sina.com,这个就是第三方Cookie技术,在我的应用中设置Cookie信息,并将这个Cookie的域设置成其他的域名,同时如果我将这个域设置成sina.com,来攻击新浪网站因为cookie也是数据的,如果每次都携带cookie的话,server需要处理cookie,增加server的负担,所以会禁止第三方cookie,所以这个方法是没有效果的。当然我们可以在浏览器中禁止Cookie和第三方Cookie的信息

getMaxAge()/getPath()/getDomain():这三个方法是和上面的三个方法一一对应的,是获取相对应的值

getName():这个方法是获取Cookie中字段的名称,就是我们在Cookie的构造函数中的第一个参数Name,这样我们就可以找到我们想要的Cookie信息

getValue():这个方法是获取Cookie中字段的值,就是我们在Cookie的构造函数中的第二个参数Value,我们通过getName()方法检测到我们想要的Cookie,然后就可以通过这个方法获取Cookie中的信息

下面就来看一下实例:通过Cookie技术来实现客户机上次访问的时间

[java] view plaincopyprint?

  1. //显示上次访问的时间
  2. public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
  3. //设置编码
  4. response.setCharacterEncoding("utf-8");
  5. response.setContentType("text/html;charset=utf-8");
  6. PrintWriter out = response.getWriter();
  7. out.print("你上一次访问的时间是:");
  8. //获得用户的时间cookie,并且获取值,如果第一次的话,是没有Cookie信息的,所以Cookie数组可能为null,所以我们要做判断
  9. Cookie cookies[] = request.getCookies();
  10. for(int i=0;cookies != null && i<cookies.length;i++){
  11. if(cookies[i].getName().equals("lastAccessTime")){
  12. long cookieValue = Long.parseLong(cookies[i].getValue());
  13. Date date = new Date(cookieValue);
  14. out.print(date.toLocaleString());
  15. }
  16. }
  17. //创建每次访问的时候,我们都会回写一个Cookie给客户机,并且将Cookie的有效期设置为30天,路径设置成整个web应用
  18. Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
  19. cookie.setMaxAge(30*24*3600);
  20. cookie.setPath("/ServletDemo");
  21. response.addCookie(cookie);
  22. }
//显示上次访问的时间
	public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
		//设置编码
		response.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		out.print("你上一次访问的时间是:");

		//获得用户的时间cookie,并且获取值,如果第一次的话,是没有Cookie信息的,所以Cookie数组可能为null,所以我们要做判断
		Cookie cookies[] = request.getCookies();
		for(int i=0;cookies != null && i<cookies.length;i++){
			if(cookies[i].getName().equals("lastAccessTime")){
				long cookieValue = Long.parseLong(cookies[i].getValue());
				Date date = new Date(cookieValue);
				out.print(date.toLocaleString());
			}
		}

		//创建每次访问的时候,我们都会回写一个Cookie给客户机,并且将Cookie的有效期设置为30天,路径设置成整个web应用
		Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
		cookie.setMaxAge(30*24*3600);
		cookie.setPath("/ServletDemo");
		response.addCookie(cookie);

	}

在doGet方法中调用这个方法测试一下:

当我们第一次访问ServletCookie的时候,因为是没有Cookie的信息的,所以显示为空,但是会回写一个Cookie给浏览器

当我们再次去访问ServletCookie的时候,就会携带上次回写的Cookie信息,同时此时服务器也会回写给客户机最新的访问时间Cookie信息:

我们也是可以看到的是这次携带的Cookie中的lastAccessTime字段的值是和我们第一次访问的时候服务器回写给客户机的Cookie中的字段值相等的

下面我们在来一下怎么删除一个Cookie信息,有时候我们发现一个Cookie的信息没有用了,但是他的有效期还没过期,所以这时候需要去删除这个Cookie,我们可以直接在浏览器中的直接删除Cookie资源的,当然我们也可以直接在代码中删除的,原理很简单,就是在回写一个相同的Cookie覆盖想要删除的Cookie,并且将这个Cookie的有效时间设置成0,这样就可以删除Cookie了,比如我们删除我们上面的那个Cookie信息:

[java] view plaincopyprint?

  1. //删除Cookie信息
  2. public void test1(HttpServletRequest request,HttpServletResponse response){
  3. //下面是删除cookie
  4. Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
  5. cookie.setMaxAge(0);//将有效期设置成0
  6. cookie.setPath("/ServletDemo");//设置的路径必须要和之前的一样,否则是删除不了的
  7. response.addCookie(cookie);
  8. }
//删除Cookie信息
	public void test1(HttpServletRequest request,HttpServletResponse response){
		//下面是删除cookie
		Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
		cookie.setMaxAge(0);//将有效期设置成0
		cookie.setPath("/ServletDemo");//设置的路径必须要和之前的一样,否则是删除不了的
		response.addCookie(cookie);
	}

我们在次访问ServletCookie的时候,这时候还是会携带一个Cookie信息的,这个Cookie信息是上一次的,此时服务器还是会回写一个有效期为0的Cookie给客户机

当我们再次访问ServletCookie的时候,这时候就不会在携带Cookie信息了

这样我们就成功删除了lastAccessTime这个Cookie的信息

我们在来总结一些Cookie的一些细节:

一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。  一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。 浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。 如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。 注意,删除cookie时,path必须一致,否则不会删除

二、Session

Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它web资源时,其它web资源再从用户各自的session中取出数据为用户服务。

同时Session也是一个域对象,我们之前介绍了两个域对象:ServletContext和Request,那么Session域的作用范围是:默认情况下是在一个会话期间,当然这个范围我们是可以设置的,设置之后可以在多个会话之间。那么Session的生命周期是:

1.Session什么时候创建:

某server端程序(如Servlet)调用HttpServletRequest.getSession(true)这样的语句时才会被创建。

2.Session什么时候销毁:

Session在下列情况下被删除:  A.程序调用HttpSession.invalidate()

B.距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间  C.服务器进程被停止  再次注意关闭浏览器只会使存储在客户端浏览器内存中的session cookie失效,不会使服务器端的session对象失效。

下面在来看一下Session技术原理:

下面在来看一下Session的相关api:

getAttribute(String name)/getAttributeNames()/setAttribute(String name)/removeAttribute(String name):这些方法都是和Session域中的值有关的方法,和之前的ServletContext,Request域的是一样的

getCreationTime():获取Session的创建时间

getId():获取session的id

getServletContext():获取ServletContext对象

invalidate():删除session的方法,就是将session设置成无效的

setMaxInactiveInterval(int interval):这个方法设置session的最大有效时间

下面来看一下Session的第一个例子:

[java] view plaincopyprint?

  1. HttpSession session = request.getSession();
  2. System.out.println("SessionObject:"+session);
  3. System.out.println("SessionId:"+session.getId());
HttpSession session = request.getSession();
System.out.println("SessionObject:"+session);
System.out.println("SessionId:"+session.getId());

打印Session的对象和ID

我们定义两个Servlet,将上面的代码拷贝到Servlet中,访问第一个Servlet1,然后再访问第二个Servlet2,我们看一下打印结果:

运行结果:

SessionObject:[email protected] SessionId:96F3F15432E0660E0654B1CE240C4C36 SessionObject:[email protected] SessionId:96F3F15432E0660E0654B1CE240C4C36

我们可以看到我访问了两个不同的Servlet,但是通过getSession()方法获取到的Session是同一个,所以getSession()这个方法内部的执行过程是先看有没有Session,如果有就不创建了,没有的话就直接创建。

其实request还有一个方法:getSession(boolean mode)

如果mode是true的话,效果是和getSession()方法的效果一样的

如果mode是false的话,效果是:如果有Session就返回,没有的话,就不创建session,而返回一个null

上面我们看到了Session的生命周期,session销毁的时机并不是关闭浏览器,而是在用户在一段时间内(默认是30分钟)不使用了,就自动销毁的,比如:一个人打开一个浏览器,开启一个session,这时候他离开了超过30分钟,等再回来操作的时候,session就被销毁了,当然这个有效时间是可以设置的,上面讲到了两种方法:

一种是通过web.xml文件中配置(单位是分钟):

[java] view plaincopyprint?

  1. <session-config>
  2. <session-out>10</session-out>
  3. </session-config>
	<session-config>
		<session-out>10</session-out>
	</session-config>

另一种是通过代码设置(单位是秒):

session.setMaxInactiveInterval(30*60);

那么我们getSession()方法是怎么判断Session是存在的呢?其实Session的实现原理是基于Cookie的技术实现的,每次我们在创建一个session的时候,都会有一个对应的sessionid,然后会将这个sessionid信息写入到cookie中,但是这个cookie的有效时间是这个浏览器进程中,即这个cookie是在服务器的内存中的,不会回写给客户机缓存到磁盘中。所以我们会在一个浏览器中使用同一个session的,当我们关闭浏览器的时候,这个cookie就被销毁了,那么sessionid就没有了,当我们在打开浏览器的时候,服务端又会重新创建一个session。所以我们会误认为session的生命周期是浏览器进程。当用户关闭了浏览器虽然session cookie已经消失,但session对象仍然保存在服务器端 

假如现在有一个人买了几千本书,准备去付款,但是不小心把浏览器关了,这时候在打开浏览器的时候就会发现之前买的书都不在了,这种情况是很严重的,所以我们要改善这种情况,修改的原理很简单,我们知道那个sessionid是写在cookie中的,但是这个cookie默认的情况下只存在服务器的内存中的,不会回写到客户机浏览器的缓存中。如果我们要是修改的话,只需要修改这个cookie的有效期,将这个cookie回写到客户机浏览器中即可。

[java] view plaincopyprint?

  1. HttpSession session = request.getSession();
  2. String sessionId = session.getId();
  3. Cookie cookie = new Cookie("JSESSIONID",sessionId);//把系统的session id的覆盖掉
  4. cookie.setPath("/ServletDemo");
  5. cookie.setMaxAge(30*360);//30分钟,因为session的生命周期是30分钟
  6. response.addCookie(cookie);
HttpSession session = request.getSession();
String sessionId = session.getId();
Cookie cookie = new Cookie("JSESSIONID",sessionId);//把系统的session id的覆盖掉
cookie.setPath("/ServletDemo");
cookie.setMaxAge(30*360);//30分钟,因为session的生命周期是30分钟
response.addCookie(cookie);

默认携带sessionid信息的cookie字段是:JESSIONID 我们可以覆盖这个信息的,将有效期设置成30分钟,因为默认的session的有效期是30分钟,所以如果将cookie的有效期大于30分钟的话,是没有意义的,因为当超过30分钟之后,session就销毁了,即使携带cookie过去,也是找不到对应的session了,但是我们也是可以设置session的有效时间的。所以我们最好将这个cookie的有效期设置要不超过session的有效期,不然是没有意义的。运行之后,我们可以查看浏览器中的Cookie信息,这里使用IE浏览器,点击F12键,选择缓存-》查看Cookie信息:

上面的就将sessionid的信息以cookie的形式回写到客户机缓存中,当我们关闭浏览器中,在开启浏览器的话,还是可以访问到我们之前的数据的,其实这也就实现了多个浏览器之间共享一个session的机制。

但是现在还有一个问题,如果用户手贱他把浏览器设置成禁止Cookie了,那么又悲剧了,我们服务端写给客户机的Cookie,但是客户机是不接受的,所以这样也是会丢失数据的。那么这时候我们又该怎么办呀?

一种新技术又产生了,那就是URL重写:

我们首先来看一下,用户禁止Cookie之后,我们会发现在同一个浏览器进程中,访问不同的Servlet,数据也是没有的:

我们这里定义三个Servlet:IndexServlet,BuyServlet,PayServlet

模拟的场景就是:IndexServlet是首页,里面写入连个超链接,分别链接到购买物品Servlet:BuyServlet,和付款Servlet:PayServlet,代码如下:

IndexServlet:

[java] view plaincopyprint?

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
  2. req.getSession();
  3. String buyUrl = "/ServletDemo/BuyServlet";
  4. String payUrl = "/ServletDemo/PayServlet";
  5. resp.getWriter().print("<a href=‘"+buyUrl+"‘>Buy</a><br/>");
  6. resp.getWriter().print("<a href=‘"+payUrl+"‘>Pay</a><br/>");
  7. }
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		req.getSession();
		String buyUrl = "/ServletDemo/BuyServlet";
		String payUrl = "/ServletDemo/PayServlet";
		resp.getWriter().print("<a href=‘"+buyUrl+"‘>Buy</a><br/>");
		resp.getWriter().print("<a href=‘"+payUrl+"‘>Pay</a><br/>");
	}

BuyServlet:

[java] view plaincopyprint?

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
  2. //购买了一件物品,将这个物品存入到session中,然后将这个sessionid回写到客户机,有效时间是30分钟
  3. HttpSession session = req.getSession();
  4. session.setAttribute("store", "air-confication");
  5. Cookie cookie = new Cookie("JSESSIONID",session.getId());//把系统的session id的覆盖掉
  6. cookie.setMaxAge(30*360);
  7. cookie.setPath("/ServletDemo");
  8. resp.addCookie(cookie);
  9. }
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		//购买了一件物品,将这个物品存入到session中,然后将这个sessionid回写到客户机,有效时间是30分钟
		HttpSession session = req.getSession();
		session.setAttribute("store", "air-confication");
		Cookie cookie = new Cookie("JSESSIONID",session.getId());//把系统的session id的覆盖掉
		cookie.setMaxAge(30*360);
		cookie.setPath("/ServletDemo");
		resp.addCookie(cookie);
	}

PayServlet:

[java] view plaincopyprint?

  1. public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
  2. //我们从session中拿去商品进行显示
  3. HttpSession session = request.getSession();
  4. response.getWriter().print("you buy store is:"+session.getAttribute("store"));
  5. }
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
		//我们从session中拿去商品进行显示
		HttpSession session = request.getSession();
		response.getWriter().print("you buy store is:"+session.getAttribute("store"));
	}

我们试验禁止浏览器的Cookie:

使用IE浏览器:选择工具-》Internet选项-》隐私-》高级-》禁止Cookie

这里有一个问题要注意,浏览器使用localhost域名,阻止Cookie是没有效果的,这个问题纠结了好长时间!我们需要直接使用:127.0.0.1来测试:http://127.0.0.1:8080/ServletDemo/IndexServlet

我们点击Buy,这时候会回写一个sessionid,但是客户机禁止Cookie了,所以浏览器没有缓存sessionid,不信的话我们可以查看浏览器中缓存的Cookie信息,是没有我们的Cookie的。

然后我们返回在点击Pay的时候发现,购买的东东为null

这个我们在同一个浏览器中都发现购买的东西都不见了,这个很蛋疼的,我们之前不回写sessionid,即使我们禁止Cookie的话也是没有影响的,因为那个Cookie是在浏览器进程中的,不会回写,所以禁止Cookie没有影响。但是我们现在是回写了sessionid的Cookie,所以这个问题我们得解决,上面说过了使用URL重写技术,其实这个技术很简单的,就是将网站中的所以url后面会携带一个sessionid,这样就可以保证当禁止Cookie的时候,我们还是能够得到sessionid,然后找到相应的session。测试:

这里只需要修改IndexServlet中的代码:

[java] view plaincopyprint?

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
  2. resp.setDateHeader("expires", 0);
  3. req.getSession();
  4. String buyUrl = resp.encodeURL("/ServletDemo/BuyServlet");
  5. String payUrl = resp.encodeURL("/ServletDemo/PayServlet");
  6. resp.getWriter().print("<a href=‘"+buyUrl+"‘>Buy</a><br/>");
  7. resp.getWriter().print("<a href=‘"+payUrl+"‘>Pay</a><br/>");
  8. }
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		resp.setDateHeader("expires", 0);
		req.getSession();
		String buyUrl = resp.encodeURL("/ServletDemo/BuyServlet");
		String payUrl = resp.encodeURL("/ServletDemo/PayServlet");
		resp.getWriter().print("<a href=‘"+buyUrl+"‘>Buy</a><br/>");
		resp.getWriter().print("<a href=‘"+payUrl+"‘>Pay</a><br/>");
	}

一定要执行req.getSession()这个方法,来生成一个session,不然encodeURL中是不会得到sessionid的

这里就是重写了,当然我们直接使用encodeURL这个方法,而不是直接自己去将sessionid拼接到url中

访问IndexServlet:

我们可以看到响应信息是我们回写客户机显示的连接,url后面是会携带jsessionid字段的

这样,即使用户禁止了Cookie,也是可以实现数据保留的。但是我们也看到了URL重写的弊端,对于每个url都必须重写,这个是很麻烦的,而且还有一个问题,针对URL重写技术,当我们关闭浏览器的时候,再去访问的时候还是失败,因为我们关闭浏览器,再去打开浏览器访问IndexServlet的时候,会重新创建一个session,这时候在将sessionid重写到url中,所以不是之前的session了,至此,也知道了,URL重写是做不到多个浏览器进程之间的session数据共享的,那么这个问题我们需要去该吗?这个是没办法修改的,只能做到这一步了。

下面在来看一下上面内容的一些细节:

当我们在使用URL重写技术的时候,我们把浏览器中的Cookie不禁止,即保留Cookie和URL重写这两种技术

我们会发现当用户携带Cookie过来的时候,url链接之后是没有jsessionid字段了,这个得益于encodeURL方法中做了判断

所以优先级是Cookie中的sessionid高

下面我们在来通过一个实例来总结一下所讲的内容

实例:防止表单重复提交

方法一:前端使用JS技术

[java] view plaincopyprint?

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  2. <html>
  3. <head>
  4. <title>My JSP ‘1.jsp‘ starting page</title>
  5. <meta http-equiv="pragma" content="no-cache">
  6. <meta http-equiv="cache-control" content="no-cache">
  7. <meta http-equiv="expires" content="0">
  8. <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
  9. <meta http-equiv="description" content="This is my page">
  10. <script type="text/javascript">
  11. var iscommitted = false;
  12. function dosubmits(){
  13. if(!iscommitted){
  14. iscommitted = true;
  15. return true;
  16. }else{
  17. return false;
  18. }
  19. }
  20. </script>
  21. <script type="text/javascript">
  22. function dosubmit(){
  23. var input = document.getElementById("submit");
  24. input.disabled = ‘disabled‘;
  25. return true;
  26. }
  27. </script>
  28. </head>
  29. <body>
  30. <form action="/ServletDemo/FormResubmit1" method="post" onsubmit="return dosubmits()">
  31. <input type="text" name="username">
  32. <input id="submit" type="submit" value="提交">
  33. </form>
  34. </body>
  35. </html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>

    <title>My JSP ‘1.jsp‘ starting page</title>

	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="This is my page">

	<script type="text/javascript">
		var iscommitted = false;
		function dosubmits(){
			if(!iscommitted){
				iscommitted = true;
				return true;
			}else{
				return false;
			}
		}
	</script>

	<script type="text/javascript">
		function dosubmit(){
			var input = document.getElementById("submit");
			input.disabled = ‘disabled‘;
			return true;
		}
	</script>

  </head>

  <body>
  	<form action="/ServletDemo/FormResubmit1" method="post" onsubmit="return dosubmits()">
  		<input type="text" name="username">
  		<input id="submit" type="submit" value="提交">
  	</form>
  </body>
</html>

我们定义了两个js函数,这两个js函数都是可以实现防止表单的重复提交

dosubmits()函数是在用户提交一次之后就通过一个变量来控制不允许提交

dosubmit()函数是在用户提交一次之后就将提交按钮设置成不可点击

但是使用上面的这种方法来防止表单重复提交是有问题的,首先客户是可以通过查看文件的源代码,然后修改的代码的

而且,这种方式解决不了一些问题,比如:当用户点击提交之后,他立刻点击刷新按钮或者回退按钮,然后又可以提交了,所以说前端技术是不能彻底防止表单的重复提交,但是我们还是要这么做的,因为这样做至少还是能起到一定的防止效果的,当然我们还需要在服务器端做处理的,实现原理是:

服务器端首先产生一个随机数,标记一个唯一的表单,然后将这个随机数写入到session域中保存,同时我们将这个随机数使用转发技术携带给一个jsp/html表单中,在使用隐藏标签将这个随机数携带给提交的servlet中,然后再这个servlet中拿到这个隐藏标签的参数值,和我们之前写入到session域中的那个随机数进行比较,正确的话,说明是第一次提交,我们这时候就将那个随机数从session域中删除,当用户再一次点击提交表单的时候,再来判断的时候,因为session域中没有了这个随机数,所以是提交失败的

下面来看一下实现代码:

IndexServlet:

[java] view plaincopyprint?

  1. package com.weijia.servletsession;
  2. import java.io.IOException;
  3. import java.security.MessageDigest;
  4. import java.util.Random;
  5. import javax.servlet.ServletException;
  6. import javax.servlet.http.HttpServlet;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import sun.misc.BASE64Encoder;
  10. //表单重复提交
  11. public class IndexServlet extends HttpServlet{
  12. private static final long serialVersionUID = 1L;
  13. @Override
  14. protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
  15. doPost(req,resp);
  16. }
  17. @Override
  18. protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
  19. resp.setDateHeader("expires", 0);
  20. try{
  21. test(req,resp);
  22. }catch(Exception e){
  23. e.printStackTrace();
  24. }
  25. }
  26. //产生表单
  27. public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
  28. //产生表单号
  29. String token = TokenProcessor.generateToken();
  30. request.getSession().setAttribute("token", token);
  31. request.getRequestDispatcher("/form.jsp").forward(request, response);
  32. }
  33. }
  34. //单例模式,一个人报数比多个人报数重复率底
  35. class TokenProcessor{
  36. private static final TokenProcessor instance = new TokenProcessor();
  37. private TokenProcessor(){
  38. }
  39. public static TokenProcessor getInstance(){
  40. return instance;
  41. }
  42. public static String generateToken(){
  43. String token = System.currentTimeMillis() + new Random().nextInt() + "";
  44. try{
  45. MessageDigest md = MessageDigest.getInstance("md5");
  46. byte[] md5 = md.digest(token.getBytes());
  47. //base64
  48. //0011 0010 1100 1101 0010 1001
  49. //00001100 00101100 00110100 00101001:最小是0,最大的数是63,共64个数
  50. //base码表
  51. //数据传输的作用
  52. BASE64Encoder encoder = new BASE64Encoder();
  53. return encoder.encode(md5);
  54. }catch(Exception e){
  55. return null;
  56. }
  57. }
  58. }
package com.weijia.servletsession;

import java.io.IOException;
import java.security.MessageDigest;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import sun.misc.BASE64Encoder;

//表单重复提交
public class IndexServlet extends HttpServlet{

	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		doPost(req,resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		resp.setDateHeader("expires", 0);
		try{
			test(req,resp);
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	//产生表单
	public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
		//产生表单号
		String token = TokenProcessor.generateToken();
		request.getSession().setAttribute("token", token);
		request.getRequestDispatcher("/form.jsp").forward(request, response);
	}

}

//单例模式,一个人报数比多个人报数重复率底
class TokenProcessor{

	private static final TokenProcessor instance = new TokenProcessor();

	private TokenProcessor(){

	}

	public static TokenProcessor getInstance(){
		return instance;
	}

	public static String generateToken(){
		String token = System.currentTimeMillis() + new Random().nextInt() + "";
		try{
			MessageDigest md = MessageDigest.getInstance("md5");
			byte[] md5 = md.digest(token.getBytes());
			//base64
			//0011 0010 1100 1101 0010 1001
			//00001100 00101100 00110100 00101001:最小是0,最大的数是63,共64个数
			//base码表
			//数据传输的作用
			BASE64Encoder encoder = new BASE64Encoder();
			return encoder.encode(md5);
		}catch(Exception e){
			return null;
		}
	}

}

这里面我们定义了一个令牌发生器(随机数产生器),使用的是单例模式

但是有一个问题就是:使用Random类产生的随机数的长度是不一定的,我们现在想让每次得到的随机数的长度是一样的,此时我们会想到MD5,首先每个数据的MD5是唯一的,还有MD5的长度是一定的128位(16Bytes),但是还有一个问题那个MD5是一个字节数组,当我们将其转化成String类型的时候,不管使用什么码表,都会出现乱码的问题,在随机数中出现了乱码,那就等于是死了,所以我们需要解决这个问题,这时候我们想到了BASE64编码,因为我们知道任何字节数据通过BASE64编码之后生成的字节数据的大小是在0~63之间的(原理很简单,就是将字节数组从左到右,每六位是一组,然后再高位补足两个0,那么这个字节大小的范围就是在0~63了),同时BASE64有一套自己的码表,里面只有大小写字母和数字,这样就不会产生乱码了。

下面在来看一下我们转发的form.jsp中的表单定义:

[html] view plaincopyprint?

  1. <form action="/ServletDemo/FormResubmit" method="post">
  2. <input type="hidden" name="token" value="${token}">
  3. 用户名:<input type="text" name="username"><br/>
  4. <input type="submit" value="提交">
  5. </form>
  <form action="/ServletDemo/FormResubmit" method="post">
  	<input type="hidden" name="token" value="${token}">
  	用户名:<input type="text" name="username"><br/>
  	<input type="submit" value="提交">
  </form>

这里我们使用了隐藏标签hidden,同时也使用了el表达式来取出之前存入到session域中的token字段值,

下面再来看一下处理这个表单的Servlet:FormResubmit

[java] view plaincopyprint?

  1. package com.weijia.servletsession;
  2. import java.io.IOException;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. //表单重复提交
  8. public class FormResubmit extends HttpServlet{
  9. @Override
  10. protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
  11. doPost(req,resp);
  12. }
  13. @Override
  14. protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
  15. boolean flag = isTokenValid(req);
  16. if(!flag){
  17. System.out.println("请不要重复提交");
  18. return;
  19. }
  20. System.out.println("向数据库中注册用户--------");
  21. req.getSession().removeAttribute("token");
  22. }
  23. //验证表单提交是否有效,返回true,表示表单可以提交
  24. public boolean isTokenValid(HttpServletRequest request){
  25. //首先判断传递过来的表单号是否有效
  26. String clientToken = request.getParameter("token");
  27. if(clientToken == null){
  28. return false;
  29. }
  30. //然后再判断服务器端session域中时候又令牌信息了
  31. String serverToken = (String)request.getSession().getAttribute("token");
  32. if(serverToken == null){
  33. return false;
  34. }
  35. //在比较表单携带过来的随机数和session域中的令牌信息是否一致
  36. if(!clientToken.equals(serverToken)){
  37. return false;
  38. }
  39. return true;
  40. }
  41. }
package com.weijia.servletsession;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//表单重复提交
public class FormResubmit extends HttpServlet{

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		doPost(req,resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		boolean flag = isTokenValid(req);
		if(!flag){
			System.out.println("请不要重复提交");
			return;
		}
		System.out.println("向数据库中注册用户--------");
		req.getSession().removeAttribute("token");

	}

	//验证表单提交是否有效,返回true,表示表单可以提交
	public boolean isTokenValid(HttpServletRequest request){
		//首先判断传递过来的表单号是否有效
		String clientToken = request.getParameter("token");
		if(clientToken == null){
			return false;
		}

		//然后再判断服务器端session域中时候又令牌信息了
		String serverToken = (String)request.getSession().getAttribute("token");
		if(serverToken == null){
			return false;
		}

		//在比较表单携带过来的随机数和session域中的令牌信息是否一致
		if(!clientToken.equals(serverToken)){
			return false;
		}

		return true;
	}

}

这样我们就可以防止表单的重复提交了,其实上面的实现原理就是struts框架中防止表单重复提交的原理

总结:这一篇主要讲解了Session和Cookie的相关知识,到此关于JavaWeb的知识,大体上说的差不多了,后面只剩下一个JSP和标签库的相关知识了,这两篇文章会在后面更新。

时间: 2024-11-05 20:37:22

[转载]JavaEE学习篇之----Session&&Cookie的相关文章

[转载]JavaEE学习篇之--JQuery技术详解

原文链接:http://blog.csdn.net/jiangwei0910410003/article/details/32102187 1.简介2.工具3.jQuery对象 1.DOM对象转化成jQuery对象 2.jQuery对象转化成DOM对象4.jQuery选择器 1.基本选择器 2.层次选择器 3.过滤选择器 4.基础过滤选择器 5.内容过滤选择器5.可见度过滤选择器 1.属性过滤选择器 2.子元素过滤选择器 3.表单对象属性过滤选择器 4.表单选择器6.jQuery中的DOM操作

[转载]JavaEE学习篇之--JDBC详解

原文链接:http://blog.csdn.net/jiangwei0910410003/article/details/26164629 目录1.摘要2.JDBC的使用步骤 1.注册驱动 只做一次 2.建立连接Connection 3.创建执行SQL的语句Statement 4.处理执行结果ResultSet 5.释放资源3.使用JDBC来实现CRUD的操作4.Statement中的sql依赖注入的问题5.JDBC中特殊数据类型的操作问题 1.第一个是日期问题 2.第二个问题就是大文本数据的问

[转载]JavaEE学习篇之——网络传输数据中的密码学知识以及Tomcat中配置数字证书EE

原文链接:http://blog.csdn.net/jiangwei0910410003/article/details/21716557 今天是学习JavaWeb的第二天,我们来了解什么呢?就了解一下Tomcat中配置数字证书的相关内容,但是在说这部分内容的时候,我们貌似得先说一下数字证书的相关概念,那说到数字证书的时候我们还得了解一些密码学的相关知识,这就是连锁反应吗?好吧不多说了,先来看一下密码学中关于网络中数据传输的知识. 首先来了解一下网络上传输数据的加密方式: 第一种是对称加密:就是

转载 Tomcat集群配置学习篇-----分布式应用

Tomcat集群配置学习篇-----分布式应用 现目前基于javaWeb开发的应用系统已经比比皆是,尤其是电子商务网站,要想网站发展壮大,那么必然就得能够承受住庞大的网站访问量:大家知道如果服务器访问量过大,就会出现服应用务器崩溃的情况,这个时候怎么办,难道就只能去重启服务器吗?好,如果是一般的小型公益网站到也无所谓,但如果是比如像大型航空公司售票等电子商务网站,每天每小时都有大量的订单业务,如果这些售票系统一旦崩溃后,再去重启,这些时间和客户的损失就直接会影响到航空公司的利益,这些损失如何去避

(转载)OC学习篇之[email&#160;protected]关键字的作用以及#include和#import的区别

前一篇文章说到了OC中类的三大特性,今天我们来看一下在学习OC的过程中遇到的一些问题,该如何去解决,首先来看一下我们之前遗留的一个问题: 一.#import和#include的区别 当我们在代码中使用两次#include的时候会报错:因为#include相当于拷贝头文件中的声明内容,所以会报重复定义的错误 但是使用两次#import的话,不会报错,所以他可以解决重复导入的问题,他会做一次判断,如果已经导入一次就不导入了 二.关键字@class的作用 在来看一下OC中的关键字@class的作用,在

2014-07-09 Java Web的学习(5)-----会话管理(Cookie和Session)

1.什么是会话 会话,牛津词典对其的解释是进行某活动连续的一段时间.从不同的层面看待会话,它有着类似但不全然相同的含义.比如,在web应用的用户看来,他打开浏览器访问一个电子商务网站,登录.并完成购物直到关闭浏览器,这是一个会话.而在web应用的开发者开来,用户登录时我需要创建一个数据结构以存储用户的登录信息,这个结构也叫做会话.因此在谈论会话的时候要注意上下文环境.而本文谈论的是一种基于HTTP协议的用以增强web应用能力的机制或者说一种方案,它不是单指某种特定的动态页面技术,而这种能力就是保

express学习(三)—— cookie和session

express学习(三)-- cookie和session cookie存在浏览器中,最大只能保存4K数据,不安全 session存在服务器中,不能独立(先读取cookie再读取session),较安全 cookie 工具 发送cookie: 基本的东西先写好: const express = require('express'); var server=express(); // cookie server.use('/',function(req,res){ res.cookie('名字',

(转载)OC学习篇之---概述

前言 终于开启了OC的学习篇了,之前由于工作上的事,学习就一直搁浅了,不过最近由于各种原因,感觉必须要开启iOS的开发旅程了,不然就老了.因为之前一直是做Android的,所以学习iOS来就没那么费劲了,当然我们知道,Android是Java语言支撑的,iOS是OC支撑的,关于OC的学习,会和Java相对比这来,这样效率也会高点,同时在大学里学了C/C++所以,学习OC就没什么难度了,就是一套新的api. 概述 目前来说,Objective-C(简称OC)是iOS开发的核心语言,在开发过程中也会

(转载)OC学习篇之---单例模式

在之前的一片文章中介绍了对象的拷贝相关知识,今天我们来看一下OC中的单例模式,单例模式在设计模式中用的可能是最多的一种了,而且也是最简单的一种 实现单例模式有三个条件 1.类的构造方法是私有的 2.类提供一个类方法用于产生对象 3.类中有一个私有的自己对象 针对于这三个条件,OC中都是可以做到的 1.类的构造方法是私有的 我们只需要重写allocWithZone方法,让初始化操作只执行一次 2.类提供一个类方法产生对象 这个可以直接定义一个类方法 3.类中有一个私有的自己对象 我们可以在.m文件