JWT(JSON Web Token)是一种基于 JSON 的开放标准(RFC 7519),用于在网络应用间安全传递声明。其核心结构包含三部分:
Header(头部):通常由两部分组成,令牌类型(如 JWT)和使用的签名算法(如 HMAC SHA256 或 RSA)。
{
"alg": "HS256",
"typ": "JWT"
}
此 JSON 被 Base64Url 编码形成 JWT 的第一部分。
Payload(负载):包含声明(Claims),即关于实体(通常是用户)和其他数据的声明。声明分为三类:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
此 JSON 被 Base64Url 编码形成 JWT 的第二部分。
Signature(签名):为创建签名部分,需使用编码后的 Header、编码后的 Payload、一个秘钥(secret)和 Header 中指定的签名算法。例如使用 HMAC SHA256 算法:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名用于验证消息在传输过程中未被更改,并且在使用私钥签名的情况下,还可以验证 JWT 的发送者身份。
JWT 的典型工作流程涉及以下步骤:
Bearer
)。JWT 与传统的会话认证机制(如基于 session 的认证)有显著区别:
JWT 在 SpringBoot 应用中广泛应用于以下场景:
Spring Security 是 Spring 生态系统中用于身份验证和授权的框架。其核心组件包括:
SecurityFilterChain:Spring Security 的入口点,负责处理 HTTP 请求的安全过滤。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
AuthenticationManager:负责处理身份验证请求,通常由 ProviderManager
实现,它管理多个 AuthenticationProvider
。
AuthenticationProvider:具体的身份验证提供者,如基于用户名密码的 DaoAuthenticationProvider
。
UserDetailsService:用于加载用户信息的服务接口,通常从数据库或其他存储中获取用户数据。
PasswordEncoder:用于密码加密和解密的接口,如 BCryptPasswordEncoder
。
在 SpringBoot 中集成 JWT 主要涉及以下几个关键组件:
OncePerRequestFilter
。AuthenticationProvider
接口。Authentication
实现类,如 UsernamePasswordAuthenticationToken
的变体。io.jsonwebtoken
库的功能。在 Spring Security 中配置 JWT 过滤器链的典型方式:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final AuthenticationManager authenticationManager;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter,
AuthenticationManager authenticationManager) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.authenticationManager = authenticationManager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return authenticationManager;
}
}
JWT 认证的完整流程包括:
Authentication
对象并设置到 SecurityContextHolder
中。Authentication
对象中的权限信息决定是否允许访问资源。JWT 的生成过程涉及以下核心步骤:
构建 Header:设置令牌类型和签名算法。
Map<String, Object> header = new HashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
构建 Payload:添加声明信息,如用户 ID、用户名、角色等。
Map<String, Object> claims = new HashMap<>();
claims.put("sub", user.getId());
claims.put("username", user.getUsername());
claims.put("roles", user.getRoles());
claims.put("iat", System.currentTimeMillis() / 1000);
claims.put("exp", System.currentTimeMillis() / 1000 + 3600); // 1小时过期
签名生成:使用 Header、Payload 和秘钥生成签名。
String secret = "your-256-bit-secret"; // 安全的秘钥
String token = Jwts.builder()
.setHeader(header)
.setClaims(claims)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
JJWT(Java JWT)是一个流行的 JWT 处理库,其核心类包括:
Jwts:工厂类,提供创建和解析 JWT 的静态方法。
public final class Jwts {
public static JwtBuilder builder() {
return new DefaultJwtBuilder();
}
public static JwtParser parser() {
return new DefaultJwtParser();
}
}
JwtBuilder:用于构建 JWT 的接口,默认实现为 DefaultJwtBuilder
。
public interface JwtBuilder {
JwtBuilder setHeader(Map<String, Object> header);
JwtBuilder setClaims(Map<String, Object> claims);
JwtBuilder signWith(SignatureAlgorithm alg, String secret);
String compact();
}
JwtParser:用于解析 JWT 的接口,默认实现为 DefaultJwtParser
。
public interface JwtParser {
JwtParser setSigningKey(String key);
Claims parseClaimsJws(String jws);
}
JWT 的签名验证是确保令牌完整性和真实性的关键步骤。验证过程如下:
public boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token);
return true;
} catch (SignatureException e) {
// 签名无效
logger.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
// JWT 格式错误
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
// JWT 过期
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
// JWT 不受支持
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
// JWT 为空
logger.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
JWT 的过期时间(exp)是一个重要的安全特性。当验证 JWT 时,JJWT 库会自动检查 exp 声明:
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
若令牌已过期,parseClaimsJws
方法会抛出 ExpiredJwtException
。处理过期令牌的常见策略包括:
JWT 认证过滤器通常继承自 OncePerRequestFilter
,确保每个请求只被过滤一次。其核心方法是 doFilterInternal
:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
// 从请求中提取 JWT
String jwt = extractJwtFromRequest(request);
// 验证 JWT
if (jwt != null && jwtTokenProvider.validateToken(jwt)) {
// 获取认证信息
Authentication authentication = jwtTokenProvider.getAuthentication(jwt);
// 设置到 SecurityContext 中
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Could not set user authentication in security context", e);
}
// 继续过滤链
filterChain.doFilter(request, response);
}
private String extractJwtFromRequest(HttpServletRequest request) {
// 从请求头中提取 JWT
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
过滤器的首要任务是拦截请求并从请求中提取 JWT。常见的提取位置包括:
Authorization
头,格式为 Bearer
。?token=
。private String extractJwt(HttpServletRequest request) {
// 尝试从 Authorization 头提取
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
// 尝试从请求参数提取
String tokenParam = request.getParameter("token");
if (tokenParam != null) {
return tokenParam;
}
// 尝试从 Cookie 提取
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("jwt".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
当 JWT 验证通过后,过滤器需要创建 Authentication
对象并设置到 SecurityContextHolder
中:
public Authentication getAuthentication(String token) {
// 从 JWT 中提取用户信息
Claims claims = jwtTokenProvider.getClaims(token);
String username = claims.getSubject();
// 获取用户权限
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get("roles", String.class).split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// 创建并返回认证对象
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
在 Spring Security 配置中,需要将 JWT 过滤器添加到过滤器链中:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
这里使用 addFilterBefore
将 JWT 过滤器添加到 UsernamePasswordAuthenticationFilter
之前,确保在用户名密码认证之前先进行 JWT 认证。
Spring Security 的授权决策基于以下核心组件:
SecurityContextHolder
访问。ROLE_ADMIN
)或权限(如 READ_PRIVILEGE
)的形式存在。Authentication
和请求资源的访问规则做出授权决策。Spring Security 支持基于角色的访问控制,通过 @PreAuthorize
、@PostAuthorize
注解或 XML 配置实现:
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public User getUser(@PathVariable Long id) {
// 获取用户信息
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(@PathVariable Long id) {
// 删除用户
}
}
在 JWT 中,角色信息通常存储在 claims 中:
{
"sub": "1234567890",
"roles": "ROLE_USER,ROLE_ADMIN",
"iat": 1516239022,
"exp": 1516242622
}
除了角色,Spring Security 还支持更细粒度的基于权限的访问控制:
@RestController
@RequestMapping("/api/posts")
public class PostController {
@PostMapping
@PreAuthorize("hasAuthority('CREATE_POST')")
public Post createPost(@RequestBody Post post) {
// 创建帖子
}
@PutMapping("/{id}")
@PreAuthorize("hasAuthority('UPDATE_POST') and @postSecurity.checkOwner(authentication, #id)")
public Post updatePost(@PathVariable Long id, @RequestBody Post post) {
// 更新帖子
}
}
Spring Security 允许定义自定义授权表达式,通过实现 SecurityExpressionRoot
和 SecurityExpressionHandler
:
public class CustomSecurityExpressionRoot extends SecurityExpressionRoot {
private final PostService postService;
public CustomSecurityExpressionRoot(Authentication authentication, PostService postService) {
super(authentication);
this.postService = postService;
}
public boolean isPostOwner(Long postId) {
User user = (User) getPrincipal();
Post post = postService.findById(postId);
return post.getAuthor().getId().equals(user.getId());
}
}
配置自定义表达式处理器:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.expressionHandler(customExpressionHandler())
.antMatchers("/api/posts/**").access("isPostOwner(#postId)")
.anyRequest().authenticated();
}
@Bean
public DefaultWebSecurityExpressionHandler customExpressionHandler() {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new CustomPermissionEvaluator());
return handler;
}
}
JWT 的安全性很大程度上依赖于签名和加密的正确使用:
对称加密(HS256/HS512):使用相同的秘钥进行签名和验证。简单高效,但需要确保秘钥安全。
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
非对称加密(RS256/RS512):使用私钥签名,公钥验证。更安全,适合分布式系统,但性能较低。
// 签名
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.RS256, privateKey)
.compact();
// 验证
Jwts.parser()
.setSigningKey(publicKey)
.parseClaimsJws(token);
为防止 JWT 泄露,应采取以下措施:
为平衡安全性和用户体验,可实现刷新令牌机制:
刷新令牌流程:
@PostMapping("/api/auth/refresh")
public ResponseEntity<?> refreshToken(@RequestBody RefreshTokenRequest request) {
// 验证刷新令牌
if (refreshTokenService.validateRefreshToken(request.getRefreshToken())) {
// 获取用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(
refreshTokenService.getUsernameFromRefreshToken(request.getRefreshToken()));
// 生成新的访问令牌
String accessToken = jwtTokenProvider.generateToken(userDetails);
// 返回新的访问令牌
return ResponseEntity.ok(new TokenResponse(accessToken, request.getRefreshToken()));
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
虽然 JWT 是无状态的,但在某些情况下需要撤销令牌(如用户注销)。常见的解决方案包括:
黑名单机制:将已撤销的令牌存入 Redis 等缓存中,验证时检查令牌是否在黑名单中。
public boolean isTokenBlacklisted(String token) {
return redisTemplate.hasKey("revoked_tokens:" + token);
}
public void revokeToken(String token) {
redisTemplate.opsForValue().set("revoked_tokens:" + token, "revoked",
getTokenExpiration(token), TimeUnit.MILLISECONDS);
}
短有效期令牌:使用较短的有效期,减少需要撤销的场景。
刷新令牌控制:通过控制刷新令牌的有效性,间接控制访问令牌的有效性。
OAuth2 是一种授权框架,用于允许第三方应用访问受保护资源,而无需共享用户凭据。其核心组件包括:
OAuth2 可以使用 JWT 作为访问令牌(JWT Access Token),这种方式称为 JWT Bearer Token。优势包括:
Spring Security OAuth2 提供了对 JWT 的支持,通过 JwtAccessTokenConverter
实现:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123456"); // 签名秘钥
return converter;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter());
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("client")
.secret("secret")
.authorizedGrantTypes("password", "refresh_token")
.scopes("read", "write")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(86400);
}
}
OpenID Connect 是建立在 OAuth2 之上的身份验证协议,使用 JWT 作为 ID Token 传递用户身份信息:
.well-known/openid-configuration
端点提供元数据。Spring Security 提供了对 OpenID Connect 的支持,简化了集成流程:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.and()
.oauth2ResourceServer()
.jwt();
}
}
JWT 的签名和验证操作涉及加密算法,可能成为性能瓶颈。优化建议:
JWT 的大小会影响网络传输性能。优化建议:
sub
而非 subject
)。JWT 过滤器的性能直接影响请求处理速度。优化建议:
在分布式系统中,JWT 的性能优化需要特别注意:
为有效监控 JWT 的使用情况,应设计以下关键指标:
审计日志应记录与 JWT 相关的关键事件:
@Service
public class JwtAuditService {
private final Logger auditLogger = LoggerFactory.getLogger("jwt.audit");
public void logTokenGenerated(String username, String tokenId) {
auditLogger.info("Token generated for user: {}, tokenId: {}", username, tokenId);
}
public void logTokenValidated(String username, String tokenId, boolean success) {
auditLogger.info("Token validated for user: {}, tokenId: {}, success: {}",
username, tokenId, success);
}
public void logTokenRevoked(String username, String tokenId, String reason) {
auditLogger.info("Token revoked for user: {}, tokenId: {}, reason: {}",
username, tokenId, reason);
}
public void logInvalidToken(String token, String reason, String ipAddress) {
auditLogger.warn("Invalid token detected. Reason: {}, IP: {}", reason, ipAddress);
}
}
基于监控数据,建立异常检测机制和告警规则:
定期分析 JWT 相关性能数据,识别瓶颈并优化:
在微服务架构中,身份验证面临以下挑战:
JWT 在微服务中的典型应用模式:
基于 JWT 的微服务安全架构设计: