Spring Boot微服务携带有效Access Token仍重定向至Login问题
我有一个使用JHipster生成、集成Keycloak的Spring Boot微服务应用,版本信息如下:
- JHipster - 7.9.3
- Spring Boot - 3.0.2
- Spring Cloud - 2022.0.1
- Keycloak - 20.0.3
手动升级了JHipster生成的Spring Boot版本。
安全配置类代码:
@EnableWebSecurity @EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) @Import(SecurityProblemSupport.class) public class SecurityConfiguration { private final JHipsterProperties jHipsterProperties; @Value("${spring.security.oauth2.client.provider.oidc.issuer-uri}") private String issuerUri; private final SecurityProblemSupport problemSupport; public SecurityConfiguration(JHipsterProperties jHipsterProperties, SecurityProblemSupport problemSupport) { this.problemSupport = problemSupport; this.jHipsterProperties = jHipsterProperties; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .csrf() .disable() .exceptionHandling() .authenticationEntryPoint(problemSupport) .accessDeniedHandler(problemSupport) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests() .requestMatchers("/api/authenticate").permitAll() .requestMatchers("/api/auth-info").permitAll() .requestMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN) .requestMatchers("/api/**").authenticated() .requestMatchers("/management/health").permitAll() .requestMatchers("/management/health/**").permitAll() .requestMatchers("/management/info").permitAll() .requestMatchers("/management/prometheus").permitAll() .requestMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) .and() .oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(authenticationConverter()) .and() .and() .oauth2Client(); return http.build(); // @formatter:on } Converter<Jwt, AbstractAuthenticationToken> authenticationConverter() { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter()); return jwtAuthenticationConverter; } @Bean JwtDecoder jwtDecoder() { NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuerUri); OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(jHipsterProperties.getSecurity().getOauth2().getAudience()); OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri); OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator); jwtDecoder.setJwtValidator(withAudience); return jwtDecoder; } }
安全相关应用属性:
spring: security: oauth2: resource: filter-order: 3 client: provider: oidc: issuer-uri: http://localhost:8080/realms/samplerealm registration: oidc: authorization-grant-type: client_credentials client-id: microservice-client client-secret: <VALID_CLIENT_SECRET> scope: openid, profile, email, offline_access # last one for refresh tokens
应用监听localhost:8087的HTTP请求。
在Keycloak中创建了dev-client客户端,使用Postman测试应用API:通过该客户端从Keycloak获取Access Token,将其以Bearer ----access token----形式放入Authorization请求头,但即使Token有效,API仍将请求重定向至localhost:8087/login并返回登录HTML页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>Please sign in</title> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> <link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous" /> </head> <body> <div class="container"> <h2 class="form-signin-heading">Login with OAuth 2.0</h2> <table class="table table-striped"> </table> </div> </body> </html>
所有HTTP请求(如GET)均出现此问题,尝试使用password授权类型获取的Token测试,结果一致。
核心原因
出现重定向到/login的情况,说明Spring Security没有正确识别请求中的JWT Token,转而触发了OAuth2客户端的登录流程。通常是因为资源服务器的过滤器优先级异常,或Token验证环节不通过导致的。
解决方案步骤
1. 移除过时的filter-order配置
Spring Boot 3.x中,spring.security.oauth2.resource.filter-order已被废弃,该配置会干扰过滤器链的正常优先级。直接删除配置中的以下内容:
# 移除这部分配置 resource: filter-order: 3
2. 确保JWT Token受众(Audience)匹配
检查Keycloak生成的Token中的aud字段,是否包含应用配置中jhipster.security.oauth2.audience的值。如果未配置该属性,需在application.yml中添加:
jhipster: security: oauth2: audience: microservice-client
注意此处audience需与Keycloak客户端的Client ID(即microservice-client)一致,否则AudienceValidator会验证失败,导致Token不被认可。
3. 调整SecurityFilterChain配置
优化filterChain方法,确保资源服务器配置完整且无冗余:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .csrf(csrf -> csrf.disable()) .exceptionHandling(exception -> exception .authenticationEntryPoint(problemSupport) .accessDeniedHandler(problemSupport) ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/authenticate").permitAll() .requestMatchers("/api/auth-info").permitAll() .requestMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN) .requestMatchers("/api/**").authenticated() .requestMatchers("/management/health").permitAll() .requestMatchers("/management/health/**").permitAll() .requestMatchers("/management/info").permitAll() .requestMatchers("/management/prometheus").permitAll() .requestMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .jwtAuthenticationConverter(authenticationConverter()) ) ) .oauth2Client(Customizer.withDefaults()); return http.build(); // @formatter:on }
4. 修复角色权限映射
默认的JwtGrantedAuthorityConverter会从Token的scope字段提取权限,前缀为SCOPE_。若API使用AuthoritiesConstants.ADMIN(通常为ROLE_ADMIN),需自定义转换器映射Keycloak角色:
Converter<Jwt, AbstractAuthenticationToken> authenticationConverter() { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> { Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access"); Collection<String> roles = (Collection<String>) realmAccess.get("roles"); return roles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) .collect(Collectors.toList()); }); return jwtAuthenticationConverter; }
5. 检查Keycloak客户端配置
确保Keycloak中的dev-client客户端:
- 开启
Service Accounts Enabled(使用client_credentials授权时) - 为用户/服务账号分配对应角色(如
admin) - Token签名算法与应用默认配置一致(默认RS256)
验证步骤
- 使用JWT解析工具检查获取的Token,确认
iss、aud、exp、realm_access.roles等字段是否正确。 - 重启应用,用Postman携带正确Token请求API,观察是否不再重定向到登录页面。
内容的提问来源于stack exchange,提问作者user6952691

