Cookie&Session

**Cookie:**客户端会话跟踪技术

- 用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含**多次**请求和响应。

**Session:**服务端会话跟踪技术

- 服务器用来识别浏览器的过程,这个过程就是**会话跟踪**

**Cookie:**客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问。

1.1 Cookie的基本使用

对于Cookie的使用,我们更关注的应该是后台代码如何操作Cookie,对于Cookie的操作主要分两大类,分别是发送Cookie获取Cookie,对于上面这两块内容,分别该如何实现呢?

发送Cookie

  • 创建Cookie对象,并设置数据
Cookie cookie = new Cookie("key","value");
  • 发送Cookie到客户端:使用response对象
response.addCookie(cookie);

获取Cookie

  • 获取客户端携带的所有Cookie,使用request对象
Cookie[] cookies = request.getCookies();
  • 遍历数组,获取每一个Cookie对象:for
  • 使用Cookie对象方法获取数据
cookie.getName();
cookie.getValue();

1.2 代码实现

介绍完获取Cookie创建和获取后,做个案例实现

需求:在AServlet中存入数据,在BServlet中获取数据。

AServlet

@WebServlet("/AServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie cookie = new Cookie("name","zhangsan");
        //设置存活时间   ,1周 7天
        cookie.setMaxAge(60*60*24*7);
        response.addCookie(cookie);
    }

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

BServlet

@WebServlet("/BServlet")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            String name = cookie.getName();
            String value = cookie.getValue();
            System.out.println("cookie = " + name+":"+value);
        }
    }

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

总结:

在这节中,我们主要讲解了Cookie的基本使用,包含两部分内容

  • 发送Cookie:
    • 创建Cookie对象,并设置值:Cookie cookie = new Cookie("key","value");
    • 发送Cookie到客户端使用的是Reponse对象:response.addCookie(cookie);
  • 获取Cookie:
    • 使用Request对象获取Cookie数组:Cookie[] cookies = request.getCookies();
    • 遍历数组
    • 获取数组中每个Cookie对象的值:cookie.getName()和cookie.getValue()

1.3 Cookie的原理分析

对于Cookie的实现原理是基于HTTP协议的,其中设计到HTTP协议中的两个请求头信息:

  • 响应头:set-cookie

  • 请求头: cookie

  • 前面的案例中已经能够实现,AServlet给前端发送Cookie,BServlet从request中获取Cookie的功能

  • 对于AServlet响应数据的时候,Tomcat服务器都是基于HTTP协议来响应数据

  • 当Tomcat发现后端要返回的是一个Cookie对象之后,Tomcat就会在响应头中添加一行数据==Set-Cookie:username=zs==

  • 浏览器获取到响应结果后,从响应头中就可以获取到Set-Cookie对应值username=zs,并将数据存储在浏览器的内存中

  • 浏览器再次发送请求给BServlet的时候,浏览器会自动在请求头中添加==Cookie: username=zs==发送给服务端BServlet

  • Request对象会把请求头中cookie对应的值封装成一个个Cookie对象,最终形成一个数组

  • BServlet通过Request对象获取到Cookie[]后,就可以从中获取自己需要的数据

1.4 Cookie的使用细节

在这节我们主要讲解两个知识,第一个是Cookie的存活时间,第二个是Cookie如何存储中文。

1.4.1 Cookie的存活时间

默认情况下,Cookie存储在浏览器内存中,当浏览器关闭,内存释放,则Cookie被销毁。

如何将Cookie持久化存储?

Cookie其实已经为我们提供好了对应的API来完成这件事,这个API就是setMaxAge

  • 设置Cookie存活时间
setMaxAge(int seconds)

参数值为:

1.正数:将Cookie写入浏览器所在电脑的硬盘,持久化存储。到时间自动删除

2.负数:默认值,Cookie在当前浏览器内存中,当浏览器关闭,则Cookie被销毁

3.零:删除对应Cookie

				Cookie cookie = new Cookie("name","zhangsan");
        //设置存活时间   ,1周 7天
        cookie.setMaxAge(60*60*24*7);
        response.addCookie(cookie);

1.4.2 Cookie存储中文

tomcast8.0之前由于tomcat默认编码是ISO 8859-1 不支持cookie存储中文。tomcat8.0之后,支持存入中文

Tomcat7 Cookie不能直接存储中文,但是如果有这方面的需求,这个时候该如何解决呢?

这个时候,我们可以使用之前学过的一个知识点叫URL编码,所以如果需要存储中文,就需要进行转码,具体的实现思路为:

1.在AServlet中对中文进行URL编码,采用URLEncoder.encode(),将编码后的值存入Cookie中

2.在BServlet中获取Cookie中的值,获取的值为URL编码后的值

