token,session,cookie,jwt,oauth2傻傻分不清楚

1. 一文搞懂 Token、Session、Cookie、JWT 和 OAuth2

最近发现有些小伙伴,对 Token、Session、Cookie、JWT、OAuth2 这些概念非常容易搞混。

有些小伙伴在工作中可能会遇到过这样的困惑:

  • 做登录功能时,到底该用 Session 还是 JWT?
  • OAuth2 和 Token 是什么关系?
  • 为什么有的方案要把 Token 存在 Cookie 里?

今天这篇文章专门跟大家一起聊聊这个话题,希望对你会有所帮助。

1.1. 从餐厅就餐模型开始讲

为了让大家更好理解,我先用一个餐厅就餐的比喻来解释这些概念:

餐厅就餐模型比喻图

现在,让我们深入每个概念的技术细节。

1.2. Cookie:HTTP 的世界身份证

1.2.1. 什么是 Cookie?

Cookie 是存储在浏览器端的一小段文本数据,由服务器通过 HTTP 响应头的 Set-Cookie 字段发送给浏览器,浏览器随后会自动在每次请求中通过 Cookie 头将其带回给服务器。

工作原理

Cookie 工作原理图

// 服务器设置Cookie
@PostMapping("/login")
public ResponseEntity login(@RequestBody User user, HttpServletResponse response) {
    if (authService.authenticate(user)) {
        Cookie cookie = new Cookie("session_id", generateSessionId());
        cookie.setMaxAge(3600); // 1小时有效期
        cookie.setHttpOnly(true); // 防止XSS攻击
        cookie.setSecure(true); // 仅HTTPS传输
        cookie.setPath("/"); // 对整个站点有效
        response.addCookie(cookie);
        return ResponseEntity.ok().build();
    }
    return ResponseEntity.status(401).build();
}

// 读取Cookie
@GetMapping("/profile")
public ResponseEntity getProfile(@CookieValue("session_id") String sessionId) {
    User user = sessionService.getUserBySession(sessionId);
    return ResponseEntity.ok(user);
}
属性 作用 安全建议
HttpOnly 防止 JavaScript 访问 必须设置为 true,防 XSS
Secure 仅通过 HTTPS 传输 生产环境必须设置为 true
SameSite 控制跨站请求时是否发送 Cookie 建议设置为 Strict 或 Lax
Max-Age 设置 Cookie 有效期 根据业务安全性要求设置

1.3. Session:服务端的用户档案

1.3.1. 什么是 Session?

Session 是存储在服务器端的用户状态信息。服务器为每个用户创建一个唯一的 Session ID,并通过 Cookie 将这个 ID 传递给浏览器,浏览器后续请求时带上这个 ID,服务器就能识别用户身份。

Session 存储结构

// 典型的Session数据结构
public class UserSession {
    private String sessionId;
    private String userId;
    private String username;
    private Date loginTime;
    private Date lastAccessTime;
    private Map<String, Object> attributes; // 自定义属性

    // 省略getter/setter
}

1.3.2. Session 实战代码

// 基于Spring Session的实现
@PostMapping("/login")
public String login(@RequestParam String username,
                   @RequestParam String password,
                   HttpSession session) {
    User user = userService.authenticate(username, password);
    if (user != null) {
        // 将用户信息存入Session
        session.setAttribute("currentUser", user);
        session.setAttribute("loginTime", new Date());
        return "redirect:/dashboard";
    }
    return "login?error=true";
}

@GetMapping("/dashboard")
public String dashboard(HttpSession session) {
    // 从Session获取用户信息
    User user = (User) session.getAttribute("currentUser");
    if (user == null) {
        return "redirect:/login";
    }
    return "dashboard";
}

1.3.3. Session 的存储方案

1.3.3.1. 内存存储(默认)

# application.yml
server:
  servlet:
    session:
      timeout: 1800 # 30分钟过期时间

1.3.3.2. Redis 分布式存储

@Configuration
@EnableRedisHttpSession // 启用Redis Session存储
public class SessionConfig {
    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }
}

1.3.3.3. Session 集群同步问题

Session 集群同步问题示意

1.4. Token:去中心化的身份令牌

1.4.1. 什么是 Token?

Token 是一种自包含的身份凭证,服务器不需要在服务端存储会话状态,所有必要信息都包含在 Token 本身中。

Token vs Session 核心区别

Token vs Session 核心区别对比图

1.4.2. Token 实战代码

// 生成Token
public String generateToken(User user) {
    long currentTime = System.currentTimeMillis();
    return JWT.create()
            .withIssuer("myapp") // 签发者
            .withSubject(user.getId()) // 用户ID
            .withClaim("username", user.getUsername())
            .withClaim("role", user.getRole())
            .withIssuedAt(new Date(currentTime)) // 签发时间
            .withExpiresAt(new Date(currentTime + 3600000)) // 过期时间
            .sign(Algorithm.HMAC256(secret)); // 签名密钥
}

// 验证Token
public boolean validateToken(String token) {
    try {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret))
                .withIssuer("myapp")
                .build();
        DecodedJWT jwt = verifier.verify(token);
        return true;
    } catch (JWTVerificationException exception) {
        return false;
    }
}

1.5. JWT:现代化的 Token 标准

1.5.1. 什么是 JWT?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。

这种信息可以被验证和信任,因为它是数字签名的。

