Java Web基础学习——Cookie&&Session

本文参与「少数派读书笔记征文活动」https://sspai.com/post/45653

参考文章:https://blog.csdn.net/jiangwei0910410003/article/details/23337043

遇到的问题:

1、jstl包的导入(Idea 2018.02):首先我通过File->Project Structure->Libraries直接添加jstl的jar包失败,报错:org.apache.jasper.JasperException: This absolute uri http://java.sun.com/jsp/jstl/core) cannot be resolved in either web.xml or the jar files deployed with this application;解决方法:在web/WEB-INF下新建lib,然后将jstl 1.2(http://tomcat.apache.org/taglibs/standard/)的四个jar包全部导入lib中,右键lib->Add as Library
记得在jsp中添加:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
注意:不同jstl的版本对servlet和jsp版本的要求也不同,查看servlet和jsp版本的方法:找到tomcat解压文件夹->lib->jsp-api.jar/servlet-api.jar分别解压->META-INF->MANIFEST.MF


2、EL表达式取值过程:
EL表达式取值必需是servlet四大作用域(servletContext>session>request>pageContext)中有的值,这四个域都有setAttribute()和getAttribute(); EL表达式会自动从四大域中按作用范围从小到大搜寻对应名字的值,其内部调用的就是pageContext的findAttribute()

3、提交表单方式(Post/Get):
get方式效率高但安全性低,post是封装后进行提交安全性高。get方式常用于搜索、查询;post方法常用于用户注册登陆等

4、servlet页面跳转(转发/重定向):
HttpServletResponse.sendRedirect(url)——请求重定向

  • 可以重定向到任何url
  • 重定向后浏览器url变为目的url
  • 浏览器接收到了响应之后又向服务器发送一次请求,所以相当于两次请求

RequestDispatcher.forward(url)——请求转发

  • 只能转发到同一个Web应用程序中的资源,url中的“/xxx”表示相对于WEB应用的路径
  • 转发后,浏览器url不变
  • 在执行当前文件的过程中转向执行目标文件,两个文件(当前文件/目标文件)属于同一次请求(共用request和response对象)

无论是forward方法还是sendRedirect方法调用前面都不能有PrintWriter输出到客户端
forward方法报错: java.lang.IllegalStateException: Cannot forward after response has been committed
sendRedirect报错:java.lang.IllegalStateException

5、web.xml加载过程:
当启动一个WEB项目时,容器包括(JBoss、Tomcat等)首先会读取项目web.xml配置文件里的配置,当这一步骤没有出错并且完成之后,项目才能正常地被启动起来。后续加载顺序是:<context-param>-><listener>-><filter>-><servlet>;如果web.xml中出现了相同的元素,则按照在配置文件中出现的先后顺序来加载

6、meta:

  • HTML <meta> Tag

例子:

<head>
  <meta charset="UTF-8">
  <meta name="description" content="My Jsp Page">
  <meta name="keywords" content="HTML,CSS,XML,JavaScript">
  <meta name="author" content="baozi">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

Metadata元数据是关于数据的数据(信息);元数据不会显示在页面上,但可以进行机器分析

  • HTML <meta> http-equiv Attribute

例子(每30秒刷新一次页面):

<head>
  <meta http-equiv="refresh" content="30">
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <meta http-equiv="default-style" content="the document's preferred stylesheet"> 
</head>

http-equiv属性为content属性的information/value提供http header;http-equiv属性可用于模拟http响应头

7、设置无缓存:

response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", -10);

以上设置方式有一个缺点:本地浏览器可以read,但是HTTP代理无法read
HTTP代理参考:https://imququ.com/post/web-proxy.html