3.将获取的值在进行URL解码,采用URLDecoder.decode(),就可以获取到对应的中文值

存中文Cookie

				String name = "张三";
        //cookie存入中文,使用URLEncoder
        name = URLEncoder.encode(name, "UTF-8");
//        Cookie cookie = new Cookie("name","zhangsan");
        Cookie cookie = new Cookie("name",name);
        //设置存活时间   ,1周 7天
        cookie.setMaxAge(60*60*24*7);
        response.addCookie(cookie);

取中文Cookie

Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            String name = cookie.getName();
            String value = cookie.getValue();
//            System.out.println("cookie = " + name+":"+value);
            if ("name".equals(name)) {
                String decode = URLDecoder.decode(value, "UTF-8");
                System.out.println("decode = " + decode);
            }
        }

2. Session

**Session:**是服务端技术,将数据保存到服务端,Session是基于Cookie实现的。

Session的工作流程

  • 在服务端的AServlet获取一个Session对象,把数据存入其中
  • 在服务端的BServlet获取到相同的Session对象,从中取出数据
  • 就可以实现一次会话中多次请求之间的数据共享了
  • 现在最大的问题是如何保证AServlet和BServlet使用的是同一个Session对象

2.1 Session的基本使用

具体的使用步骤为:

  • 获取Session对象,使用的是request对象
HttpSession session = request.getSession();
  • Session对象提供的功能:

    • 存储数据到 session 域中

      void setAttribute(String name, Object o)
      
    • 根据 key,获取值

      Object getAttribute(String name)
      
    • 根据 key,删除该键值对

      void removeAttribute(String name)
      

2.2 代码实现

介绍完Session相关的API后,接下来通过一个案例来完成对Session的使用

需求:在一个Servlet中往Session中存入数据,在另一个Servlet中获取Session中存入的数据

  1. 创建名为SessionDemo1的Servlet类,获取Session对象、存储数据

    @WebServlet("/SessionDemo1")
    public class SessionDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            HttpSession session = request.getSession();
            session.setAttribute("username","zs");
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    
  2. 创建名为SessionDemo2的Servlet类,获取Session对象、获取数据

    @WebServlet("/SessionDemo2")
    public class SessionDemo2 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            HttpSession session = request.getSession();
            String username = (String) session.getAttribute("username");
            System.out.println("username = " + username);
            // 销毁
            session.invalidate();
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    
  • **注意:**Session中可以存储的是一个Object类型的数据,也就是说Session中可以存储任意数据类型。

​ 介绍完Session的基本使用之后,那么Session的底层到底是如何实现一次会话两次请求之间的数据共享呢?

2.3 Session的原理分析

Session是基于Cookie实现的

Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个。

那么它们是一个对象么?答案是肯定的,要验证这个结论也很简单,只需要在上面案例中的两个Servlet中分别打印下Session对象

那么最主要的问题就来了,Session是如何保证在一次会话中获取的Session对象是同一个呢?

  1. SessionDemo1在第一次获取session对象的时候,session对象会有一个唯一的标识,假如是id:10

  2. SessionDemo1在session中存入其他数据并处理完成所有业务后,需要通过Tomcat服务器响应结果给浏览器

  3. Tomcat服务器发现业务处理中使用了session对象,就会把session的唯一标识id:10当做一个cookie,添加Set-Cookie:JESSIONID=10到响应头中,并响应给浏览器

  4. 浏览器接收到响应结果后,会把响应头中的coookie数据存储到浏览器的内存中

  5. 浏览器在同一会话中访问SessionDemo2的时候,会把cookie中的数据按照cookie: JESSIONID=10的格式添加到请求头中并发送给服务器Tomcat

  6. SessionDemo2获取到请求后,从请求头中就读取cookie中的JSESSIONID值为10,然后就会到服务器内存中寻找id:10的session对象,如果找到了,就直接返回该对象,如果没有则新创建一个session对象

  7. 关闭打开浏览器后,因为浏览器的cookie已被销毁,所以就没有JESSIONID的数据,服务端获取到的session就是一个全新的session对象

至此,Session是基于Cookie来实现的这就话,我们就解释完了。

2.4 Session的使用细节

这节我们会主要讲解两个知识,第一个是Session的钝化和活化,第二个是Session的销毁,首先来学习什么是Session的钝化和活化?

2.4.1 Session钝化与活化

**疑问:**服务器重启后,Session中的数据是否还在?

  1. 服务器端AServlet和BServlet共用的session对象应该是存储在服务器的内存中
  2. 服务器重新启动后,内存中的数据应该是已经被释放,对象也应该都销毁了, Tomcat使用钝化****和活化,保证对象的唯一性。
  3. 服务器是正常关闭和启动,session中的数据是可以被保存下来的。