JWT 结构

header.payload.signature

解码示例

// Header
{
"alg": "HS256",
"typ": "JWT"
}

// Payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}

// Signature
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

1.5.2. JWT 实战代码

// 创建JWT
public String createJWT(User user) {
    return Jwts.builder()
            .setHeaderParam("typ", "JWT")
            .setSubject(user.getId())
            .setIssuer("myapp")
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 3600000))
            .claim("username", user.getUsername())
            .claim("role", user.getRole())
            .signWith(SignatureAlgorithm.HS256, secret.getBytes())
            .compact();
}

// 解析JWT
public Claims parseJWT(String jwt) {
    return Jwts.parser()
            .setSigningKey(secret.getBytes())
            .parseClaimsJws(jwt)
            .getBody();
}

// 在Spring Security中使用JWT
@Component
public class JwtFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain chain) {
        String token = resolveToken(request);
        if (token != null && validateToken(token)) {
            Authentication auth = getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        chain.doFilter(request, response);
    }
}

1.5.3. JWT 的最佳实践

1.5.3.1. 安全存储

// 前端安全存储方案
// 不推荐:localStorage(易受XSS攻击)
// 推荐:HttpOnly Cookie(防XSS)或内存存储

1.5.3.2. 令牌刷新机制

// 双Token机制:Access Token + Refresh Token
public class TokenPair {
    private String accessToken;  // 短期有效:1小时
    private String refreshToken; // 长期有效:7天
}

// 刷新令牌接口
@PostMapping("/refresh")
public ResponseEntity refresh(@RequestBody RefreshRequest request) {
    String refreshToken = request.getRefreshToken();
    if (validateRefreshToken(refreshToken)) {
        String userId = extractUserId(refreshToken);
        String newAccessToken = generateAccessToken(userId);
        return ResponseEntity.ok(new TokenPair(newAccessToken, refreshToken));
    }
    return ResponseEntity.status(401).build();
}

1.6. OAuth 2.0:授权框架之王

1.6.1. 什么是 OAuth 2.0?

OAuth 2.0 是一个授权框架,允许第三方应用在获得用户授权后,代表用户访问受保护的资源。

OAuth 2.0 角色

  • 资源所有者(Resource Owner):用户
  • 客户端(Client):第三方应用
  • 授权服务器(Authorization Server):颁发访问令牌
  • 资源服务器(Resource Server):托管受保护资源

1.6.2. OAuth 2.0 授权码流程

OAuth 2.0 授权码流程图

1.6.3. OAuth 2.0 实战代码

// Spring Security OAuth2配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("clientapp")
                .secret(passwordEncoder.encode("123456"))
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .scopes("read", "write")
                .redirectUris("http://localhost:8080/callback");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .tokenStore(tokenStore())
                .accessTokenConverter(accessTokenConverter());
    }
}

// 资源服务器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/api/public/**").permitAll()
                .antMatchers("/api/private/**").authenticated()
                .antMatchers("/api/admin/**").hasRole("ADMIN");
    }
}

1.7. 五大概念对比

为了让大家更清晰地理解这五个概念的关系和区别,我准备了以下对比表格:

1.7.1. 功能定位对比

概念 本质 存储位置 主要用途 特点
Cookie HTTP 状态管理机制 浏览器 维持会话状态 自动携带,有大小限制
Session 服务端会话信息 服务器 存储用户状态 服务端状态,需要存储管理
Token 访问凭证 客户端/服务端 身份认证 自包含,可验证
JWT Token 的一种实现标准 客户端/服务端 安全传输信息 标准化,自包含,可签名
OAuth2 授权框架 不直接存储 第三方授权 标准化授权流程

1.7.2. 应用场景对比

场景 推荐方案 原因说明
传统 Web 应用 Session + Cookie 简单易用,生态成熟
前后端分离应用 JWT 无状态,适合 API 认证
第三方登录 OAuth 2.0 标准化授权,安全可靠
微服务架构 JWT 分布式认证,无需会话同步
移动端应用 Token 轻量级,适合移动网络

1.7.3. 安全考虑对比

安全威胁 Cookie 方案防护 Token/JWT 方案防护
XSS 攻击 HttpOnly Cookie 避免 localStorage 存储
CSRF 攻击 SameSite Cookie 自定义 Header + CSRF Token
令牌泄露 短期有效 + HTTPS 短期有效 + HTTPS + 刷新机制
数据篡改 服务端验证 签名验证

1.8. 总结

通过今天的深入探讨,我们可以得出以下结论:

  1. Cookie 是载体:HTTP 协议的状态管理机制,是 Session 和 Token 的传输媒介之一。
  2. Session 是状态:服务端维护的会话状态,需要借助 Cookie 或 URL 重写来实现。
  3. Token 是凭证:认证授权的凭证,可以放在 Cookie、Header 或 URL 中。
  4. JWT 是标准:Token 的一种标准化实现,自包含、可验证、可信任。
  5. OAuth2 是框架:授权框架,定义了完整的第三方授权流程。

最终建议

  • 简单 Web 应用:Session + Cookie
  • 前后端分离:JWT + HTTP Header
  • 第三方授权:OAuth 2.0 + JWT

没有最好的方案,只有最合适的方案。

理解每个技术的本质和适用场景,才能做出正确的架构决策。