8、抓包:
除了使用google或火狐自带的开发者工具抓包外,可使用wireshark;但是一般附带安装的WinPcap无法抓本地包,所以建议用Npcap(https://nmap.org/npcap/)替换WinPcap,安装时记得勾选Use DLT NULL as the loopback interface...和Install Npcap in WinPcap API...
启动wireshark,就会发现网络接口列表中的Npcap Loopback adapter,这个就是用来抓本地回环包的网络接口(如果是ipv6,localhost解析为::1)
注意:安装Npcap后可能出现无法连接网络的状况,此时只需通过控制面板->网络和Internet->网络和共享中心->更改适配器设置->右键禁用Npcap Loopback adapter即可;连接网络后在再启用

Cookie

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

javax.servlet.http.Cookie类用于创建一个Cookie,HttpServletResponse接口也定义了一个addCookie(Cookie cookie),用于在响应头中增加一个相应的Set-Cookie头字段;同样,HttpServletRequest接口也定义了一个getCookies(),它用于获取客户端提交的Cookie

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

  • void  setMaxAge(int expiry):设置Cookie的有效时间,也就是Cookie在浏览器中的缓存时间,单位是秒(s)
  • void  setPath(String uri):指定客户端返回Cookie的路径path,即当访问路径为path的资源的时候会携带Cookie信息;如果不进行设置的话,默认是访问路径会携带Cookie
  • void  setDomain(String domain):设置我们在访问domain域的时候会携带Cookie信息数据,这个可以做到第三方Cookie和跨域Cookie的信息访问操作,如:在我们的应用中设置新浪域名sina.com,这个就是第三方Cookie技术;在应用中设置Cookie信息,并将这个Cookie的域设置成sina.com,来攻击新浪网站,因为cookie也是数据的,如果每次都携带cookie的话,server需要处理cookie,增加server的负担,所以会禁止第三方cookie。我们可以在浏览器中禁止Cookie和第三方Cookie的信息
  • void setValue(String newValue):为当前Cookie设置新值
  • int  getMaxAge():获取Cookie的有效时间
  • String  getValue():获取Cookie中字段的值
  • String  getPath():获取返回当前Cookie的路径
  • String  getDomain():获取当前Cookie的域名

例子1:使用Cookie技术查看客户端上次访问的时间

package com.lovebaobao.cookieconfig;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.Date;

@WebServlet(name="Test",
    urlPatterns = "/servlet/Test")
public class Test extends HttpServlet {

    private static final long serialVersionUID = 1L;

    public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException{
        test(request,response);
    }

    public void test(HttpServletRequest request,HttpServletResponse response)throws IOException {
        //设置编码
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("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")){     //一个Cookie对应一组信息(键值对),多个Cookie则构成一个用户的信息
                long cookieValue=Long.parseLong(cookies[i].getValue());

                /*
                Date(long date):Allocates a Date object and initializes it to represent the specified number of
                milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT.
                 */

                Date date=new Date(cookieValue);
                DateFormat formatdate=DateFormat.getDateTimeInstance();   //获取实例(精确到秒)(DateFormat为抽象类)
                out.print(formatdate.format(date));
            }
        }

        //创建每次访问的时候,都回写一个Cookie给客户端,并且将Cookie的有效期设置为30天,路径设置成整个Web
        Cookie cookie=new Cookie("lastAccessTime",System.currentTimeMillis()+"");
        //删除Cookie(写一个相同的Cookie覆盖想要删除的Cookie,并将有效时间设置成0)
        //cookie.setMaxAge(0);
        cookie.setMaxAge(30*24*3600);
        cookie.setPath("/");        //整个Web
        response.addCookie(cookie);

    }
}

第一次运行结果:
Cookie信息

Header信息

再次运行结果:
Cookie信息

Header信息

删除Cookie信息,只需将有效期设置为0:cookie.setMaxAge(0);(cookie名称和路径path必须与要覆盖的cookie相同!)

第一次运行结果:
Cookie信息

Header信息

再次运行结果:
Cookie信息

Header信息

总结:

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

注意:很多Web浏览器支持会话恢复功能,这个功能可以使浏览器保留所有tab标签,然后在重新打开浏览器的时候将其还原,与此同时,cookie也会恢复
另一种设置cookie有效期的方式(在特定的日期(Expires)才会失效):
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie

Session

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

Session技术原理如图:

同时javax.servlet.http.HttpSession接口也是一个域对象;Session的生命周期是
1、创建
某server端程序(如Servlet)调用HttpServletRequest.getSession(true)这样的语句时才会被创建(如果当前请求存在session则不会重新创建)
2、销毁
(1)程序调用HttpSession.invalidate()
(2)距离上一次客户端发送的session id时间间隔超过了session的最大有效时间
(3)服务器进程被停止
注意:关闭浏览器只会使存储在客户端浏览器内存中的session cookie失效,不会使服务器端的session对象失效

