SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现

会话技术

  1. 功能:提供用户登陆成功后的登陆标记 (一次登录,一段时间都登录)
  2. 会话
    1. 定义:包含一次或多次请求和响应的访问操作
    2. 建立会话:用户打开浏览器访问 Web 服务器资源时建立会话
    3. 结束会话:一方断开连接时结束会话
  3. 会话跟踪
    1. 定义:一种维护浏览器状态的方法
    2. 功能:服务器通过会话跟踪来识别多次请求是否来自于同一浏览器
    3. 同一次会话的多次请求间共享数据
  4. 会话跟踪方案
    1. 客户端会话跟踪技术 : Cookie

      SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现_第1张图片
    2. 服务端会话跟踪技术 : Session

      SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现_第2张图片
    3. 令牌技术(token)

      令牌 : 用户身份的标识,实际上就是一个字符串


Cookie

  1. 定义:浏览器访问服务器后,服务器传给浏览器的一段数据,由浏览器保存,后续通信中浏览器也要将 Cookie 发送给服务器
  2. 功能
    1. 识别用户身份 (uid)
    2. 存储用户偏好:让浏览器记住这位访客的特定信息
  3. 工作条件
    1. 浏览器每次访问该服务器,都必须带上 Cookie
    2. 浏览器需要保存 Cookie,不得轻易删除
    3. 不能跨域
  4. 工作流程
    1. 客户端提交一个HTTP请求给服务端
    2. 服务端 Set-Cookie,同时提交响应内容给客户端
    3. 客户端再次向服务器请求,并在请求头中携带一个Cookie
  5. 有效期
    1. Expire 值:Cookie 在生成时就会被指定一个 Expire 值,这就是 Cookie 的生存周期
    2. 立即清除 Cookie:生存周期设置为 “0” 或负值,这样在关闭浏览器时,就马上清除 Cookie,不会记录用户信息,更加安全
  6. 缺点
    1. 数量限制:一个浏览器能创建的 Cookie 数量最多为 300 个,并且每个不能超过 4KB,每个 Web 站点能设置的 Cookie 总数不能超过 20 个
    2. 安全性低:存在跨站点脚本攻击的可能,脚本指令可以读取当前站点的所有 Cookie 内容,并且可以提交到指定服务器重现其功能

问:为什么需要 Cookie? 答:web程序使用的HTTP协议是无状态的协议,对于事务处理没有记忆能力,如果后续处理需要前面的信息则必须重传,导致每次连接传送的数据量增大


Session

一、概述

  1. 定义
    1. Session (会话控制),Session 对象存储特定用户会话所需的属性及配置信息
    2. SessionID:客户端第一次请求服务器时,服务器为客户端算出的一个值,存储在 Cookie 中,用于定位用户 Session 在服务器中的位置
  2. 与 Cookie 的区别:Cookie 可以通过伪造来实现登录并进行一些 HTTP 请求,从安全性上来讲,Session 比 Cookie 安全性稍微高一些
  3. 功能 :提高安全性
  4. 有效期:一般为半小时,可以根据需求设定
  5. 缺点:Session 是存储在服务器当中的,所以 Session 过多,会对服务器产生压力

二、相关工具类

  1. HttpSession
    1. 定义:javax.servlet.http.HttpSession 类,是 JavaWeb 自带的工具类

    2. 常用方法

      命令 功能
      getAttribute("attributeName") 获取指定名称的属性值
      setAttribute("attributeName", myAttributeObject) 设置指定名称的属性值
  2. DTO:Data Transfer Object,在 Session 中的 MyClass 应该去掉敏感信息,转为 MyClassDTO 进行传输

三、认证流程

工作流程

  1. 创建 Session :用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
  2. 响应 Session ID:服务器响应请求时,将此 Session 的唯一标识信息 SessionID 返回给浏览器
  3. 存入 Cookie:浏览器接收到服务器返回的 SessionID 信息后,将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
  4. 携带 Cookie:当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息
    1. 存在 Cookie → 自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息
      1. 找到 Session → 证明用户已经登录可执行后面操作
      2. 未找到 Session → 可能是已过期的 Session 或者是假 Cookie
    2. 不存在 Cookie → 说明用户没有登录或者登录失效

