Angular Keycloak适配器生成的Token无法通过Spring Boot后端授权验证
问题:Angular Keycloak适配器生成的Token无法访问Spring Boot后端
Keycloak基础配置
- 版本:Keycloak 22.0.3,本地默认端口启动
- Angular与Spring Boot使用同一Realm下的不同客户端
Angular客户端配置
- 客户端ID:angular-client
- URIs配置:适配Angular应用地址
- 流程配置:标准授权流程
- 其余为默认配置
Spring Boot客户端配置
- 客户端ID:spring-client
- URIs配置:适配Spring Boot应用地址
- 流程配置:与Angular一致的标准授权流程
- 其余为默认配置
Angular适配器实现
使用keycloak-angular 14.1.0库,初始化配置如下:
Provider代码:
{ provide: APP_INITIALIZER, useFactory: initializeKeycloak, multi: true, deps: [KeycloakService] }
Factory代码:
function initializeKeycloak (keycloak: KeycloakService) { return () => keycloak.init ({ config: { url: 'example.com:8080', realm: 'SpringBootKeycloak', clientId: 'angular-client' }, initOptions: { onLoad: 'check-sso', silentCheckSsoRedirectUri: window.location.origin + '/assets/silent-check-sso.html' } }); }
Angular端登录、登出及Web授权正常,但生成的Access Token无法通过Spring Boot后端验证
Spring Boot安全配置
application.properties配置
spring.security.oauth2.client.registration.keycloak.client-id=spring-client spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code spring.security.oauth2.client.registration.keycloak.scope=openid spring.security.oauth2.client.provider.keycloak.issuer-uri=example.com:8080/realms/SpringBootKeycloak spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username spring.security.oauth2.resourceserver.jwt.issuer-uri=example.com:8080/realms/SpringBootKeycloak spring.security.oauth2.resourceserver.jwt.jwk-set-uri=example.com:8880/auth/realms/SpringBootKeycloak/protocol/openid-connect/certs
SecurityConfig代码
@Configuration @EnableWebSecurity @EnableMethodSecurity class SecurityConfig { SecurityConfig() { } @Bean protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } @Order(1) @Bean public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()); http.oauth2ResourceServer((oauth2) -> oauth2 .jwt(Customizer.withDefaults())); return http.build(); } @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { return http.getSharedObject(AuthenticationManagerBuilder.class) .build(); } }
有效Token测试示例
通过密码模式获取的Token可正常访问后端:
获取Token
POST example.com:8080/realms/SpringBootKeycloak/protocol/openid-connect/token Content-Type: application/x-www-form-urlencoded client_id=spring-client&username=user&password=password&grant_type=password
使用Token请求接口
GET example.com:8081/api/resource Authorization: Bearer {{token}}
错误信息
浏览器返回
401 Unauthorized x-frame-options DENY
Spring Boot控制台栈跟踪
java.lang.StackOverflowError: null at org.springframework.aop.support.AopUtils.isEqualsMethod(AopUtils.java:151) ~[spring-aop-6.0.9.jar:6.0.9] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:175) ~[spring-aop-6.0.9.jar:6.0.9] at jdk.proxy2/jdk.proxy2.$Proxy175.authenticate(Unknown Source) ~[na:na] at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201) ~[spring-security-core-6.1.0.jar:6.1.0]
已尝试无效方案
手动配置JwtDecoder指定RS256签名算法,未解决问题:
@Configuration @EnableWebSecurity @EnableMethodSecurity public class WebSecurityConfig { @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") private String jwkSetUri; @Bean public JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).jwsAlgorithm(SignatureAlgorithm.RS256).build(); } }
预期目标
使用Angular适配器生成的Access Token完成Spring Boot后端REST接口的授权验证
解决建议
检查Token受众与权限
解析Angular生成的Token,确认aud字段包含spring-client,或在Keycloak客户端配置中开启"Full Scope Allowed",确保Token拥有访问后端的权限。修正JWK-Set URI端口错误
当前配置中jwk-set-uri端口为8880,与Keycloak默认端口8080不符,修改为:spring.security.oauth2.resourceserver.jwt.jwk-set-uri=example.com:8080/realms/SpringBootKeycloak/protocol/openid-connect/certs移除冗余AuthenticationManager Bean
资源服务器模式下无需手动定义AuthenticationManager,该Bean会引发循环调用导致栈溢出,删除以下代码:@Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { return http.getSharedObject(AuthenticationManagerBuilder.class) .build(); }确保请求正确携带Token
实现Angular HTTP拦截器,自动为请求添加Token头:@Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private keycloakService: KeycloakService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return from(this.keycloakService.getToken()).pipe( switchMap(token => { if (token) { request = request.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } return next.handle(request); }) ); } }配置后端CORS允许跨域
Spring Boot添加CORS配置Bean,允许Angular应用跨域请求:@Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://example.com:4200"); // 替换为Angular实际地址 config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); }
内容的提问来源于stack exchange,提问作者Slawomir
相关产品推荐
相关产品推荐

