Skip to content

6. 会话跟踪

  • 什么是会话? 会话可简单理解为:打开浏览器 -> 访问一些服务器内容 -> 关闭浏览器,整个过程称之为一个会话。
  • 会话过程中要解决的问题?
    • HTTP 是一种“无状态”协议,每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,服务器要想办法为每个用户保存这些数据:
    • 如用户登录场景,当不同的用户登录系统后,我们如何在主页面显示不同的用户名? 购物场景:打开浏览器 -> 浏览商品列表 -> 加入购物车(把商品信息保存下来) -> 关闭浏览器;
  • 会话跟踪技术是一种在客户端与服务器间保持HTTP状态的解决方案,管理浏览器客户端和服务器端之间会话过程中产生的会话数据。
    • 从开发角度考虑,是使上一次请求所传递的数据能够维持状态到下一次请求,并且辨认出是否相同的客户端所发送出来的。
  • 会话跟踪技术的解决方案主要分为以下几种:
    • Cookie技术
    • Session技术
    • URL重写技术
    • 隐藏表单域技术
  • Cookie技术是一种在客户端保持会话跟踪的解决方案,会话数据保存在客户端浏览器。
  • Cookie在用户第一次访问服务器时,由服务器通过响应头的方式发送给客户端浏览器;当用户再次向服务器发送请求时会附带上这些文本信息。
  • 在使用Cookie时,要保证客户端浏览器接受Cookie。

javax.servlet.http.Cookie类,用于存储会话数据,存储过程如下

  • 1、构造Cookie对象 Cookie(java.lang.String name, java.lang.String value);
  • 2、设置cookie
    • void setPath(java.lang.String uri):设置cookie的有效访问路径。
      • 默认cookie只能由创建它的应用获得,设置cookie.setPath(“/”),则可在同一应用服务器内的所有应用共享访问。
    • void setMaxAge(int expiry) : 设置cookie的有效时间
      • 该Cookie失效时间,单位秒。如果为正数,则该Cookie在expiry秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1
    • void setValue(java.lang.String newValue) :设置cookie的值
      • Cookie的值不能设置成非ASSIC字符,如果要使用中文可以通过URLEncoder将其编码,否则会出异常。 Cookie大小和数量的限制,不同的浏览器有所区别,一般每个域名最多可存储50个cookie,Cookie总的的大小限制为4KB。
  • 3、将Cookie对象响应给客户端浏览器,存储在客户端。 void response.addCookie(Cookie cookie) : 发送cookie

6.1.1 设置和获取cookie

java
@WebServlet(name = "CookieServlet",urlPatterns = "/getCookie")
public class CookieServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        Cookie cookie = new Cookie("token","login-2020年12月10日13:49:56");

        response.addCookie(cookie);
    }

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

6.1.2 获取cookie

java
package com.neuedu.session;

import javax.servlet.ServletException;
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;

/**
 * 项目:      java1-session
 * 类名:       ${NAME}
 * 创建时间:  2020/12/10  13:53
 * 描述 :     ${dc}
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 * http://127.0.0.1:8080/web/showCookie
 */
@WebServlet(name = "ShowCookieServlet",urlPatterns = "/showCookie")
public class ShowCookieServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        Cookie[] cookies = request.getCookies();

        StringBuffer cookieInfo = new StringBuffer();

        if(cookies != null){
            for (Cookie cookie : cookies) {
                cookieInfo.append(cookie.getName()+": "+cookie.getValue()+"<br/>");
            }
        }

        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(cookieInfo.toString());
        out.flush();
        out.close();
    }

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

6.2 Cookie存活时间

  • Cookie有一定的存活时间,不会在客户端一直保存,默认情况下,Cookie保存在浏览器内存中,在浏览器关闭时失效,这种Cookie也称为会话Cookie,若要使Cookie较长时间的保存在磁盘上,可以通过Cookie对象的setMaxAge()方法设置其存活时间。

  • Cookie对象可以通过setMaxAge()方法设置其存活时间,时间以秒为单位;

    • void setMaxAge(int expiry);
      • 时间若为正整数,表示其存活的秒数;
      • 时间若为负数,表示其为临时Cookie;
      • 时间若为0,表示通知浏览器删除相应的Cookie,删除cookie时,path必须一致,否则不会删除;
    • 例如: name.setMaxAge(72460*60);
      name.setMaxAge(0)删除Cookie

    演示代码

6.3 Cookie的局限性

Cookie的缺点主要集中在其安全性和隐私保护上,主要包括以下几种:

  • Cookie可能被禁用,当用户非常注重个人隐私保护时,很可能会禁用浏览器的Cookie功能;
  • Cookie可能被删除,因为每个Cookie都是硬盘上的一个文件,因此很有可能被用户删除;
  • Cookie的大小和个数受限,不同浏览器有所区别,Cookie保存的数据不能超过4095个字节,50个/每个域名;
  • Cookie安全性不够高,所有的Cookie都是以纯文本的形式记录于文件中,因此如果要保存用户名密码等信息时,最好事先经过加密处理。

