Spring Security OAuth2.0系列:JWT令牌详解

2026-07-11编程开发270351

Spring Security OAuth2.0结合JWT (JSON Web Tokens) 是一种流行的安全认证方法,用于在微服务架构中实现安全的API访问,本文介绍Spring Security OAuth2.0系列:JWT令牌详解,感兴趣的朋友跟随小编一起看看吧

目录
  • 1. JWT令牌介绍
    • 1.1 什么是JWT?
    • 1.2 JWT的结构
    • 1.3 JWT的应用场景
  • 2. 生成JWT令牌
    • 2.1 准备工作
    • 2.2 生成JWT令牌的步骤
    • 2.3 生成令牌的最佳实践
  • 3. 校验JWT令牌
    • 3.1 校验令牌的步骤
    • 3.2 校验令牌的常见场景
    • 3.3 安全注意事项

1. JWT令牌介绍

1.1 什么是JWT?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。它由三部分组成:头部(Header)、载荷(Payload)和签名(Signature),通过点号(.)分隔。

JWT的主要特点:

  • 紧凑性:体积小,可通过URL、POST参数或HTTP头部传输
  • 自包含性:包含所有必要信息,减少数据库查询
  • 安全性:使用签名确保令牌不被篡改
  • 标准化:基于JSON格式,跨语言支持良好

1.2 JWT的结构

一个典型的JWT令牌格式:xxxxx.yyyyy.zzzzz

1. 头部(Header)

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg:签名算法(如HS256、RS256)
  • typ:令牌类型,固定为"JWT"

2. 载荷(Payload)
包含声明(claims),分为三类:

  • 注册声明:预定义的声明,如iss(签发者)、exp(过期时间)、sub(主题)
  • 公共声明:可自定义,但应避免冲突
  • 私有声明:自定义声明,用于传递业务数据

3. 签名(Signature)
使用头部指定的算法,对编码后的头部和载荷进行签名,确保令牌完整性。

1.3 JWT的应用场景

  1. 身份认证:用户登录后,服务器生成JWT返回给客户端
  2. 授权:访问受保护资源时,客户端携带JWT
  3. 信息交换:安全地在各方之间传递信息

2. 生成JWT令牌

2.1 准备工作

首先,我们需要添加JWT依赖。以Java Spring Boot为例:



    io.jsonwebtoken
    jjwt-api
    0.11.5


    io.jsonwebtoken
    jjwt-impl
    0.11.5
    runtime


    io.jsonwebtoken
    jjwt-jackson
    0.11.5
    runtime

2.2 生成JWT令牌的步骤

步骤1:创建JWT工具类

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
    // 密钥(实际项目中应从配置读取)
    private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    // 令牌过期时间(24小时)
    private static final long EXPIRATION_TIME = 24 * 60 * 60 * 1000;
    /**
     * 生成JWT令牌
     * @param username 用户名
     * @param userId 用户ID
     * @param additionalClaims 额外声明
     * @return JWT令牌字符串
     */
    public static String generateToken(String username, String userId, 
                                       Map additionalClaims) {
        // 合并声明
        Map claims = new HashMap();
        claims.put("sub", username);
        claims.put("userId", userId);
        claims.put("iat", new Date()); // 签发时间
        if (additionalClaims != null) {
            claims.putAll(additionalClaims);
        }
        // 生成令牌
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SECRET_KEY, SignatureAlgorithm.HS256)
                .compact();
    }
}

步骤2:生成包含自定义声明的令牌

public class JwtDemo {
    public static void main(String[] args) {
        // 自定义声明
        Map customClaims = new HashMap();
        customClaims.put("role", "admin");
        customClaims.put("department", "IT");
        customClaims.put("permissions", new String[]{"read", "write", "delete"});
        // 生成令牌
        String token = JwtUtil.generateToken("zhangsan", "123456", customClaims);
        System.out.println("生成的JWT令牌:");
        System.out.println(token);
        // 解码查看内容(仅解码,不验证)
        String[] parts = token.split("\\.");
        System.out.println("\n解码后的内容:");
        System.out.println("Header: " + new String(java.util.Base64.getUrlDecoder().decode(parts[0])));
        System.out.println("Payload: " + new String(java.util.Base64.getUrlDecoder().decode(parts[1])));
    }
}

2.3 生成令牌的最佳实践

  • 密钥管理
    • 使用强密钥(至少256位)
    • 定期轮换密钥
    • 将密钥存储在安全的地方(如密钥管理服务)
  • 声明设计
    • 避免存储敏感信息(如密码)
    • 控制令牌大小,避免过大
    • 使用标准声明提高互操作性
  • 过期时间设置
    • 访问令牌:较短(如15分钟-1小时)
    • 刷新令牌:较长(如7天-30天)

3. 校验JWT令牌

3.1 校验令牌的步骤

