package com.eduassistant.security.jwt;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;
@Component
public class JwtUtils {
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
@Value("${app.jwt.expiration-ms}")
private int jwtExpirationMs;
@Value("${app.jwt.secret}") // 从配置读取固定密钥
private String jwtSecret;
// 使用固定密钥
private SecretKey getSigningKey() {
// 确保这里的解码方式与密钥生成方式一致
byte[] keyBytes = Base64.getDecoder().decode(jwtSecret);
return Keys.hmacShaKeyFor(keyBytes);
}
public String generateJwtToken(Authentication authentication) {
logger.debug("开始生成JWT令牌...");
String username = authentication.getName();
String role = authentication.getAuthorities().iterator().next().getAuthority();
String token = Jwts.builder()
.setSubject(username)
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
.compact();
logger.info("JWT令牌生成成功 - 用户名: {}, 角色: {}", username, role);
return token;
}
public String getUserNameFromJwtToken(String token) {
logger.debug("从JWT令牌解析用户名...");
try {
String username = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
logger.debug("成功解析用户名: {}", username);
return username;
} catch (Exception e) {
logger.error("解析用户名失败: {}", e.getMessage());
return null;
}
}
public boolean validateJwtToken(String authToken) {
logger.debug("验证JWT令牌: {}", authToken);
try {
Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(authToken);
logger.debug("JWT令牌验证成功");
return true;
} catch (SecurityException e) {
logger.error("无效的JWT签名: {}", e.getMessage());
} catch (MalformedJwtException e) {
logger.error("无效的JWT令牌: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.warn("JWT令牌已过期: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("不支持的JWT令牌: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string为空: {}", e.getMessage());
} catch (Exception e) {
logger.error("未知的JWT验证错误: {}", e.getMessage());
}
return false;
}
}
package com.eduassistant.security.jwt;
import com.eduassistant.security.UserDetailsServiceImpl;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class JwtAuthFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class);
private final JwtUtils jwtUtils;
private final UserDetailsServiceImpl userDetailsService;
public JwtAuthFilter(JwtUtils jwtUtils, UserDetailsServiceImpl userDetailsService) {
this.jwtUtils = jwtUtils;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestURI = request.getRequestURI();
logger.debug("处理请求: {} {}", request.getMethod(), requestURI);
// 跳过认证端点的JWT验证
if (requestURI.startsWith("/api/auth/login") || requestURI.startsWith("/api/auth/register")) {
logger.debug("跳过认证端点的JWT验证: {}", requestURI);
filterChain.doFilter(request, response);
return;
}
try {
String jwt = parseJwt(request);
if (jwt != null) {
logger.debug("找到JWT令牌: {}", jwt);
if (jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
logger.debug("加载用户详情: {}", username);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
logger.info("用户认证成功: {}", username);
}
} else {
logger.warn("JWT令牌验证失败");
sendErrorResponse(response, "无效的令牌", HttpServletResponse.SC_UNAUTHORIZED);
return;
}
} else {
logger.debug("未找到JWT令牌");
sendErrorResponse(response, "需要认证", HttpServletResponse.SC_UNAUTHORIZED);
return;
}
} catch (ExpiredJwtException e) {
logger.error("令牌已过期: {}", e.getMessage());
sendErrorResponse(response, "令牌已过期", HttpServletResponse.SC_UNAUTHORIZED);
return;
} catch (JwtException | IllegalArgumentException e) {
logger.error("令牌验证失败: {}", e.getMessage());
sendErrorResponse(response, "无效的令牌", HttpServletResponse.SC_UNAUTHORIZED);
return;
} catch (Exception e) {
logger.error("认证过程中发生错误: {}", e.getMessage(), e);
sendErrorResponse(response, "服务器错误", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
filterChain.doFilter(request, response);
}
private void sendErrorResponse(HttpServletResponse response, String message, int status) throws IOException {
response.setStatus(status);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"" + message + "\"}");
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuth)) {
logger.trace("Authorization 头: {}", headerAuth);
if (headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
} else {
logger.debug("Authorization 头不以 'Bearer ' 开头");
}
} else {
logger.trace("未找到 Authorization 头");
}
return null;
}
}
package com.eduassistant.config;
import com.eduassistant.security.jwt.JwtAuthFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
private static final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
private final JwtAuthFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
private final StudentAccessFilter studentAccessFilter;
public WebSecurityConfig(JwtAuthFilter jwtAuthFilter,
AuthenticationProvider authenticationProvider,
StudentAccessFilter studentAccessFilter) {
this.jwtAuthFilter = jwtAuthFilter;
this.authenticationProvider = authenticationProvider;
this.studentAccessFilter = studentAccessFilter;
logger.info("WebSecurityConfig 初始化完成");
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/auth/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/error",
"/favicon.ico"
).permitAll()
.requestMatchers("/api/teacher/**").hasRole("TEACHER") // 添加教师端权限控制
.requestMatchers("/api/student/**").hasRole("STUDENT") // 添加学生端权限控制
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.exceptionHandling(exceptions ->
exceptions.authenticationEntryPoint((request, response, authException) -> {
logger.warn("未认证访问: {}", request.getRequestURI());
response.sendError(HttpStatus.UNAUTHORIZED.value(), "需要认证");
})
)
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(studentAccessFilter, JwtAuthFilter.class);
return http.build();
}
// 添加 CORS 配置
@Bean
public CorsConfigurationSource corsConfigurationSource() {
logger.debug("配置CORS策略...");
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("*")); // 允许所有来源(生产环境应限制)
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setExposedHeaders(List.of("Authorization"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
告诉我为什么会一直出现
2025-07-15 16:23:24.459 [http-nio-8080-exec-7] DEBUG c.e.security.jwt.JwtAuthFilter - 处理请求: POST /api/student/learning/ask
2025-07-15 16:23:24.459 [http-nio-8080-exec-7] DEBUG c.e.security.jwt.JwtAuthFilter - 找到JWT令牌: eyJhbGciOiJIUzUxMiJ9.e30.MSn5b9IQindKchEDyEtpnL3XsvHFKmr-cr04XJkvf8ZuIXh0i6J1FL40sG9FJq_oFCzoLALDKZu5F3mEnunO3w
2025-07-15 16:23:24.459 [http-nio-8080-exec-7] DEBUG c.eduassistant.security.jwt.JwtUtils - 验证JWT令牌: eyJhbGciOiJIUzUxMiJ9.e30.MSn5b9IQindKchEDyEtpnL3XsvHFKmr-cr04XJkvf8ZuIXh0i6J1FL40sG9FJq_oFCzoLALDKZu5F3mEnunO3w
2025-07-15 16:23:24.461 [http-nio-8080-exec-7] ERROR c.eduassistant.security.jwt.JwtUtils - 未知的JWT验证错误: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
2025-07-15 16:23:24.461 [http-nio-8080-exec-7] WARN c.e.security.jwt.JwtAuthFilter - JWT令牌验证失败
即使令牌正确还是一直出现这个,错误也一直是这个,其他错误也是出现这个,没有出现对应的错误提示
最新发布