6.4 Session

  • Session是指使用HttpSession对象实现会话跟踪的技术,是一种在服务器端保持会话跟踪的解决方案。
  • HttpSession对象是javax.servlet.http.HttpSession接口的实例,也称为会话对象。
  • HttpSession对象会在用户第一次访问服务器时由容器创建(注意只有访问JSP、Servlet等程序时才会创建,只访问HTML、IMAGE等静态资源并不会创建),当用户调用其失效方法(invalidate()方法)或超过其最大不活动时间时会失效。在此期间,用户与服务器之间的多次请求都属于同一个会话。
  • Session和Cookie的主要区别在于:
    • Cookie是把用户的数据写给用户的浏览器。
    • Session技术把用户的数据写到用户独占的session中。
  • 服务器在创建会话对象时,会为其分配一个唯一的会话标识——SessionId,以“JSESSIONID”的属性名保存在客户端Cookie中,在用户随后的请求中,服务器通过读取Cookie中的JSESSIONID属性值来识别不同的用户,从而实现对每个用户的会话跟踪。

通过下图理解Session的工作原理:

HttpServletRequest接口提供了获取HttpSession对象的方法:

方法描述
getSession()获取与客户端请求关联的当前的有效的Session,若没有Session关联则新建一个
getSession(boolean create)获取与客户端请求关联的当前的有效的Session,若没有Session关联,当参数为真时,Session被新建,为假时,返回空值

6.4.1 创建Session

java
@WebServlet(name = "CreateServlet",urlPatterns = "/createServlet")
public class CreateSessionServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        resp.getWriter().write("id:"+session.getId());
    }
}
java
@WebServlet(name = "SessionServlet",urlPatterns = "/session")
public class SessionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //如果以前没有关联的session会报错
        HttpSession session = request.getSession(false);

        System.out.println(session.getId());

        PrintWriter out = response.getWriter();
        out.write(session.getId());

    }

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

6.4.2 session的使用

HttpSession接口提供了存取会话域属性和管理会话生命周期的方法

方法描述
void setAttribute(String key,Object value)以key/value的形式将对象保存在HttpSession对象中
Object getAttribute(String key)通过key获取对象值
void removeAttribute(String key)从HttpSession对象中删除指定名称key所对应的对象
void invalidate()设置HttpSession对象失效
void setMaxInactiveInterval(int interval)设定HttpSession对象的非活动时间(以秒为单位),若超过这个时间,HttpSession对象将会失效
int getMaxInactiveInterval()获取HttpSession对象的有效非活动时间(以秒为单位)
String getId()获取HttpSession对象标识sessionid
long getCreationTime()获取HttpSession对象产生的时间,单位是毫秒
long getLastAccessedTime()获取用户最后通过这个HttpSession对象送出请求的时间

attribute的使用

java
@WebServlet(name = "LoginServlet",urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String username = request.getParameter("username");

        HttpSession session = request.getSession();
        session.setAttribute("username",username);

    }

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

获取attribute

java
@WebServlet(name = "ShowUserInfoServlet", urlPatterns = "/showUser")
public class ShowUserInfoServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = (String) request.getSession().getAttribute("username");
        PrintWriter out = response.getWriter();
        out.write("welcome:"+username);
        out.flush();
        out.close();
    }

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

删除attribute

java
@WebServlet(name = "RemoveSessionAttributeServlet",urlPatterns = "/removeAttribute")
public class RemoveSessionAttributeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        session.removeAttribute("username");
    }

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

session销毁

@WebServlet(name = "CreateServlet",urlPatterns = "/createServlet")
public class CreateSessionServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        resp.getWriter().write("id:"+session.getId());
    }
}

当调用完invalite ,在请求createServlet ,sessionid是新的,说明,重新创建session

@WebServlet(name = "InvalidateSessionServlet",urlPatterns = "/invalite")
public class InvalidateSessionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        session.invalidate();
    }

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

session有效期设置范围

项目中配置、Tomcat默认web.xml中有效期

<session-config>
    <session-timeout>1</session-timeout>
  </session-config>

Session失效时间 session具有一定声生命周期,如果session超过会话的最大不活动时间,会话自动失效,会话的最大不活动时间指会话超过此时间段不进行任何操作; 设置Session的失效时间

方法1:在工程的web.xml中配置session的生命周期,单位为分钟; <session-config> <session-timeout>15</session-timeout> </session-config>