**钝化:**在服务器正常关闭后,Tomcat会自动将Session数据写入硬盘的文件中

	- 钝化的数据路径为:`项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser`

**活化:**再次启动服务器后,从文件中加载数据到Session中

  • 数据加载到Session中后,路径中的SESSIONS.ser文件会被删除掉

2.4.2 Session销毁

session的销毁会有两种方式:

默认情况下,无操作,30分钟自动销毁

  • 对于这个失效时间,是可以通过配置进行修改的

    • 在项目的web.xml中配置

      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
               version="4.0">
      <!--    配置session存活时间,100(单位分钟)-->
          <session-config>
              <session-timeout>100</session-timeout>
          </session-config>
      </web-app>
      
    • 如果没有配置,默认是30分钟,默认值是在Tomcat的web.xml配置文件中写死的

  • 调用Session对象的invalidate()进行销毁

    				HttpSession session = request.getSession();
            String username = (String) session.getAttribute("username");
            System.out.println("username = " + username);
            // 销毁
            session.invalidate();
    

3. Cookie和Session小结

  • Cookie 和 Session 都是来完成一次会话内多次请求间==数据共享==的。

所需两个对象放在一块,就需要思考:

Cookie和Session的区别是什么?

Cookie和Session的应用场景分别是什么?

  • 区别:
    • 存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端
    • 安全性:Cookie不安全,Session安全
    • 数据大小:Cookie最大3KB,Session无大小限制
    • 存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟
    • 服务器性能:Cookie不占服务器资源,Session占用服务器资源
  • 应用场景:
    • 购物车:使用Cookie来存储
    • 以登录用户的名称展示:使用Session来存储
    • 记住我功能:使用Cookie来存储
    • 验证码:使用session来存储
  • 结论
    • Cookie是用来保证用户在未登录情况下的身份识别
    • Session是用来保存用户登录后的数据

介绍完Cookie和Session以后,具体用哪个还是需要根据具体的业务进行具体分析。

4. 用户登录注册案例

4.1 需求分析

需求说明:

  1. 完成用户登录功能,如果用户勾选**“记住用户”** ,则下次访问登录页面自动填充用户名密码

  2. 完成注册功能,并实现验证码功能

4.1.1 用户登录功能

需求:用户登录成功后,跳转到登陆并在页面上展示当前登录的用户名称,选中记住密码,重新进入登陆页,自动填充用户名和密码

  1. 前端通过表单发送请求和数据给Web层的LoginServlet

  2. 在LoginServlet中接收请求和数据[用户名和密码]

  3. 在LoginServlet模拟数据库中存在的用户名和密码,如果输入正确,并判断是否记录密码,则使用Cookie响应给浏览器,存储用户名密码

技术难点:

如何在JSP页面直接获取Cookie中的值呢?

在页面可以使用EL表达式,${cookie.==key==.value}

key:指的是存储在cookie中的键名称

  1. 在login.jsp用户名的表单输入框使用value值给表单元素添加默认值,value可以使用${cookie.username.value}

  2. 在login.jsp密码的表单输入框使用value值给表单元素添加默认值,value可以使用${cookie.password.value}

代码实现

login.jsp

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

<head>
    <meta charset="UTF-8">
    <title>登陆</title>
    <link href="css/login.css" rel="stylesheet">
</head>

<body>
<div id="loginDiv" style="height: 350px">
    <form action="/09CookieSession/LoginServlet" method="post" id="form" >
        <h1 id="loginMsg">登陆</h1>
        <div id="errorMsg">${login_msg}</div>
        <p>用户名:<input id="username" name="username" type="text" value="${cookie.username.value}"></p>

        <p>密  码:<input id="password" name="password" type="password" value="${cookie.pd.value}"></p>
        <p>记住密码:<input id="remember" name="remember" value="1" type="checkbox"></p>
        <div id="subDiv">
            <input type="submit" class="button" value="登陆">
            <input type="reset" class="button" value="重置">&nbsp;&nbsp;&nbsp;
            <a href="/09CookieSession/register.jsp">没有账号?</a>
        </div>
    </form>
</div>

</body>
</html>

LoginServlet

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    //模拟从数据查询的数据
    String name = "zhangsan";
    String password = "123456";

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String pd = request.getParameter("password");
        String remember = request.getParameter("remember");
        if (name.equals(username) && password.equals(pd)) {
            response.setContentType("text/html;charset = utf-8");
            response.getWriter().write("<h1>" + username + "欢迎你,登陆成功</h1>");
            //选中记住密码
            if ("1".equals(remember)) {
                //想客户端发送用户名密码,保存
                Cookie cookie1 = new Cookie("username", username);
                // 设置Cookie的存活时间,一周
                cookie1.setMaxAge(60 * 60 * 24 * 7);
                Cookie cookie2 = new Cookie("pd", pd);
                cookie2.setMaxAge(60 * 60 * 24 * 7);
                response.addCookie(cookie1);
                response.addCookie(cookie2);
            }
        } else {
            request.setAttribute("login_msg", "用户名或密码错误");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }

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