原理说明

  1. 登录:客户端提交登录表单

  2. 校验:服务器进行登录校验(如果校验失败在直接抛出异常)

  3. 获取 Session 对象

    1. Request 中已经有 Session → 获取 HttpSession 对象
    2. Request 中还没有 Session → 创建 HttpSession 对象,并生成唯一标识符(SessionId)
    request.getSession()          // 如果 request 中有 session 则获取
    
  4. 更新登录状态:将用户对象 user 以 USER_LOGIN_STATE 为键存储在该 HttpSession 中

    1. USER_LOGIN_STATE 是一个常量,用来标识用户登录状态
    request.getSession().setAttribute(USER_LOGIN_STATE, user);        // 更新Session登录状态
    
  5. 设置过期时间:设置该 HttpSession 的过期时间为 1 小时(即 1 小时如果内没有与该 HttpSession 关联的请求,该 HttpSession 将被自动销毁)

    request.getSession().setMaxInactiveInterval(60 * 60);             // 设定Session过期时间
    
  6. 回传 SessionId:服务器将 SessionId 写入 Response Headers 中的 Set-Cookie

    HTTP/1.1 200 OK
    Set-Cookie: JSESSIONID=1234567890ABCDEF; Path=/; HttpOnly
    
  7. 保存 SessionId:客户端将 SessionId 存储在 Cookie 中

  8. 客户端请求资源:客户端登录后,每次访问服务器都会将 SessionId 写在 请求头的 Cookie 属性中

    GET /some/resource HTTP/1.1
    Host: www.example.com
    Cookie: JSESSIONID=1234567890ABCDEF
    

四、示例

实现逻辑

  1. 创建 DTO 类

    1. 创建 com.projectname.dto.UserDTO 类
    2. 定义 UserDTO 类中的属性 (希望展示的 User 的属性)
  2. 创建 UserHolder 工具类

    1. 创建 com.projectname.utils.UserHolder 类
    2. 定义 ThreadLocal 本地线程 tl
    3. 定义 save / get / remove 方法
  3. 修改 UserServiceImpl 中的返回值

    1. 调用 hutools 的 BeanUtil 工具类,使用其 copyProperties(User, UserDTO.class) 方法进行 DTO 映射

    2. 修改 session 中的返回值 (User → UserDTO)

      session.setAttribute(”User”, BeanUtil.copyProperties( user, UserDTO.class)

  4. 修改 UserController 中的返回值

    1. UserDTO user = UserHolder.getUser();
  5. 创建拦截器

    1. 获取用户请求的 Token 或其他标识
    2. 验证 Token,并解析出用户信息
    3. 将用户信息存入 UserHolder
    4. 在请求完成后清除 UserHolder 的数据,防止内存泄漏
  6. 配置拦截器:注册拦截器,让其拦截需要验证的路径

示例代码

  1. UserDTO 类(com.projectname.dto.UserDTO)

    // com.projectname.dto.UserDTO
    @Data
    public class UserDTO {
        private Long id;
        private String nickName;
        private String icon;
    }
    
  2. UserHolder 类(com.myproject.utils.UserHolder.java)

    // com.projectname.utils.UserHolder
    public class UserHolder {
        private static final ThreadLocal tl = new ThreadLocal<>();
    
        public static void saveUser(UserDTO user){
            tl.set(user);
        }
    
        public static UserDTO getUser(){
            return tl.get();
        }
    
        public static void removeUser(){
            tl.remove();
        }
    }
    
  3. 拦截器(com.myproject.config.UserInterceptor.java)

    1. 目标:自动拦截所有请求,如果有用户信息则存入 UserHolder 中
    @Component
    public class UserInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 1. 从请求头中获取 Token,如果没有 Token 则跳过拦截器(可能是未登录状态)
            String token = request.getHeader("Authorization");
            if (!StringUtils.hasText(token)) {
                return true;
            }
    
            // 2. 校验 Token 并解析用户信息(此处简单模拟,实际应调用 Token 验证服务或工具类)
            UserDTO user = validateTokenAndGetUser(token);
            if (user == null) {        // 如果解析失败,允许继续(未登录状态)
                return true;
            }
    
            // 3. 将用户信息保存到 UserHolder 中
            UserHolder.setUser(user);
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            // 请求处理完成后清理 ThreadLocal,防止内存泄漏
            UserHolder.clear();
        }
    
    		// 模拟 Token 校验并获取用户信息
        private UserDTO validateTokenAndGetUser(String token) {
            // 示例:模拟解析 Token 获取用户
            if ("valid-token".equals(token)) {
                return getUserByToken(token);
            }
            return null; // Token 无效或解析失败
        }
        
    }
    
  4. 配置拦截器

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Autowired
        private UserInterceptor userInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(userInterceptor)             // 注册拦截器
                    .addPathPatterns("/**")                      // 拦截所有路径
                    .excludePathPatterns("/login", "/register"); // 排除登录、注册等公开路径
        }
    }
    

你可能感兴趣的:(java,spring,boot)