步骤1:创建校验工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
public class JwtValidator {
    private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    /**
     * 验证并解析JWT令牌
     * @param token JWT令牌
     * @return 解析后的声明
     * @throws Exception 验证失败时抛出异常
     */
    public static Claims validateAndParseToken(String token) throws Exception {
        return Jwts.parserBuilder()
                .setSigningKey(SECRET_KEY)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
    /**
     * 验证令牌是否有效
     * @param token JWT令牌
     * @return 是否有效
     */
    public static boolean isValidToken(String token) {
        try {
            Claims claims = validateAndParseToken(token);
            // 检查过期时间
            Date expiration = claims.getExpiration();
            if (expiration.before(new Date())) {
                return false; // 令牌已过期
            }
            // 检查签发时间(可选)
            Date issuedAt = claims.getIssuedAt();
            if (issuedAt.after(new Date())) {
                return false; // 签发时间在未来
            }
            return true;
        } catch (Exception e) {
            return false; // 签名无效或其他错误
        }
    }
    /**
     * 获取令牌中的用户信息
     * @param token JWT令牌
     * @return 用户信息
     */
    public static UserInfo extractUserInfo(String token) {
        try {
            Claims claims = validateAndParseToken(token);
            UserInfo userInfo = new UserInfo();
            userInfo.setUsername(claims.getSubject());
            userInfo.setUserId(claims.get("userId", String.class));
            userInfo.setRole(claims.get("role", String.class));
            userInfo.setIssuedAt(claims.getIssuedAt());
            userInfo.setExpiration(claims.getExpiration());
            return userInfo;
        } catch (Exception e) {
            return null;
        }
    }
    // 用户信息类
    public static class UserInfo {
        private String username;
        private String userId;
        private String role;
        private Date issuedAt;
        private Date expiration;
        // getters and setters
    }
}

步骤2:在实际应用中使用校验

public class AuthInterceptor {
    /**
     * 拦截器中的令牌验证
     */
    public boolean preHandle(String token) {
        // 1. 检查令牌是否存在
        if (token == null || token.isEmpty()) {
            return false;
        }
        // 2. 验证令牌格式
        if (!token.matches("^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+$")) {
            return false;
        }
        // 3. 验证令牌有效性
        if (!JwtValidator.isValidToken(token)) {
            return false;
        }
        // 4. 提取用户信息并设置到上下文
        JwtValidator.UserInfo userInfo = JwtValidator.extractUserInfo(token);
        if (userInfo == null) {
            return false;
        }
        // 设置用户信息到线程上下文
        SecurityContext.setCurrentUser(userInfo);
        return true;
    }
}

3.2 校验令牌的常见场景

场景1:API接口鉴权

@RestController
@RequestMapping("/api")
public class UserController {
    @GetMapping("/profile")
    public ResponseEntity getUserProfile(@RequestHeader("Authorization") String authHeader) {
        // 提取Bearer令牌
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return ResponseEntity.status(401).body("未提供有效的认证令牌");
        }
        String token = authHeader.substring(7);
        try {
            // 验证令牌
            Claims claims = JwtValidator.validateAndParseToken(token);
            // 检查权限
            String role = claims.get("role", String.class);
            if (!"admin".equals(role) && !"user".equals(role)) {
                return ResponseEntity.status(403).body("权限不足");
            }
            // 返回用户信息
            Map response = new HashMap();
            response.put("username", claims.getSubject());
            response.put("userId", claims.get("userId"));
            response.put("role", role);
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            return ResponseEntity.status(401).body("令牌无效或已过期");
        }
    }
}

场景2:刷新令牌机制

public class TokenRefreshService {
    /**
     * 刷新访问令牌
     * @param refreshToken 刷新令牌
     * @return 新的访问令牌
     */
    public String refreshAccessToken(String refreshToken) {
        try {
            // 验证刷新令牌
            Claims claims = JwtValidator.validateAndParseToken(refreshToken);
            // 检查令牌类型
            String tokenType = claims.get("token_type", String.class);
            if (!"refresh".equals(tokenType)) {
                throw new IllegalArgumentException("不是刷新令牌");
            }
            // 检查是否在黑名单中(可选)
            if (isTokenRevoked(refreshToken)) {
                throw new IllegalArgumentException("令牌已被撤销");
            }
            // 生成新的访问令牌
            Map newClaims = new HashMap();
            newClaims.put("userId", claims.get("userId"));
            newClaims.put("role", claims.get("role"));
            return JwtUtil.generateToken(
                claims.getSubject(),
                claims.get("userId", String.class),
                newClaims
            );
        } catch (Exception e) {
            throw new RuntimeException("刷新令牌失败: " + e.getMessage());
        }
    }
    private boolean isTokenRevoked(String token) {
        // 实现令牌撤销检查逻辑
        return false;
    }
}

3.3 安全注意事项

  • 令牌存储安全
    • 客户端:使用HttpOnly Cookie或安全存储
    • 服务端:验证签名前检查令牌格式
  • 防止重放攻击
    • 使用jti(JWT ID)声明
    • 维护已使用令牌的黑名单
    • 设置合理的过期时间
  • 签名算法选择
    • HS256:对称加密,性能好,需要安全保管密钥
    • RS256:非对称加密,更安全,公钥可公开
  • 令牌撤销机制
    • 维护令牌黑名单
    • 使用短期令牌+刷新令牌
    • 关键操作需要重新认证

到此这篇关于Spring Security OAuth2.0系列:JWT令牌详解的文章就介绍到这了,更多相关Spring Security OAuth JWT令牌内容请搜索就爱读以前的文章或继续浏览下面的相关文章希望大家以后多多支持就爱读!

您可能感兴趣的文章:

  • JWT + Spring Security / OAuth2.0:微服务统一登录、鉴权、单点登录全解析
  • Spring Security Oauth2整合JWT的详细步骤和核心配置
  • Spring Clou整合 Security + Oauth2 + jwt实现权限认证的详细过程
  • 使用JWT作为Spring Security OAuth2的token存储问题
  • Spring Security OAuth2实现使用JWT的示例代码

《Spring Security OAuth2.0系列:JWT令牌详解.doc》

下载本文的Word格式文档,以方便收藏与打印。