1.会话跟踪概述

1.1.什么是会话

客户端访问服务端发生的一系列请求和响应过程称之为会话。 通俗的说:从打开浏览器向服务器端发送请求开始,到客户端关闭浏览器结束访问为止,就是一个会话。

1.2.会话跟踪技术

HTTP 是一种“无状态”协议。也就是说:当一个请求响应结束后,不会留下任何痕迹。或者说:下一次请求响应不会获得上一次请求响应的任何信息。
但是,从开发角度考虑,我们希望上一次请求所传递的数据能够维持状态到下一次请求,并且辨认出是否相同的客户端所发送出来的。也就是说:服务器端业务是需要有状态的。比如:登陆一次后,在一个会话范围内,就不用再重复登陆了。
那么会话跟踪技术,就是一种在客户端与服务器间保持HTTP状态的解决方案,管理浏览器客户端和服务器端之间会话过程中产生的会话数据。
会话跟踪技术解决方案有:

  1. Cookie
  2. Session
  3. URL重写
  4. 隐藏表单域
  5. … …

    1.3.Cookie

    Cookie由服务器生成,然后发送给客户端浏览器,浏览器会将 Cookie保存到客户端内存中或者保存到客户端某个目录下的文本文件内。下次请求同一网站时就发送该 Cookie给 服务器(前提是浏览器设置为启用 Cookie)。
    Cookie较为典型的应用就是:保存已成功登录过的用户的用户名信息,以便在 下一次登录此网站时,不需输入用户名,简化登录手续,完成自动登录等功能。

    注意: 如果是通过ajax 访问,那么sevlet会使用out.print()向客户端响应数据,并且在Cookie中写入sessionid,返回给客户端。但是这样一来,这样就会将cookie中原有的值覆盖掉。也就是说:通过ajax的请求方式,服务器端将无法为客户端设置cookie。

所以,下面使用表单提交来演示Cookie。

1.3.1.Cookie实例

  1. 前端index.html文件

    1. <h3>登陆</h3>
    2. <form action="http://localhost:8080/demo/login" method="post">
    3. 用户名:<input type="text" name="userName"><br>
    4. 密码:<input type="password" name="password"><br>
    5. <input type="submit">
    6. </form>
    7. <a href="http://localhost:8080/demo/login">自动登陆</a>
  2. 服务器端LoginServlet.java文件

    package servlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.ServletException;
    import java.io.IOException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    @WebServlet("/login")
    public class LoginServlet extends HttpServlet {
     @Override
     protected void doGet(HttpServletRequest request, HttpServletResponse response) 
                          throws ServletException, IOException {
         response.setHeader("Access-Control-Allow-Origin", "*"); 
         boolean isLogin = false;
         //获取从客户端发送过来的Cookie(获取的是一个Cookie数组)
         Cookie[] cookieArr = request.getCookies();
         if(cookieArr!=null) {
             //遍历每一个cookie的名称(使用getName()方法)
             for(Cookie cookie : cookieArr) {
                 if(cookie.getName().equals("user")) {
                     System.out.println("自动登陆成功");
                     isLogin = true;
                 }
             }
         }
         if(!isLogin) {
             String userName = request.getParameter("userName");
             String password = request.getParameter("password");
             if(userName.equals("test")&&password.equals("111")) {
                 System.out.println("登陆成功");
                 //创建用户信息Cookie
                 Cookie userCookie = new Cookie("user","ok");
                 //设置Cookie过期时间为一周
                 userCookie.setMaxAge(24*60*60); 
                 //向客户端发生Cookie
                 response.addCookie(userCookie);
             }else {
                 System.out.println("登陆失败");
             }
         }
     }
     @Override
     protected void doPost(HttpServletRequest request, HttpServletResponse response) 
                           throws ServletException, IOException {
         doGet(request,response);
     }
    }
    
    1. 第一次登陆成功后,服务器端向客户端发送一个用户信息的Cookie,保存在客户端。
    2. 第二次就可以直接使用 “自动登陆” 了。
    3. 在客户端浏览器的 “开发者工具” 中,Application -> Storage -> Cookies 中,可以看到在http://localhost:8080 域下,已经存储了 user 名称的Cookie。

1.3.2.Cookie的缺点

