本文将基于提供的代码示例,详细介绍如何在一个Java微服务项目中使用Spring Security、JWT和Spring Cloud Gateway来构建一个高效且安全的微服务体系,并整合性能优化措施。
首先,我们从Spring Security配置开始。你已经提供了一个基本的SecurityConfig
类,它负责配置认证和授权管理。以下是基于你的代码示例进行的一些优化和扩展:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/register", "/auth/login").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
.and()
.cors();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
性能优化点:
WebAsyncManagerIntegrationFilter
等,以减少过滤器链长度,提高性能。@EnableGlobalMethodSecurity(prePostEnabled = true)
注解启用方法级别的权限控制,减少不必要的安全性检查。接下来是JWT过滤器的实现,用于在请求到达服务之前验证JWT的有效性。这里我们还将集成缓存策略以提高性能。
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider jwtTokenProvider; // 使用JwtTokenProvider
@Autowired
private StringRedisTemplate redisTemplate;
// Caffeine Cache for token parsing results caching
private final Cache<String, Claims> tokenCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("token");
if (token == null) {
filterChain.doFilter(request, response);
return;
}
Claims claims = getCachedClaims(token);
if (claims == null) {
// 使用JwtTokenProvider的validateToken方法验证token,并获取Claims
if(jwtTokenProvider.validateToken(token)) {
// 假设JwtTokenProvider有一个getClaimsFromToken方法用于获取Claims
claims = jwtTokenProvider.getClaimsFromToken(token);
setCachedClaims(token, claims);
} else {
throw new RuntimeException("无效的JWT令牌");
}
}
String userId = (String) claims.get("userId");
String redisKey = "login:" + userId;
String userStr = redisTemplate.opsForValue().get(redisKey);
LoginUser loginUser = JSONUtil.toBean(userStr, LoginUser.class);
if (loginUser == null) {
throw new RuntimeException("用户未登录");
}
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
private Claims getCachedClaims(String token) {
return tokenCache.getIfPresent(token);
}
private void setCachedClaims(String token, Claims claims) {
tokenCache.put(token, claims);
}
}
性能优化点:
为了让Gateway能够提前验证JWT,我们需要在Gateway层添加相应的过滤器。
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 一个使用Spring Cloud Gateway的路由定位器(RouteLocator),它配置了一个路由规则,
* 用于将匹配特定路径模式的请求转发到指定的服务,并在转发前通过一个过滤器(JwtAuthenticationWebFilter)进行处理。
*/
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("service_route", r -> r.path("/service/**")
.filters(f -> f.filter(new JwtAuthenticationWebFilter()))
.uri("lb://service"))
.build();
}
}
public class JwtAuthenticationWebFilter implements GatewayFilter, Ordered {
private final JwtTokenProvider jwtTokenProvider;
public JwtAuthenticationWebFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
/**
* filter方法是过滤器的核心逻辑,每当有请求经过时都会调用此方法。
* 首先从请求中提取JWT令牌(resolveToken)。
* 如果令牌存在且有效,则使用jwtTokenProvider获取对应的认证信息(Authentication)。
* 将认证信息设置到SecurityContext中,以便后续处理可以访问到当前用户的身份信息。
* 最后,继续处理请求链(chain.filter(exchange)),即将请求传递给下一个过滤器或目标服务
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String token = resolveToken(request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(auth);
SecurityContextHolder.setContext(securityContext);
}
return chain.filter(exchange);
}
private String resolveToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
@Override
public int getOrder() {
return -200; // 设置优先级
}
}
为了提高用户体验,可以实现refresh token机制,允许用户通过refresh token获取新的access token。
@PostMapping("/token/refresh")
public ResponseEntity<?> refreshToken(@RequestBody TokenRefreshRequest request) {
String refreshToken = request.getRefreshToken();
if (jwtTokenProvider.validateToken(refreshToken)) {
String accessToken = jwtTokenProvider.generateAccessTokenFromUsername(
jwtTokenProvider.getUsernameFromToken(refreshToken));
return ResponseEntity.ok(new TokenRefreshResponse(accessToken, refreshToken));
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid refresh token");
}
}
为了及时发现潜在的安全威胁,加入监听并记录Spring Security的安全事件。
@EventListener
public void monitorAuthEvents(AbstractAuthenticationEvent event) {
if(event instanceof AuthenticationSuccessEvent){
log.info("认证成功: {}", event.getAuthentication().getName());
} else if(event instanceof AuthenticationFailureBadCredentialsEvent){
metrics.increment("auth.failure.bad_credentials");
}
}
这些措施包括了禁用不必要的过滤器链、使用Caffeine Cache缓存JWT解析结果等,确保系统既安全又高效。