Session API:

  • Object  getAttribute(String name):返回在此会话中使用指定名称绑定的对象
  • Enumeration<String>  getAttributeNames():返回此会话中所有名称
  • void setAttribute(String name,Object value):绑定一个指定名称的对象到此会话
  • void removeAttribute(String name):从此会话中移除某对象
  • long  getCreationTime():返回创建此会话的时间(即从格林威治标准时间1970.1.1至今的毫秒数)
  • ServletContext  getServletContext():返回ServletContext对象到当前会话
  • void  invalidate():删除session的方法,即设置session为无效
  • void serMaxInactiveInterval(int interval):设置当前会话的最大有效时间,单位是秒

例子2:

TestServlet1

package com.lovebaobao.cookieconfig;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet(name = "TestServlet1",
    urlPatterns = "/servlet/TestServlet1")
public class TestServlet1 extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");

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

        //request.getRequestDispatcher("/servlet/TestServlet2").forward(request,response);
        response.sendRedirect("/servlet/TestServlet2");
    }
}

TestServlet2

package com.lovebaobao.cookieconfig;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;

@WebServlet(name = "TestServlet2",
    urlPatterns = "/servlet/TestServlet2")
public class TestServlet2 extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");

        HttpSession session=request.getSession();
        System.out.println("SessionObject:"+session);
        String sessionId=session.getId();
        System.out.println("SessionId:"+sessionId);

        Cookie cookie=new Cookie("JSESSIONID",sessionId);   //把系统的sessionid覆盖掉
        cookie.setPath("/");
        //session.setMaxInactiveInterval(30*60);
        cookie.setMaxAge(30*60);//因为Session的默认生命周期是30分钟
        response.addCookie(cookie);//回写到客户端浏览器

    }
}

运行结果:
控制台

SessionObject:org.apache.catalina.session.StandardSessionFacade@2e3e251a
SessionId:71DF387678DFE2615BE749FD35D84A48
SessionObject:org.apache.catalina.session.StandardSessionFacade@2e3e251a
SessionId:71DF387678DFE2615BE749FD35D84A48

Cookie信息

Header信息

总结:

  1. 通过控制台输出知道,TestServlet1和TestServlet2获取的sessionid是同一个;因为getSession()的执行过程是先查看是否有Session,如果有就不创建了,如果没有再创建
  2. 设置session有效期有3种方法:
    (1)在web.xml文件中配置(单位是分钟)
    <session-config>
        <session-out>30</session-out>
    </session-config>
    (2)通过session.setMaxInactiveInterval(30*60);
    (3)通过cookie.setMaxAge(30*60);

备注:Session的实现原理

其实Session的实现原理是基于Cookie的技术实现,每次创建session都会对应生成一个session id,然后将这个session id信息写入到cookie中,但是这个cookie的有效时间是这个浏览器的进程,即cookie是在服务器的内存中,没有写到客户端磁盘。我们会在浏览器中使用同一个session,当我们关闭浏览器的时候,session id也就没有了,当我们打开浏览器的时候,服务器端又会重新创建一个session,此时方法二setMaxInactiveInterval(30*60)就无效了。所以为了解决这个问题我们需要修改这个cookie的有效期,并将这个cookie回写到客户端浏览器中,即方法三cookie.setMaxAge(30*60)

但是现在还有一个问题,如果浏览器禁止Cookie,那我们写给客户端的Cookie,客户端是不接受的,所以这样也会丢失数据,于是一项新技术产生了——URL重写:其实这个技术很简单,就是将网站中的网址后面携带一个session id,这样就可以保证当禁止Cookie的时候,仍然可以得到session id,然后找到相应的session

例子3:

IndexServlet

package com.lovebaobao.cookieconfig;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "IndexServlet",
    urlPatterns = "/servlet/IndexServlet")
public class IndexServlet extends HttpServlet {

    public final static long serialVersionUID=1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");

        request.getSession();

        //URL重写
        String buyUrl=response.encodeURL("/servlet/BuyServlet");
        String payUrl=response.encodeURL("/servlet/PayServlet");

//        String buyUrl="/servlet/BuyServlet";
//        String payUrl="/servlet/PayServlet";
        response.getWriter().print("<a href='"+buyUrl+"'>Buy</a><br/>");
        response.getWriter().print("<a href='"+payUrl+"'>Pay</a><br/>");
    }
}

BuyServlet

package com.lovebaobao.cookieconfig;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;

@WebServlet(name = "BuyServlet",
    urlPatterns = "/servlet/BuyServlet")
public class BuyServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");

        HttpSession session=request.getSession();
        session.setAttribute("store","volleyball");
        Cookie cookie=new Cookie("JSESSIONID",session.getId());
        cookie.setMaxAge(30*60);
        cookie.setPath("/");
        response.addCookie(cookie);
    }
}

