• Java Web学习总结(6)Cookie/Session


    一、会话的概念

    会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话

    二、会话过程中要解决的一些问题

    每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,程序要想办法为每个用户保存这些数据。

    三、保存会话数据的两种技术

    1、Cookie

      Cookie意为"甜饼",是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。

    由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。

    Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

    2、Session

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

    四,Cookie类的主要方法

    int getMaxAge()

    返回Cookie过期之前的最大时间,以秒计算。

    void setMaxAge(int expiry)

    以秒计算,设置Cookie过期时间。

    String getName()

    返回Cookie的名字。名字和值是我们始终关心的两个部分,笔者会在后面详细介绍 getName/setName。

    void setValue(String newValue)

    Cookie创建后设置一个新的值。

      

    String getValue()

    返回Cookie的值。笔者也将在后面详细介绍getValue/setValue。

    void setDomain(String pattern)

    设置cookie中Cookie适用的域名

      

    String getDomain()

    返回Cookie中Cookie适用的域名. 使用getDomain() 方法可以指示浏览器把Cookie返回给同 一域内的其他服务器,而通常Cookie只返回给与发送它的服务器名字完全相同的服务器。注意域名必须以点开始(例如.yesky.com)

    void setPath(String uri)

    指定Cookie适用的路径。

    String getPath()

    返回Cookie适用的路径。如果不指定路径,Cookie将返回给当前页面所在目录及其子目录下 的所有页面。

    void setSecure(boolean flag)

    指出浏览器使用的安全协议,例如HTTPS或SSL。

    boolean getSecure()

    如果浏览器通过安全协议发送cookies将返回true值,如果浏览器使用标准协议则返回false值。

    void setVersion(int v)

    设置Cookie所遵从的协议版本。

    int getVersion()

    返回Cookie所遵从的协议版本。

    void setComment(String purpose)

    设置cookie中注释。

    String getComment()

    返回Cookie中注释,如果没有注释的话将返回空值。

    Cookie(String name, String value)

    实例化Cookie对象,传入cooke名称和cookie的值。

    response接口也中定义了一个addCookie方法,它用于在其响应头中增加一个相应的Set-Cookie头字段。 同样,request接口中也定义了一个getCookies方法,它用于获取客户端提交的Cookie。

    五,Cookie使用

    1,使用cookie记录用户上一次访问的时间

    public class CookieDemo extends HttpServlet{
     
        private static final long serialVersionUID = 5757885987685925915L;
        
        @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 {
            //设置服务器以UTF-8编码输出
            resp.setCharacterEncoding("UTF-8");
            //设置浏览器以UTF-8编码进行接收
            resp.setContentType("text/html;charset=UTF-8");
            PrintWriter out = resp.getWriter();
            
            //获取Cookie数组
            Cookie[] cookie = req.getCookies();
            if(cookie == null){
                out.write("这是你的第一次访问!");
            } else {
                for (Cookie ck : cookie) {
                    if(ck.getName().equals("cookieName")){
                        //获取Cookie里面保存的数据
                        Long time = Long.parseLong(ck.getValue());
                        Date date = new Date(time);
                        out.write("上次访问时间:" + date.toLocaleString());
                    }
                }
            }
            
            //创建一个cookie,cookie的名字是cookieName
            Cookie cookies = new Cookie("cookieName", System.currentTimeMillis()+"");
            //设置Cookie的有效期为1天,这样即使关闭了浏览器,下次再访问时,也依然可以通过cookie获取用户上一次访问的时间。
            cookies.setMaxAge(24*60*60);
            //将cookie对象添加到response对象中
            resp.addCookie(cookies);
        }
    }

    第一次访问时,如下所示:

    再次访问:

    2,删除Cookie

    public class CookieDemo 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 {
              //创建一个名字为lastAccessTime的cookie
            Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");
            //将cookie的有效期设置为0,命令浏览器删除该cookie
            cookie.setMaxAge(0);
            req.addCookie(cookie);
        }
    }

    3,cookie中存/取中文

    public class CookieDemo2 extends HttpServlet{
     
        private static final long serialVersionUID = 5757885987685925915L;
        
        @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 {
            //设置服务器以UTF-8编码输出
            resp.setCharacterEncoding("UTF-8");
            //设置浏览器以UTF-8编码进行接收
            resp.setContentType("text/html;charset=UTF-8");
            PrintWriter out = resp.getWriter();
            
            //创建一个cookie,cookie的名字是cookieName
            //存储中文时,使用URLEncoder类里面的encode(String s, String enc)方法进行中文转码
            Cookie cookies = new Cookie("cookieName", URLEncoder.encode("哎哟!不错哟", "UTF-8"));
            //将cookie对象添加到response对象中
            resp.addCookie(cookies);
            
            //获取Cookie数组
            Cookie[] cookie = req.getCookies();
            if(cookie != null){
                for (Cookie ck : cookie) {
                    if(ck.getName().equals("cookieName")){
                        //获取Cookie里面保存的数据
                        String text = ck.getValue();
                        //使用URLDecoder类里面的decode(String s, String enc)进行解码
                        out.write("存储的中文数据:" + URLDecoder.decode(text, "UTF-8"));
                    }
                }
            }
        }
    }

    结果如下:

    Cookie注意细节

    1,一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。

    2,一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。

    3,浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。

    4,如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。

    六,Session简单介绍

    在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。

    Session和Cookie的主要区别

    1,Cookie是把用户的数据写给用户的浏览器。

    2,Session技术把用户的数据写到用户独占的session中。

    3,Session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。

    七,Session基础知识

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

    当用户打开浏览器,访问某个网站操作session时,服务器就会在服务器的内存为该浏览器分配一个session对象,该session对象被这个浏览器独占。

    这个session对象也可以看做是一个容器,session默认存在时间为30min,你可以修改。

    1,Session可以用来做什么

    1,网上商城中的购物车

    2,保存登录用户的信息

    3,将某些数据放入到Session中,供同一用户的各个页面使用

    4,防止用户非法登录到某个页面。

    2,Session基本使用

    request.getSession()

    返回这个request绑定的session对象,如果没有,则创建一个

    request.getSession(boolean create)

    返回这个request绑定的session对象,如果没有,则根据create的值决定是否创建一个

    session.setAttribute(String name,Object val)

    向session中添加属性

    session.getAttribute(String name)

    从session中得到某个属性

    session.removeAttribute(String name)

    从session中删除某个属性

    session.setMaxInactiveInterval()

    设置Session的生命周期(单位秒),Session的生命周期默认是30min

    session.invalidate()

    清除所有session

    session.removeAttribute(String name)

    删除指定名称的session

    Servlet1:

    public class Servlet1 extends HttpServlet {
     
        private static final long serialVersionUID = -8236507185410764108L;
        
        @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 {
            //获取session
            HttpSession session = req.getSession();
            //将数据存储到session中
            session.setAttribute("name", "Zender");
        }
    }

    Servlet2:

    public class Servlet2 extends HttpServlet {
     
        private static final long serialVersionUID = -8236507185410764108L;
        
        @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 {
            //获取session
            HttpSession session = req.getSession();
            //获取name
            String name = (String) session.getAttribute("name");
            PrintWriter out = resp.getWriter();
            out.print("name:" + name);
        }
    }

    同一浏览器访问Servlet1,再访问Servlet2,结果如下:

    不同浏览器访问Servlet1,再访问Servlet2,结果如下:

    可以看到这时候name是null,也就是没有从session对象中取出值,因为360浏览器并没有运行Servlet1来创建Session对象,上面的session对象是Chrome浏览器独占的。

    3,Session生命周期

    Session中的属性的默认生命周期是30min,这个默认时间可以通过修改web.xml文件来修改

    1,在Tomcat根目录confweb.xml文件中修改

    <session-config>

      <session-timeout>30</session-timeout>

    </session-config>

    2,如果只需要对某一个web应用设置,则只需要修改对应web应用的web.xml文件。在这个web.xml文件中添加如上的代码:

    <session-config>

      <session-timeout>10</session-timeout>

    </session-config>

    除了设置默认生命周期之外,最重要的是在程序中设置,调用setMaxInacttiveInterval(int interval),这里的interval是以秒为单位的,而且这个方法设置的是发呆时间,比如你设置的是60秒,那么在这60秒之内如果你没有操作过session,它就会自动删除,如果你操作过,不管是设置属性还是读取属性,它都会从头开始计时。

    session.setMaxInactiveInterval(60);

    八,Session实现原理

    服务器是如何实现一个session为一个用户浏览器服务的?

    1,浏览器A先访问Servlet1,这时候它创建了一个Session,ID号为110,然后Servlet1将这个ID号以Cookie的方式返回给浏览器A。

    2,浏览器A继续访问Servlet2,那么这个请求会带上Cookie值: JSESSIONID=110,然后服务器根据浏览器A传递过来的ID号找到内存中的这个Session。

    3,浏览器B来访问Servlet1了,它的请求并没有带上 JSESSIONID这个Cookie值,由于它也要使用Session,所以服务器会新创建一个Session,ID号为111。

    4,浏览器B继续访问Servlet2,那么这个请求会带上Cookie值: JSESSIONID=111,然后服务器根据浏览器B传递过来的ID号找到内存中的这个Session。

    例如:

    Servlet1:

    public class Servlet1 extends HttpServlet {
     
        private static final long serialVersionUID = -8236507185410764108L;
        
        @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 {
            //获取session
            HttpSession session = req.getSession();
            //将数据存储到session中
            session.setAttribute("name", "Zender");
            PrintWriter out = resp.getWriter();
            out.print("create Session OK");
        }
    }

    Servlet2:

    public class Servlet2 extends HttpServlet {
     
        private static final long serialVersionUID = -8236507185410764108L;
        
        @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 {
            //获取session
            HttpSession session = req.getSession();
            //获取name
            String name = (String) session.getAttribute("name");
            PrintWriter out = resp.getWriter();
            out.println("Session ID:" + session.getId());
            out.print("name:" + name);
        }
    }

    第一次访问Servlet1时,服务器会创建一个新的sesion,并且把session的Id以cookie的形式发送给客户端浏览器,如下图所示:

    可以看到,Request Headers中并没有Cookie的信息,而Response Headers中有这么一句话:

    Set-Cookie:

    JSESSIONID=05A94199DDC64311563740CC2C78D656; Path=/CookieAndSession/; HttpOnly

    说明这个时候服务器向客户端通过Cookie传递回了 JSESSIONID这个属性。

    然后访问Servlet2,如下图所示:

    可以看到Response Headers中没有出现Set-Cookie这个头,而Request Headers中带上了Cookie这个头:

    Cookie:

    JSESSIONID=05A94199DDC64311563740CC2C78D656

    而这个头中正包含 JSESSIONID,并且它的值也就是我们之前Set-Cookie中 JSESSIONID的值。

    这就证明了我们之前图解的Session的原理,也就是服务器能够为不同的浏览器区分不同的Session的机制。

    九,Session的简单应用

    1,用户登录时候验证验证码

    Index.jsp:

    <head>
    <base href="<%=basePath%>" />
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>表单提交</title>
    <link href="css/bootstrap.css" rel="stylesheet">
    <script src="js/jquery-3.2.1.js"></script>
    <script src="js/bootstrap.js"></script>
    </head>
    <body>
        <form class="form-horizontal" action="<%=request.getContextPath()%>/CodeServlet.html" role="form" method="post">
            <div class="form-group">
                <label for="firstname" class="col-sm-1 control-label">验证码</label>
                <div class="col-sm-3">
                    <input type="text" class="form-control" name="idCodeNum"
                        placeholder="请输入验证码">
                </div>
                <img src="idCode" onclick="this.src+=''" style="cursor:pointer;" width="115" height="30" title="看不清?换一个">
            </div>
            <div class="form-group">
                <div class="col-sm-offset-1 col-sm-3">
                    <button type="submit" class="btn btn-default">提交</button>
                    <button type="reset" class="btn btn-default">重置</button>
                </div>
            </div>
        </form>
    </body>
    </html>

    CodeServlet:

    public class CodeServlet extends HttpServlet {
        private static final long serialVersionUID = -8236507185410764108L;
        
        @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 {
            //获取session
            HttpSession session = req.getSession();
            //获取存储的验证码
            String idCodeText = (String) session.getAttribute("idCode");
            System.out.println("服务端验证码:" + idCodeText);
            
            String idCodeNum = req.getParameter("idCodeNum");
            System.out.println("输入的验证码:" + idCodeNum);
            //设置浏览器以UTF-8编码进行接收
            resp.setContentType("text/html;charset=UTF-8");
            PrintWriter out = resp.getWriter();
            if(idCodeText.equalsIgnoreCase(idCodeNum)){
                out.println("验证成功!");
            } else {
                out.println("验证码错误!");
            }
        }
    }

    Web.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        id="WebApp_ID" version="3.0">
        <display-name>CookieAndSession</display-name>
        <welcome-file-list>
            <welcome-file>index.html</welcome-file>
        </welcome-file-list>
        <servlet>
            <servlet-name>CodeServlet</servlet-name>
            <servlet-class>com.zender.session.CodeServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>CodeServlet</servlet-name>
            <url-pattern>/CodeServlet.html</url-pattern>
        </servlet-mapping>
        <!-- 生成验证码的Servlet -->
        <servlet>
            <servlet-name>ValidateCode</servlet-name>
            <servlet-class>org.jelly.image.ValidateCode</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>ValidateCode</servlet-name>
            <url-pattern>/idCode</url-pattern>
        </servlet-mapping>
    </web-app>

    这里使用了jelly-core-1.7.0.GA.jar来生成了验证码,具体使用方式请点击:jelly-core-1.7.0.GA.jar

    访问http://localhost:8081/CookieAndSession/index.jsp输入验证码,点击提交:

    输入正确验证码,页面响应结果:

    输入错误验证码,页面响应结果:

    输入正确验证码,后台结果:

    输入错误验证码,后台结果:

    2,实现简易购物车

    模拟一个数据库:

    public class DB {
        private static HashMap<String, Book> hm = null;
        private DB(){
        }
     
        static{
            hm = new LinkedHashMap<String, Book>();
     
            Book book1 = new Book(1, "Java基础");
            Book book2 = new Book(2, "Oracle数据库");
            Book book3 = new Book(3, "C语言");
            Book book4 = new Book(4, "Python核心教程");
            Book book5 = new Book(5, "Web技术");
     
            hm.put(book1.getId() + "" , book1);
            hm.put(book2.getId() + "" , book2);
            hm.put(book3.getId() + "" , book3);
            hm.put(book4.getId() + "" , book4);
            hm.put(book5.getId() + "" , book5);
        }
     
        /**
         * 得到数据库中所有的书
         * @return
         */
        public static HashMap<String, Book> getBooks(){
            return hm;
        }
     
        /**
         * 根据ID得到书
         * @param id
         * @return
         */
        public static Book getBookById(String id){
            if(hm.containsKey(id)){
                return hm.get(id);
            }
            return null;
        }
    }

    ShowBookServlet这个Servlet中读取数据库中所有的书的信息,显示在页面上:

    public class ShowBookServlet extends HttpServlet {
     
        private static final long serialVersionUID = -8236507185410764108L;
        
        @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.setContentType("text/html;charset=utf-8");
            PrintWriter out = resp.getWriter();
            out.println("<h2>Zender书店</h2>");
            out.println("<table>");
            HashMap<String, Book> books = DB.getBooks();
            Iterator it = books.keySet().iterator();
            while(it.hasNext()){
                Book book = books.get(it.next());
                out.println("<tr><td>"+book.getName()+"</td><td><a href='"+ req.getContextPath() +"/BuyBookServlet.html?id="+book.getId()+"'>点击购买</a></td></tr>");
            }
            out.println("</table>");
        }
    }

    BuyBookServlet这个Servlet用于购买图书:

    public class BuyBookServlet extends HttpServlet {
     
        private static final long serialVersionUID = -8236507185410764108L;
        
        @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.setContentType("text/html;charset=utf-8");
            String id = req.getParameter("id");
            //得到用户想买的书
            Book book = DB.getBookById(id);
            HttpSession session = req.getSession();
            //得到用户用于保存所有书的容器
            List<Book> list = (ArrayList<Book>) session.getAttribute("list");
            if(list == null){
                list = new ArrayList<Book>();
                session.setAttribute("list", list);
            }
            list.add(book);
            session.setAttribute("books", list);
            // 转发到ShowMyCartServlet查看购物车   
            req.getRequestDispatcher("/ShowMyCartServlet.html").forward(req, resp);
        }
    }

    ShowMyCartServlet这个Servlet用于查看购物车:

    //显示购买的图书
    public class ShowMyCartServlet extends HttpServlet {
     
        private static final long serialVersionUID = -8236507185410764108L;
        
        @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.setContentType("text/html;charset=UTF-8");
            PrintWriter out = resp.getWriter();
            HttpSession session = req.getSession();
            List<Book> list = (ArrayList<Book>) session.getAttribute("books");
            if(list==null || list.size()==0){
                out.write("对不起,您还没有购买任何商品!!");
                return;
            }
            
            //显示用户买过的商品
            out.write("您买过如下商品:<br/>");
            for(Book book : list){
                out.write(book.getName() + "<br/>");
            }
        }
    }

    运行结果:

    3,防止用户非法登录到某个页面

    比如我们的用户管理系统,必须要登录成功后才能跳转到主页面,而不能直接绕过登录页面直接到主页面,这个应用是一个非常常见的应用。

    当在验证用户的控制器LoginClServlet.java验证用户成功后,将当前的用户信息保存在Session对象中:

    // 把user对象保存在session
    
    HttpSession session = req.getSession();
    
    session.setAttribute("loginUser", user);

    然后在主页面Main.java最开始的地方,取出Session中的登录用户信息,如果信息为空,则为非法访问,直接跳转到登录页面,并提示相关信息:

    // 取出login-user这个session
    User loginUser = (User)req.getSession().getAttribute("loginUser");
    if(loginUser == null){
        // 说明用户没有登录,让他跳转到登录页面
        req.setAttribute("error", "请登录!");
        req.getRequestDispatcher("/LoginServlet").forward(req,res);
        return;
    }

    那么这里就存在一个问题,一个网站会有很多个需要防止非法访问的页面,如果都是用这种方法岂不是很麻烦?

    两种解决办法:

    第一种:将这段验证用户的代码封装成函数,每次调用

    第二种:使用过滤器

    4,利用Session防止表单重复提交

    具体的做法:

    在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。

    在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

    1,存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。

    2,当前用户的Session中不存在Token(令牌)。

    3,用户提交的表单数据中没有Token(令牌)。

    例如:

    创建FormTokenServlet,用于生成Token和跳转到token.jsp页面:

    public class FormTokenServlet extends HttpServlet {
     
        private static final long serialVersionUID = -8236507185410764108L;
     
        @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 {
            // 创建令牌
            String token = TokenProccessor.getInstance().makeToken();
            System.out.println("在FormServlet中生成的token:" + token);
            // 在服务器使用session保存token(令牌)
            req.getSession().setAttribute("token", token);
            // 跳转到token.jsp页面
            req.getRequestDispatcher("/token.jsp").forward(req, resp);
        }
    }

    在token.jsp中使用隐藏域来存储Token(令牌),提交Token(令牌)到服务器:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%
       String path = request.getContextPath();
       String basePath = request.getScheme() + "://"
               + request.getServerName() + ":" + request.getServerPort()
               + path + "/";
    %>
    <html>
    <head>
    <base href="<%=basePath%>" />
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>表单提交</title>
    <link href="css/bootstrap.css" rel="stylesheet">
    <script src="js/jquery-3.2.1.js"></script>
    <script src="js/bootstrap.js"></script>
    </head>
    <body>
        <form class="form-horizontal" action="<%=request.getContextPath()%>/TokenServlet.html" role="form" method="post">
            <input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
            <div class="form-group">
                <label for="firstname" class="col-sm-1 control-label">用戶名</label>
                <div class="col-sm-3">
                    <input type="text" class="form-control" name="idCodeNum"
                        placeholder="请输入用戶名">
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-1 col-sm-3">
                    <button type="submit" class="btn btn-default">提交</button>
                    <button type="reset" class="btn btn-default">重置</button>
                </div>
            </div>
        </form>
    </body>
    </html>

    TokenServlet处理表单提交:

    public class TokenServlet extends HttpServlet {
     
        private static final long serialVersionUID = -8236507185410764108L;
     
        @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 b = false;
            String client_token = req.getParameter("token");
            // 如果用户提交的表单数据中没有token,则用户是重复提交了表单
            if (client_token == null) {
                b = true;
            }
            // 取出存储在Session中的token
            String serverToken = (String) req.getSession().getAttribute("token");
            // 如果当前用户的Session中不存在Token,则用户是重复提交了表单
            if (serverToken == null) {
                b = true;
            }
     
            // 存储在Session中的Token与表单提交的Token不同,则用户是重复提交了表单
            if (!client_token.equals(serverToken)) {
                b = true;
            }
     
            if (b == true) {
                System.out.println("请不要重复提交!");
                return;
            }
            // 移除session中的token
            req.getSession().removeAttribute("token");
            System.out.println("正在处理用户提交请求!!");
        }
    }

    运行结果如下:

    十,用户禁用Cookie后的Session处理

    这里存在一种情况,假如用户浏览器禁用了Cookie怎么办?比如我把Chrome的Cookie禁用,如下:

    解决方法:URL重写

    Servlet中的response提供了对URL重写的方法:

    response.encodeRedirectURL(String url)

    用于对sendRedirect方法后的url地址进行重写

    response.encodeURL(String url)

    用于对表单action和超链接的url地址进行重写

    那么URL重写是什么意思呢?其实就是人为地把JSESSIONID附在了url的后面,比如我们修改之前写的简易购物车,ShowBook中所有的点击购买超链接都要重写。

    之前我们是这么写的:

    out.println("<tr><td>"+book.getName()+"</td><td><a href='"+ req.getContextPath() +"/BuyBookServlet.html?id="+book.getId()+"'>点击购买</a></td></tr>");

    现在进行URL重写:

    req.getSession();
    String url = "/MyCart/BuyBookCl?id="+book.getId();
    url = resp.encodeURL(url);
    out.println("<tr><td>"+book.getName()+"</td><td><a href='"+url+"'>点击购买</a></td></tr>");

    需要注意的是,重写之前一定要调用或者确保使用过request.getSession()这个方法。

    重写之前,访问ShowBookServlet的源代码是这样的:

    重写之后呢:

    可以看到URL重写之后,jsessionid这个参数自动附在了url后面,由此,得以确保我们的Session在Cookie被禁用的情况下继续正常使用。这时候我们查看购物车的地址栏如下,可以明显看到jsessionid这个参数:

  • 相关阅读:
    WebStorm破解方法
    jQuery学习笔记2
    jquery.js 3.0报错, Uncaught TypeError: url.indexOf is not a function
    animate.css –齐全的CSS3动画库--- 学习笔记
    MySQL数据库---表的操作
    MySQL数据库---库的操作
    MySQL数据库---前言
    CppUnit使用和源码解析
    代码覆盖率检查
    代码Verify简介
  • 原文地址:https://www.cnblogs.com/Zender/p/7657516.html
Copyright © 2020-2023  润新知