实现JWT身份认证机制,新增JWT认证过滤器和服务,更新链接生成接口以支持JWT验证,删除旧的用户控制器,添加JWT认证文档,增强错误处理和日志记录。
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package com.gameplatform.server.security;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationFilter implements WebFilter {
|
||||
private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
|
||||
|
||||
private final JwtService jwtService;
|
||||
|
||||
public JwtAuthenticationFilter(JwtService jwtService) {
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
String path = exchange.getRequest().getPath().value();
|
||||
String method = exchange.getRequest().getMethod().name();
|
||||
|
||||
log.info("=== JWT过滤器开始处理请求 ===");
|
||||
log.info("请求路径: {}, 请求方法: {}", path, method);
|
||||
|
||||
String authHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
log.info("Authorization头: {}", authHeader != null ? authHeader.substring(0, Math.min(20, authHeader.length())) + "..." : "null");
|
||||
|
||||
if (authHeader == null) {
|
||||
log.info("未找到Authorization头,跳过JWT认证,继续处理请求");
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
if (!authHeader.startsWith("Bearer ")) {
|
||||
log.warn("Authorization头格式无效,期望格式: 'Bearer <token>',实际: {}", authHeader);
|
||||
log.info("跳过JWT认证,继续处理请求");
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
String token = authHeader.substring(7);
|
||||
log.info("开始处理JWT token,token长度: {}", token.length());
|
||||
log.debug("JWT token内容: {}", token);
|
||||
|
||||
try {
|
||||
log.info("开始解析JWT token");
|
||||
Claims claims = jwtService.parse(token);
|
||||
log.info("JWT token解析成功");
|
||||
|
||||
Long userId = claims.get("userId", Long.class);
|
||||
String userType = claims.get("userType", String.class);
|
||||
String username = claims.get("username", String.class);
|
||||
String subject = claims.getSubject();
|
||||
|
||||
log.info("JWT claims解析结果:");
|
||||
log.info(" - subject: {}", subject);
|
||||
log.info(" - userId: {}", userId);
|
||||
log.info(" - userType: {}", userType);
|
||||
log.info(" - username: {}", username);
|
||||
log.info(" - issuedAt: {}", claims.getIssuedAt());
|
||||
log.info(" - expiration: {}", claims.getExpiration());
|
||||
log.info(" - 所有claims键: {}", claims.keySet());
|
||||
|
||||
if (userId == null || userType == null || username == null) {
|
||||
log.warn("JWT token中缺少必要的claims信息");
|
||||
log.warn(" - userId: {}", userId);
|
||||
log.warn(" - userType: {}", userType);
|
||||
log.warn(" - username: {}", username);
|
||||
log.info("跳过JWT认证,继续处理请求");
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 创建Spring Security的Authentication对象
|
||||
log.info("开始创建Authentication对象");
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||
username, // principal
|
||||
null, // credentials (不需要密码)
|
||||
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + userType.toUpperCase()))
|
||||
);
|
||||
|
||||
// 设置认证详情,包含JWT claims信息
|
||||
authentication.setDetails(claims);
|
||||
|
||||
log.info("Authentication对象创建成功: {}", authentication);
|
||||
log.info("Authentication是否已认证: {}", authentication.isAuthenticated());
|
||||
log.info("Authentication的principal: {}", authentication.getPrincipal());
|
||||
log.info("Authentication的authorities: {}", authentication.getAuthorities());
|
||||
log.info("Authentication的details: {}", authentication.getDetails());
|
||||
|
||||
// 创建安全上下文并设置认证信息
|
||||
SecurityContext securityContext = new SecurityContextImpl(authentication);
|
||||
|
||||
log.info("=== JWT token验证成功,设置安全上下文 ===");
|
||||
log.info("用户: {} (ID: {}, 类型: {}) 在路径: {} 上JWT验证通过",
|
||||
username, userId, userType, path);
|
||||
|
||||
// 将安全上下文设置到ReactiveSecurityContextHolder中
|
||||
return chain.filter(exchange)
|
||||
.contextWrite(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("=== JWT token验证失败 ===");
|
||||
log.error("请求路径: {}", path);
|
||||
log.error("Authorization头: {}", authHeader);
|
||||
log.error("错误详情: {}", e.getMessage(), e);
|
||||
log.info("JWT认证失败,继续处理请求(未认证状态)");
|
||||
}
|
||||
|
||||
log.info("JWT过滤器处理完成,继续处理请求");
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,14 @@ public class JwtService {
|
||||
byte[] bytes = secret.length() < 32 ? (secret + "_pad_to_32_chars_secret_key_value").getBytes() : secret.getBytes();
|
||||
this.key = Keys.hmacShaKeyFor(bytes);
|
||||
this.accessTokenMinutes = accessTokenMinutes;
|
||||
log.info("JWT服务初始化完成 - 密钥长度: {} bytes, token过期时间: {} 分钟", bytes.length, accessTokenMinutes);
|
||||
}
|
||||
|
||||
public String generateToken(String subject, String userType, Long userId, String username, Map<String, Object> extra) {
|
||||
log.info("=== 开始生成JWT token ===");
|
||||
log.info("生成参数: subject={}, userType={}, userId={}, username={}, extra={}",
|
||||
subject, userType, userId, username, extra);
|
||||
|
||||
Instant now = Instant.now();
|
||||
var builder = Jwts.builder()
|
||||
.setSubject(subject)
|
||||
@@ -41,14 +46,44 @@ public class JwtService {
|
||||
if (extra != null) {
|
||||
extra.forEach(builder::claim);
|
||||
}
|
||||
|
||||
log.info("JWT builder配置完成,开始签名");
|
||||
String token = builder.signWith(key, SignatureAlgorithm.HS256).compact();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("JWT generated subject={}, userType={}, userId={}, username={} expInMin={}", subject, userType, userId, username, accessTokenMinutes);
|
||||
}
|
||||
|
||||
log.info("=== JWT token生成成功 ===");
|
||||
log.info("token长度: {} 字符", token.length());
|
||||
log.info("过期时间: {} 分钟后", accessTokenMinutes);
|
||||
log.info("完整token: {}", token);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
public io.jsonwebtoken.Claims parse(String token) {
|
||||
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
|
||||
log.info("=== 开始解析JWT token ===");
|
||||
log.info("token长度: {} 字符", token.length());
|
||||
log.debug("完整token: {}", token);
|
||||
|
||||
try {
|
||||
log.info("开始验证JWT签名");
|
||||
io.jsonwebtoken.Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
log.info("=== JWT token解析成功 ===");
|
||||
log.info("解析结果:");
|
||||
log.info(" - subject: {}", claims.getSubject());
|
||||
log.info(" - issuedAt: {}", claims.getIssuedAt());
|
||||
log.info(" - expiration: {}", claims.getExpiration());
|
||||
log.info(" - 所有claims: {}", claims);
|
||||
|
||||
return claims;
|
||||
} catch (Exception e) {
|
||||
log.error("=== JWT token解析失败 ===");
|
||||
log.error("token: {}", token);
|
||||
log.error("错误详情: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,107 @@
|
||||
package com.gameplatform.server.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
|
||||
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class SecurityConfig {
|
||||
private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);
|
||||
|
||||
@Autowired
|
||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
return http
|
||||
log.info("=== 开始配置Spring Security安全链 ===");
|
||||
|
||||
SecurityWebFilterChain chain = http
|
||||
.csrf(ServerHttpSecurity.CsrfSpec::disable)
|
||||
.cors(cors -> {})
|
||||
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
|
||||
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
|
||||
.securityContextRepository(securityContextRepository())
|
||||
.authenticationManager(authenticationManager())
|
||||
.authorizeExchange(ex -> ex
|
||||
.pathMatchers("/actuator/**").permitAll()
|
||||
.pathMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
|
||||
.pathMatchers(HttpMethod.GET, "/api/auth/me").permitAll()
|
||||
.pathMatchers("/api/link/**").authenticated() // 链接接口需要认证
|
||||
.anyExchange().permitAll() // 其他接口后续再收紧
|
||||
)
|
||||
// 关键:将JWT过滤器集成到Security过滤链中,放在AUTHENTICATION位置
|
||||
.addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
|
||||
.build();
|
||||
|
||||
log.info("=== Spring Security安全链配置完成 ===");
|
||||
log.info("安全配置详情:");
|
||||
log.info(" - CSRF: 已禁用");
|
||||
log.info(" - CORS: 已启用");
|
||||
log.info(" - HTTP Basic: 已禁用");
|
||||
log.info(" - Form Login: 已禁用");
|
||||
log.info(" - JWT过滤器: 已集成到Security链中 (AUTHENTICATION位置)");
|
||||
log.info(" - 路径权限配置:");
|
||||
log.info(" * /actuator/** -> 允许所有");
|
||||
log.info(" * POST /api/auth/login -> 允许所有");
|
||||
log.info(" * GET /api/auth/me -> 允许所有");
|
||||
log.info(" * /api/link/** -> 需要认证");
|
||||
log.info(" * 其他路径 -> 允许所有");
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReactiveAuthenticationManager authenticationManager() {
|
||||
log.info("创建JWT认证管理器");
|
||||
return authentication -> {
|
||||
log.info("=== JWT认证管理器开始处理认证 ===");
|
||||
log.info("认证对象: {}", authentication);
|
||||
|
||||
if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
||||
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
|
||||
log.info("处理UsernamePasswordAuthenticationToken认证");
|
||||
log.info("Principal: {}", auth.getPrincipal());
|
||||
log.info("Credentials: {}", auth.getCredentials());
|
||||
log.info("Authorities: {}", auth.getAuthorities());
|
||||
log.info("Details: {}", auth.getDetails());
|
||||
log.info("是否已认证: {}", auth.isAuthenticated());
|
||||
|
||||
// 如果已经通过JWT过滤器认证,直接返回
|
||||
if (auth.isAuthenticated()) {
|
||||
log.info("认证对象已经通过验证,返回成功");
|
||||
return Mono.just(auth);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("认证对象未通过验证,返回失败");
|
||||
return Mono.empty();
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerSecurityContextRepository securityContextRepository() {
|
||||
log.info("创建安全上下文仓库");
|
||||
return new WebSessionServerSecurityContextRepository();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
log.info("创建BCrypt密码编码器");
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user