4.2 用户注册功能

需求:用户注册有2个功能

  1. 获取jsp页面输入的用户名密码,在Servlet模拟用户注册
  2. 在jsp注册页面,可以动态获取验证码

技术难点:

register.jsp如何事实的获取验证码

  1. 前端发送请求给CheckCodeServlet
  2. CheckCodeServlet接收到请求后,生成验证码图片,将图片用Reponse对象的输出流写回到前端

思考:如何将图片写回到前端浏览器呢?

  1. Java中已经有工具类生成验证码图片,测试类中只是把图片生成到磁盘上
  2. 生成磁盘的过程中使用的是OutputStream流,如何把这个图片生成在页面呢?
  3. 前面在将Reponse对象的时候,它有一个方法可以获取其字节输出流,getOutputStream()
  4. 综上所述,我们可以把写往磁盘的流对象更好成Response的字节流,即可完成图片响应给前端

jsp请求服务器生成新的验证码,核心代码

<tr>
    <td>验证码</td>
        <td class="inputs">
        <input name="checkCode" type="text" id="checkCode">
        <img id="checkCodeImg" src="/brand-demo/checkCodeServlet">
        <a href="#" id="changeImg" >看不清?</a>
    </td>
</tr>

<script>
    document.getElementById("changeImg").onclick = function () {
       	//路径后面添加时间戳的目的是避免浏览器进行缓存静态资源
        document.getElementById("checkCodeImg").src = "/brand-demo/checkCodeServlet?"+new Date().getMilliseconds();
    }
</script>

代码实现

register.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎注册</title>
    <link href="css/register.css" rel="stylesheet">
</head>
<body>

<div class="form-div">
    <div class="reg-content">
        <h1>欢迎注册</h1>
        <span>已有帐号?</span> <a href="/09CookieSession/login.jsp">登录</a>
    </div>
    <form id="reg-form" action="/09CookieSession/RegisterServlet" method="post">

        <table>

            <tr>
                <td>用户名</td>
                <td class="inputs">
                    <input name="username" type="text" id="username">
                    <br>
                    <span id="username_err" class="err_msg" style="display: none">用户名不太受欢迎</span>
                </td>

            </tr>

            <tr>
                <td>密码</td>
                <td class="inputs">
                    <input name="password" type="password" id="password">
                    <br>
                    <span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
                </td>
            </tr>


            <tr>
                <td>验证码</td>
                <td class="inputs">
                    <input name="checkCode" type="text" id="checkCode">
<%--                    <img src="imgs/a.jpg">--%>
                    <img id="checkCodeImg" src="/09CookieSession/CheckCodeServlet">
                    <a href="#" id="changeImg">看不清?</a>
                </td>
            </tr>

        </table>

        <div class="buttons">
            <input value="注 册" type="submit" id="reg_btn">
        </div>
        <br class="clear">
    </form>

</div>
<script>
    document.getElementById("changeImg").onclick = function () {
        //路径后面添加时间戳的目的是避免浏览器进行缓存静态资源
        document.getElementById("checkCodeImg").src = "/09CookieSession/CheckCodeServlet?"+new Date().getMilliseconds();
    }
</script>
</body>
</html

RegisterServlet

@WebServlet("/RegisterServlet")
public class RegisterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //记得设置编码,不然转发的页面可能乱码
        response.setCharacterEncoding("utf-8");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String checkCode = request.getParameter("checkCode");
        //模拟用户注册,
        response.getWriter().write(username+"-"+password+"-"+checkCode);
        //比对用户输入的验证码和服务器生成的验证码是否一致
        HttpSession session = request.getSession();
        Object checkCodeGen = session.getAttribute("checkCodeGen");
        response.setContentType("text/html;charset = utf-8");
        if (checkCodeGen.equals(checkCode)) {
            request.setAttribute("register_msg","注册成功,请登录");
            request.getRequestDispatcher("/login.jsp").forward(request,response);
        }else {
            request.setAttribute("register_msg","验证码错误");
            request.getRequestDispatcher("/register.jsp").forward(request,response);
        }

    }

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

CheckCodeServlet:生成验证码的Servlet

/**
 * 使用验证码工具类生成图片,发送给jsp页面
 */
@WebServlet("/CheckCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 生成验证码
        ServletOutputStream os = response.getOutputStream();
        String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
        // 将生成的验证码,存入Session
        HttpSession session = request.getSession();
        session.setAttribute("checkCodeGen",checkCode);
    }

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