会话跟踪方案

简介: Cookie是浏览器端存储的小型数据文件,用于保存用户状态、登录信息等,但存在不安全、不可跨域等问题。Session将数据存于服务器端,通过SessionID识别用户,更安全。Token(如JWT)是一种自包含、可验证的身份凭证,广泛用于前后端分离和移动端认证,支持分布式系统,安全性高,灵活性强。

Cookie

什么是Cookie?

  • 概念:存储在用户浏览器端的一个小型数据文件,用于跟踪和保存用户的状态信息
  • 用处:主要用于保持用户登录状态、跟踪用户行为、存储用户偏好等
  • 存储在浏览器端

优点:

HTTP协议中支持的技术

缺点

  • 移动端无法使用
  • 不安全,用户可以自己禁用Cookie
  • Cookie 不能跨域

跨域区分三个维度:协议、IP/域名、端口

关于 Cookie

img

每个域下面都有各自的 Cookie,访问不同的网站带属于这个网站的Cookie,不会带别人的 Cookie,否则就会乱套了,前后端也是一样,浏览器会发送请求并且该请求携带Cookie到前端,而这个Cookie如果拿去访问后端就不可行,因为Cookie不能跨域

问题:但是 Cookie 是明文存储在用户本地,而且带有大量的用户信息,这不太安全

相关代码实践

// 设置 Cookie
@GetMapping("/c1")
public BaseResponse cookie1(HttpServletResponse response) {
   
    response.addCookie(new Cookie("loginUser", "lantz"));
    return ResultUtils.success("ok");
}

// 获取 Cookie
@GetMapping("/c2")
public BaseResponse cookie2(HttpServletRequest request) {
   
    Cookie[] cookies = request.getCookies();
    for (Cookie cookie : cookies) {
   
        if (cookie.getName().equals("loginUser")) {
   
            log.info("loginUser value=" + cookie.getValue());
        }
    }
    return ResultUtils.success("ok");
}

在测试c2接口的时候可以看到debug窗口会返回一条数据:

2025-10-12 18:23:29.206  INFO 37868 --- [nio-8080-exec-5] c.l.l.controller.SessionController       : loginUser value=lantz

说明已经成功获取Cookie

Session

什么是 Session?

  • 概念服务器端保存用户状态的机制,每个用户会话都有一个唯一的 SessionIDJSESSIONID
  • 用处:主要用于跟踪用户在服务器上的状态信息,例如登录状态和购物车内容
  • 存储在服务器端,然后对应的 Session ID 通过 Cookie 保存在客户端浏览器中

img

关于 Session

Session就解决了 Cookie 的这个问题:Cookie 是明文存储在用户本地,而且带有大量的用户信息,不太安全

Session 就是把用户的会话信息存储在服务端,然后颁发给客户端一个 sessionId,让客户端之后带着 sessionId 来请求。这样服务端就可以通过 sessionId 去找到这个用户的信息,从而识别请求。

那么客户端是如何带上 sessionId 的?

这个 sessionId 还是按照 Cookie 的形式存储在用户的本地,发起请求的时候带上即可。

演示代码

设置Session

// 设置 Session
@GetMapping("/s1")
public BaseResponse session1(HttpSession session) {
   
    log.info("HttpSession-s1: {}", session.hashCode());
    session.setAttribute("loginUser", "lantz");
    return ResultUtils.success("ok");
}

测试s1设置session并且输出当前Session的哈希码

如下所示:

HttpSession-s1: 2054650805

返回的Cookie

img

可见:在发送Session请求的时候,浏览器会为当前的请求自动生成一个哈希码,用作JSESSIONID,并且会将这个JSESSIONID加到Cookie上,一并返回给服务端

获取Session

// 获取 Session
@GetMapping("/s2")
public BaseResponse session2(HttpServletRequest request) {
   
    HttpSession session = request.getSession();
    log.info("HttpSession-s2: {}", session.hashCode());
    Object loginUser = session.getAttribute("loginUser");
    log.info("loginUser: {}", loginUser);
    return ResultUtils.success("ok");
}

测试s2获取到当前Session的哈希码

如下所示:

2025-10-12 18:40:43.562  INFO 16300 --- [nio-8080-exec-4] c.l.l.controller.SessionController       : HttpSession-s2: 2054650805
2025-10-12 18:40:43.562  INFO 16300 --- [nio-8080-exec-4] c.l.l.controller.SessionController       : loginUser: lantz

Cookie 展示:

img

Token

什么是Token

  • 概念:本质是一个加密的字符串,用于身份验证和授权,可以包含用户信息和权限,用于验证用户身份或授权访问资源。
  • 认证后,后端服务会返回 Token,存储在客户端(浏览器或移动应用中),后续客户端访问服务端需要带上这个 Token

