java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Security OAuth JWT令牌

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

作者:后会无期77

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

1. JWT令牌介绍

1.1 什么是JWT?

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

JWT的主要特点:

1.2 JWT的结构

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

1. 头部(Header)

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

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

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

1.3 JWT的应用场景

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

2. 生成JWT令牌

2.1 准备工作

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

<!-- Maven依赖 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

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<String, Object> additionalClaims) {
        // 合并声明
        Map<String, Object> 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<String, Object> 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 生成令牌的最佳实践

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<String, Object> 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<String, Object> 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 安全注意事项

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

您可能感兴趣的文章:
阅读全文