方法2:在程序硬编码设置 session.setMaxInactiveInterval(30 * 60);//设置单位为秒,设置为-1永不过期;

方法3:在Tomcat安装目录下conf/web.xml中配置(Web容器级别) <session-config> <session-timeout>15</session-timeout> </session-config>

手动销毁Session 可以通过调用invalidate()方法立即清除会话对象及其所有会话域属性,同时响应客户端浏览器清除Cookie中的JSESSIONID,在实际应用中,此方法多用来实现系统的“安全退出”功能。

session对象 setMaxInactiveInterval > 项目(上下文 )> 中间件(Tomcat 30)

java
@WebServlet(name = "SessionInactiveIntervalServlet", urlPatterns = "/inactive")
public class SessionInactiveIntervalServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        HttpSession session  =  request.getSession();
        session.setMaxInactiveInterval(10);  //具体某一个会话的 session对象有效

        PrintWriter out = response.getWriter();
        out.write(session.getId());

        out.flush();
        out.close();
    }

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

id、CreationTime、 lastAccessedTime

java
@WebServlet(name = "CreateServlet",urlPatterns = "/createServlet")
public class CreateSessionServlet extends HttpServlet {

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        resp.setContentType("text/html;charset=utf-8");

        PrintWriter out = resp.getWriter();
        out.println("id:"+session.getId()+"<br/>");
        out.println("lastAccessedTime:"+sdf.format(new Date(session.getLastAccessedTime()))+"<br/>");
        out.println("creationTime:"+sdf.format(new Date(session.getCreationTime()))+"<br/>");

        out.flush();
        out.lose();
    }
}

6.5 使用会话技术实现用户登录

6.5.1 LoginServlet

java
package com.neuedu.servlet;

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.io.PrintWriter;

/**
 * 项目:      usermanager
 * 类名:       ${NAME}
 * 创建时间:  2020/12/9  16:07
 * 描述 :     ${dc}
 * 作者 :     张金山
 * QQ :     314649444
 * Site:      https://jshand.gitee.io
 * <p>
 * http://127.0.0.1:8080/mis/login?type=page
 */
@WebServlet(name = "LoginServlet", urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String type = request.getParameter("type");
        //1 展示登录画面 type = page
        response.setContentType("text/html;charset=utf-8");

        if ("page".equals(type)) {
            StringBuffer html = new StringBuffer();
            html.append(" <form method='post' action='login'>									");
            html.append(" 	<input type='hidden' name='type' value='validate' />  ");
            html.append(" 	<table border='1' cellpadding='0' cellspacing='0' with='50'>    ");
            html.append(" 	<tr>                                                            ");
            html.append(" 		<td>用户名</td>                                             ");
            html.append(" 		<td><input type='text' name='username'></td>                        ");
            html.append(" 	</tr>                                                           ");
            html.append(" 	<tr>                                                            ");
            html.append(" 		<td>用户名</td>                                             ");
            html.append(" 		<td><input type='password' name='password'></td>                    ");
            html.append(" 	</tr>                                                           ");
            html.append(" 	<tr>                                                            ");
            html.append(" 		<td colspan='2' >                                            ");
            html.append(" 		                                                            ");
            html.append(" 		<input type='submit' value='登录'></td>                     ");
            html.append(" 		                                                            ");
            html.append(" 	</tr>                                                           ");
            html.append(" 	</table>                                                        ");
            html.append(" </form>                                                           ");
            PrintWriter out = response.getWriter();
            out.write(html.toString());
            out.flush();
            out.close();
        }
        //2 验证登录信息 type =validate
        else if ("validate".equals(type)) {

            String username = request.getParameter("username");
            String password = request.getParameter("password");


            if (("admin".equals(username) && "admin".equals(password))
                    || "root".equals(username) && "root".equals(password)) {

                //记录当前登录的人用户名 username
                HttpSession session = request.getSession();
                session.setAttribute("username", username);

                System.out.println("登录成功");
                response.sendRedirect(request.getContextPath() + "/user?type=list");

            } else {
                System.out.println("登录失败");
                response.sendRedirect(request.getContextPath() + "/login?type=page");
            }


        }


    }

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

6.5.2 需要校验登录状态的Servlet

在响应资源前进行判断(比如UserServlet)

java
 if(!isLogin(request,response)){
     //跳转到登录画面
     response.sendRedirect(request.getContextPath()+"/login?type=page");
     return;
 }
java
private boolean isLogin(HttpServletRequest request, HttpServletResponse response) {
        //
        HttpSession session = request.getSession();
        String username = (String)session.getAttribute("username");

        if(username!= null && !"".equals(username)){
            return true;
        }
        
        return false;
    }

6.6 URL重写

https://blog.csdn.net/weixin_40648117/article/details/78844100

Released under the MIT License.