PayServlet

package com.lovebaobao.cookieconfig;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Enumeration;

@WebServlet(name = "PayServlet",
    urlPatterns = "/servlet/PayServlet")
public class PayServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");

        HttpSession session=request.getSession();
        Enumeration<String> cart=session.getAttributeNames();
        response.getWriter().print("你买的东西有:<br/>");
        while(cart.hasMoreElements()){
            String name= cart.nextElement();
            response.getWriter().print(session.getAttribute(name)+"<br/>");
        }
    }
}

注意:先禁用浏览器Cookie

运行结果:

->Buy->退回->Pay

但是URL重写也存在弊端,对于每个url都必须重写,而且当我们关闭浏览器后再去访问的时候,会再重新创建一个session,而这个session id会重写到url中,所以不是之前的session了,至此,也暂时没有其他办法挽救了。

注意:如果同时保留Cookie和URL重写这两种技术,我们会发现当前用户携带Cookie过来的时候,url链接之后是没有jsessionid字段的,所以Cookie中的JSESSIONID优先级高

实例:防止表单重新提交

方法一:前端使用JS技术

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%
    String contextpath = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+contextpath+"/"; //获取当前项目路径(http://localhost:8085/studytest/)
%>

<!--
request.getScheme():返回当前页面使用的协议(http)
request.getServerName():返回当前页面所在服务器的名字(localhost)
request.getServerPort():返回当前页面所在服务器使用的端口(8085)
request.getContextPath():返回当前页面所在应用的名字(studytest)
-->

<html>
<head>
    <title>index.jsp</title>

    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="This is my page">

    <!-- 用户提交一次后,iscommitted变量变为True -->
    <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="<%=basePath%>servlet/FormSubmitServlet" method="post" onsubmit="return dosubmits()">
        <input type="hidden" name="token" value="${token}">  <!-- 使用了隐藏标签hidden,以及el表达式取出session域中的token字段值 -->
        用户名:<input type="text" name="username" title="username"><br/>
        <input id="submit" type="submit" value="提交">
    </form>
</body>
</html>

很明显上述方法是有问题的,因为当此页面重新加载刷新后,就又可以提交了;所以前端技术不能彻底防止表单的重复提交,我们还需加入服务器端的处理

方法二:

IndexServlet2

package com.lovebaobao.cookieconfig;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.Random;

@WebServlet(name = "IndexServlet2",
    urlPatterns = "/servlet/IndexServlet2")
public class IndexServlet2 extends HttpServlet {

    public final static long serialVersionUID=1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setDateHeader("expires",0);    //无缓存
        try{
            test(request,response);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }

    //产生表单
    public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
        //产生表单号
        String token=TokenProcessor.generateToken();
        request.getSession().setAttribute("token",token);
        request.getRequestDispatcher("/index.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()throws Exception{
        String token=System.currentTimeMillis()+new Random().nextInt()+"";
        try{
            /*
            static MessageDigest getInstance​(String algorithm)
            返回实现指定摘要算法的MessageDigest对象
             */
            MessageDigest md=MessageDigest.getInstance("md5");
            byte[] md5=md.digest(token.getBytes()); //通过执行填充等操作来完成哈希计算
            BASE64Encoder encoder=new BASE64Encoder();
            return encoder.encode(md5);
        }catch (Exception e){
            return null;
        }
    }
}

FormSubmitServlet

package com.lovebaobao.cookieconfig;

import sun.font.TrueTypeFont;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "FormSubmitServlet",
    urlPatterns = "/servlet/FormSubmitServlet")
public class FormSubmitServlet extends HttpServlet {

    public final static long serialVersion=1L;

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

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }

    //验证表单提交是否有效,返回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;

    }

}

运行结果:


(第一次提交)控制台
向数据库中注册用户
->退回(第二次提交)控制台
请不要重复提交表单

备注:实现原理

服务器端首先产生一个随机数,将这个随机数写入到session域中保存,然后利用隐藏标签标记一个唯一的表单(利用EL表达式获取session中的随机数存入value属性)。如果隐藏标签中的value值与之前写入到session域中的那个随机数都存在且相同,说明是第一次提交,之后就将那个随机数从session域中删除;当用户再一次点击提交表单的时候,进行相同的判断,此时session域中已没有对应的随机数,所以提交失败。

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

猜你喜欢

转载自blog.csdn.net/lovedbaobao/article/details/81479389