其实只需要一个能代表身份的凭证即可,一个服务端颁发给用户的凭证,之后的请求让用户带着这个凭证就行。

就像我们的身份证代表着我们,里面包含了我们的信息

但是担心凭证被伪造怎么办?可以把凭证给签名了,这样服务器就可验证凭证的真伪了。

这个凭证就叫 Token

如果一个用户登陆了系统,就返回一个 Token 给他,之后每次请求他带这个 Token 来就行。服务器验证了真伪之后拿到 Token 里面的用户信息就知道这个请求是谁发的了。

Token 简单地说就是一个含有凭证信息的令牌,只要服务器能识别这个令牌就能根据令牌的身份进行相应的相应。

jwt

全称: JSON Web Token(https://jwt.io/)

定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

组成:

  • 第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JWT"}
  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","username":"Tom"}
  • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

代码演示

生成令牌

@Test
public void getjwt(){
   
    Map<String, Object> claims = new HashMap<>();
    claims.put("id", 123);
    claims.put("username", "lantz");
    String jwt = Jwts.builder()
        .setClaims(claims) // 自定义载荷
        .signWith(SignatureAlgorithm.HS256, "lanuc") // 签名算法
        .setExpiration(new Date(System.currentTimeMillis() + 3600 * 100)) // 设置过期时间
        .compact();
    System.out.println(jwt);
}

生成令牌如下:

eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MTIzLCJleHAiOjE3NjAyNjc5NDQsInVzZXJuYW1lIjoibGFudHoifQ.9RYwpzNji9kfg33ge1qQUDFq_2vQdO__mr1Eze-IXlc

解析令牌

@Test
public void deCode(){
   
    Claims body = Jwts.parser()
            .setSigningKey("lanuc") // 指定签名秘钥
            .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MTIzLCJleHAiOjE3NjAyNjc5NDQsInVzZXJuYW1lIjoibGFudHoifQ.9RYwpzNji9kfg33ge1qQUDFq_2vQdO__mr1Eze-IXlc")//解析令牌
            .getBody();
    System.out.println(body);

}

生成结果:

{
   id=123, exp=1760267944, username=lantz}

SpringBoot 实现

JwtUtils

jwt工具类(JwtUtils)-- 生成令牌,解析令牌

生成令牌

/**
 * 生成令牌
 * @param claims JWT 第二部分负载 payload 中存储的内容
 * @return
 */
public static String generateToken(Map<String, Object> claims) {
   

    return Jwts.builder()
            .setClaims(claims)
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
            .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
            .compact();
}

解析令牌

/**
 * 解析JWT Token
 * @param token jwt 令牌
 * @return
 */
public static Claims parseToken(String token) {
   
    return Jwts.parser()
            .setSigningKey(SECRET_KEY)
            .parseClaimsJws(token)
            .getBody();
}

用户登录

需要先获取用户登录信息,然后才能根据用户信息生成令牌

@PostMapping("/login")
public BaseResponse<LoginResponse> userLogin(@RequestBody UseLoginRequest useLoginRequest, HttpServletRequest request){
   
    if (useLoginRequest == null){
   
        return null;
    }
    String userAccount = useLoginRequest.getUserAccount();
    String userPassword = useLoginRequest.getUserPassword();
    if (StringUtils.isAnyBlank(userAccount, userPassword)) {
   
        return null;
    }
    User user = userService.userLogin(userAccount, userPassword, request);
    if (user == null) {
   
        return ResultUtils.error(ErrorCode.PARAMS_ERROR, "登录失败");
    }
    // 获取登录用户
    User loginUer = userService.getLoginUer(request);
    // 生成令牌,下发令牌
    Map<String, Object> claims = new HashMap<>();
    claims.put("id", loginUer.getId());
    claims.put("userAccount", loginUer.getUserAccount());
    claims.put("userName", loginUer.getUserName());
    String jwt = JwtUtils.generateToken(claims); // 生成令牌

    // 创建登录响应
    LoginResponse loginResponse = new LoginResponse();
    loginResponse.setUser(user);
    loginResponse.setJwt(jwt);

    return ResultUtils.success(loginResponse);
}

测试结果:

{
   
    "code": 0,
    "data": {
   
        "user": {
   
            "id": 4,
            "userAccount": "Lantz",
            "userName": "lan",
            "userAvatar": "https://pic.code-nav.cn/user_avatar/1872161376428277761/thumbnail/D5QaPIaHNcft2xW1.jpg",
            "gender": 1,
            "userPassword": "dd8fa48dc3e51a450c6141f5c0ad6665",
            "phone": "12335555",
            "email": "22334466@email.com",
            "userStatus": 0,
            "createTime": "2025-04-18T10:43:38.000+00:00",
            "updateTime": null,
            "isDelete": null,
            "userRole": 1
        },
        "jwt": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQWNjb3VudCI6IkxhbnR6IiwiaWQiOjQsInVzZXJOYW1lIjoibGFuIiwiZXhwIjoxNzYwMjczMzMxfQ.eWcD38s2pfmYin78IssVy2tqeq0SA5CWtAjbq5CKto0"
    },
    "message": "ok"
}

end

相关文章
|
25天前
|
监控 Java Spring
AOP 切面编程
AOP(面向切面编程)通过动态代理在不修改源码的前提下,对方法进行增强。核心概念包括连接点、通知、切入点、切面和目标对象。常用于日志记录、权限校验、性能监控等场景,结合Spring AOP与@Aspect、@Pointcut等注解,实现灵活的横切逻辑管理。
319 6
AOP 切面编程
|
26天前
|
缓存 前端开发 API
登录校验---Filter过滤器
Filter是JavaWeb三大组件之一,用于拦截请求并实现登录校验、编码处理等功能。通过`doFilter()`方法实现过滤逻辑,支持配置拦截路径与过滤器链。常用于JWT令牌验证、跨域处理等场景。
311 10
登录校验---Filter过滤器
|
Kubernetes 网络协议 Ubuntu
Kubeadm 快速搭建 k8s v1.19.1 集群(Ubuntu Server 20.04 LTS)
安装准备工作安装环境要求:角色 实验环境 生产环境 操作系统 master cpu/内存:2 Core/2G cpu/内存:2 Core/4G linux 内核 4.4+ node cpu/内存:1 Core/2G cpu/内存:4 Core/16G linux 内核 4.4+ 备注 Node:应根据需要运行的容器数量进行配置; Linux 操作系统基于 x86_64 架构的各种 Linux 发行版...
1552 2
Kubeadm 快速搭建 k8s v1.19.1 集群(Ubuntu Server 20.04 LTS)
|
27天前
|
Java Linux Apache
在CentOS服务器上编译并部署NiFi源码
部署Apache NiFi在CentOS上是一个涉及细节的过程,需要注意Java环境、源码编译、配置调整等多个方面。遵循上述步骤,可以在CentOS服务器上成功部署和配置Apache NiFi,从而高效地处理和分发数据。
112 17
|
25天前
|
Java
在Java中避免科学计数法的策略
以上方法都提供了在Java中避免科学计数法的有效途径。选择哪种方法取决于具体的应用场景和需求,如需要的精度、性能考虑以及代码的可读性。在处理大数或精度要求较高的数值时,使用 `BigDecimal` 是一个好的选择。对于一般的数值格式化需求,`DecimalFormat` 或 `String.format()` 方法可能更为方便和高效。
150 19
|
25天前
|
机器学习/深度学习 数据采集 运维
别等系统崩了才救火:智能化运维,才是真正的高可用!
别等系统崩了才救火:智能化运维,才是真正的高可用!
161 8
|
28天前
|
机器学习/深度学习 Kubernetes API
【Azure APIM】自建网关(self-host gateway)收集请求的Header和Body内容到日志中的办法
在Azure API Management中,通过配置trace策略可完整记录API请求的Header和Body信息。在Inbound和Outbound策略中分别使用context.Request/Response.Headers和Body.As&lt;string&gt;方法捕获数据,并写入Trace日志,便于排查与审计。
|
7月前
|
SQL 人工智能 安全
当 MCP 遇上 Serverless,AI 时代的最佳搭档
随着 AI 技术的飞速发展,MCP(模型上下文协议) 逐渐崭露头角。这项由 Anthropic 公司(Claude 的创造者)于 2024 年 11 月推出的开放协议,正在重新定义 AI 与数字世界的交互方式。这项开放协议不仅让 AI 突破传统对话边界,更赋予其执行现实任务的能力,堪称人工智能向"行动智能体"进化的里程碑。然而从火热概念到落地业务,MCP 还需要找到云端“好搭档”。
2988 76
|
11月前
|
SQL 存储 缓存
日志服务 SQL 引擎全新升级
SQL 作为 SLS 基础功能,每天承载了用户大量日志数据的分析请求,既有小数据量的快速查询(如告警、即席查询等);也有上万亿数据规模的报表级分析。SLS 作为 Serverless 服务,除了要满足不同用户的各类需求,还要兼顾性能、隔离性、稳定性等要求。过去一年多的时间,SLS SQL 团队做了大量的工作,对 SQL 引擎进行了全新升级,SQL 的执行性能、隔离性等方面都有了大幅的提升。
414 99
|
10月前
|
存储 消息中间件 Kafka
聊一聊日志背后的抽象
本文从思考日志的本质开始,一览业界对日志使用的最佳实践,然后尝试给出分布式存储场景下对日志模块的需求抽象,最后是技术探索路上个人的一点点感悟。
594 81