cookie的缺点 :

  1. cookie安全性不够高;
  2. cookie可能被禁用;
  3. cookie存储空间很小(只有4–10KB左右)

    1.4.Session

    Cookie虽然在持久保存(可以设置过期时间)客户端数据提供了方便,但在一个会话范围内维持状态却并无优势。所以,在基于Cookie的基础上,出现了Session技术。
    Session较为典型的应用就是:当第一次登陆成功后,一个会话开始。在这个会话范围内,可以依靠Session维护登陆状态,直到此会话结束。

    1.4.1.Session原理

    使用Session维护一个会话的登陆状态:

  4. 当第一次请求时,在服务器端创建一个Session对象,并且为此对象生成一个sessionId。

  5. 同时,使用Cookie将此sessionId返回给客户端,并存储在客户端的Cookie中。
  6. 当客户端发起下一次请求时,必须携带此sessionId发送给服务器端。
  7. 服务器端根据接收的sessionId,就能找回Session对象,从而获取了上一次请求的信息。

    1.4.2.Session实例

    注意: 使用ajax 访问时,会将sessionid写入到Cookie中,返回给客户端。但是需要以下设置:

    1. 服务器端设置:response.setHeader(“Access-Control-Allow-Credentials”, “true”); 用于设置跨域时允许发送Cookie。
    2. 服务器端设置:response.setHeader(“Access-Control-Allow-Origin”, “http://127.0.0.1:8848“); 如果允许发送Cookie,那么允许跨域的url就不能为 “*”。
    3. 如果是使用Axios框架,那么需要设置:axios.defaults.withCredentials = true; 用于设置允许Axios发送Cookie。

所以,下面使用Vue-cli+Axios来演示Session。

  1. 前端Login.vue文件

    <template>
     <div>
         <h3>登陆</h3>
         用户名:<input type="text" v-model="userName"><br>
         密码:<input type="password" v-model="password"><br>
         <button @click="login">登陆</button>
     </div>
    </template>
    <script>
     export default {
         name: 'Login',
         data() {
             return {
                 userName: '',
                 password: ''
             }
         },
         methods: {
             login() {
                 this.$axios.post('http://localhost:8080/hello/login', this.$qs.stringify({
                     userName: this.userName,
                     password: this.password
                 })).then((response) => {
                     if (response.data == 'ok') {
                         this.$router.push('/news');
                     } else {
                         alert('用户名或密码错误!');
                     }
                 }).catch((error) => {
                     console.log(error);
                 });
             }
         }
     }
    </script>
    
  2. 前端News.vue文件

    <template>
    <div class="about">
     <button @click="news">news</button>
    </div>
    </template>
    <script>
     export default {
         name: 'News',
         methods:{
             news(){
                 this.$axios.get('http://localhost:8080/hello/news')
                     .then((response)=> {
                         if (response.data == 'Not logged in') {
                             alert('未登录!');
                             this.$router.push('/login');
                         } else {
                             alert('news');
                         }
                     }).catch((error)=> {
                         console.log(error);
                     });
             }
         }
     }
    </script>
    
  3. 服务器端LoginServlet.java文件

    package servlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.ServletException;
    import java.io.IOException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpSession;
    import javax.servlet.http.Cookie;
    import java.io.PrintWriter;
    @WebServlet("/login")
    public class LoginServlet extends HttpServlet {
     @Override
     protected void doGet(HttpServletRequest request, HttpServletResponse response) 
                         throws ServletException, IOException {
         //开启Cookie后,不能再使用* (这里使用request.getHeader("Origin")获取前端域)
         response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
         //设置开启Cookie
         response.setHeader("Access-Control-Allow-Credentials", "true");
         PrintWriter out = response.getWriter();
         HttpSession session = request.getSession();
         System.out.println(session.getId());
         String userName = request.getParameter("userName");
         String password = request.getParameter("password");
         if(userName.equals("test")&&password.equals("111")) {
             session.setAttribute("user","userInfo");
             out.print("ok");
         }else {
             out.print("error");
         }
         out.close();
     }
     @Override
     protected void doPost(HttpServletRequest request, HttpServletResponse response) 
                             throws ServletException, IOException {
         doGet(request,response);
     }
    }
    
  4. 服务器端NewsServlet.java文件

    package servlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.ServletException;
    import java.io.IOException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpSession;
    import javax.servlet.http.Cookie;
    import java.io.PrintWriter;
    @WebServlet("/news")
    public class NewsServlet extends HttpServlet {
     @Override
     protected void doGet(HttpServletRequest request, HttpServletResponse response) 
                          throws ServletException, IOException {
         response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
         response.setHeader("Access-Control-Allow-Credentials", "true");
         PrintWriter out = response.getWriter();
         HttpSession session = request.getSession();
         System.out.println(session.getId());
         String user = (String)session.getAttribute("user");
         if(user!=null) {
             //做news业务处理
             out.print("news");
         }else {
             out.print("Not logged in");
         }
         out.close();
     }
     @Override
     protected void doPost(HttpServletRequest request, HttpServletResponse response) 
                            throws ServletException, IOException {
         doGet(request,response);
     }
    }
    

    上面实例中:

    1. 如果不经过登陆,而是在News.vue中,点击 “news” 按钮进行非法访问,那么服务器端就会返回 “Not logged in”。
    2. 必须要在Login.vue中进行登陆,那么在News.vue中就不用再次登陆了。
    3. 在服务器端,可以使用getId方法,获取sessionId。

1.4.3.Session的创建与销毁

  1. Session的创建: 当第一次访问Servlet时,Servlet会自动创建Session对象。可以使用request.getSession() 方法获取此Session对象。getSession()方法有两个重载:
    1. getSession() 没有参数:如果存在一个Session对象,就使用此对象;否则就创建新的Session对象。
    2. getSession(boolean) 有一个布尔类型参数:
      true:等同于 getSession()
      false:如果不存在一个Session对象,就返回null
  2. Session的销毁:Session对象的销毁分为三种情况:
    1. Session对象的失效: 如果浏览器始终不关闭,那么就是说:客户端始终能带着sessionID去请求,那么将能够获取以前的session对象。但是如果浏览器被关闭,那服务器端就不能找到以前的session对象。以前的session对象将永远的丢失。session对象就相当于失效了。此时,服务器端会创建新的session对象。
    2. Session对象的主动销毁: session.invalidate();
    3. Session对象的超时销毁: 服务器自动在一定间隔时间内,将不再被访问session自动销毁。 比如:在web.xml写入如下配置:
      <session-config>
      <!-- 单位是分钟 -->
      <session-timeout>30</session-timeout>
      </session-config>
      

      总结:从上面的说明中可以得知:

      1. 关闭客户端浏览器,并不能使服务器端session对象销毁,而是不能再访问。
      2. 正是因为如此,服务器端内存中会堆积大量无法访问的session对象,所以才规定了session超时销毁。
      3. 调用invalidate方法、和session超时销毁,才能够真正的销毁session对象。

2.会话范围总结

前面对于一个会话范围的解释是有局限性的,这里我们对于一个会话做一个总结:

  1. 前端会话范围:从打开浏览器向服务器端发送请求开始,到客户端关闭浏览器结束访问为止。
  2. 服务器端会话范围:
    1. 从打开浏览器向服务器端发送请求开始,到客户端关闭浏览器结束访问为止。
      (注意:服务器端session对象仍然存在,但因为失去了sessionId,所以无法访问了)
    2. 从打开浏览器向服务器端发送请求开始,到服务器端主动调用invalidate()方法为止。
      (注意:服务器端session对象被卸载)
    3. 从打开浏览器向服务器端发送请求开始,到服务器端session超时为止。
      (注意:服务器端session对象被卸载)

扩展内容: chrome 浏览器从 80+版本开始,默认屏蔽所有第三方cookie。而且,其它主流的浏览器也都在逐步跟进chrome的策略。因此,使用 Cookie 进行会话跟踪技术将会被逐渐淘汰。

课后作业

  1. 简述什么是会话跟踪技术?
  2. 常用的会话跟踪技术有几种?
  3. Cookie的缺点有哪些?
  4. 简述如何使用Session维护一个会话的过程?
  5. 简述getSession()方法的与getSession(boolean)方法的区别?
  6. 简述Session对象销毁的三种情况?
  7. 简述前端会话范围与服务器端会话范围的区别?
  8. 30中值的单位是什么?