一文看懂cookie和session

给大家分享我收藏的几个不错的 github 项目,内容都还是不错的,如果觉得有帮助,可以顺便给个 star。

一、会话的概念

会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个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(intexpiry) 以秒计算,设置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);
    }
}

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

image

再次访问:

image

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"));
                }
            }
        }
    }
}

结果如下:

image

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,结果如下:

image

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

image

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

3、Session生命周期

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

1,在Tomcat根目录\conf\web.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为一个用户浏览器服务的?

image

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的形式发送给客户端浏览器,如下图所示:

image

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

Set-Cookie:

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

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

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

image

可以看到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://www.blogjava.net/fancydeepin/archive/2014/08/03/jelly_image.html)

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

image

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

image

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

image

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

image

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

image

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;
    }
}

BuyBookServlet这个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/>");
        }
    }
}

运行结果:

image

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("正在处理用户提交请求!!");
    }
}

运行结果如下:

image

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

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

image

解决方法: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的源代码是这样的:

image

重写之后呢:

image

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

image

最后,给大家分享我收藏的几个不错的 github 项目,内容都还是不错的,如果觉得有帮助,可以顺便给个 star。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容