Spring Boot 3 OAuth2对接Angular 16遭CORS跨域拦截问题求助
Postman通过authorization_code授权类型能正常获取access token,但Angular项目中无法完成认证流程,浏览器抛出CORS相关错误。
授权服务器配置
@Configuration @EnableWebSecurity public class AuthorizationServerConfig { private final UserRepository userRepository; private final ClientRepository clientRepository; public AuthorizationServerConfig(UserRepository userRepository, ClientRepository clientRepository) { this.userRepository = userRepository; this.clientRepository = clientRepository; } @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() .issuer("http://localhost:8089") .build(); } @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.cors(Customizer.withDefaults()); http.exceptionHandling(exceptions -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) ) ); http.oauth2ResourceServer(resourceServer -> resourceServer .jwt(Customizer.withDefaults()) ); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); return http.build(); } @Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/.well-known/*").permitAll() .requestMatchers("/login").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()); return http.build(); } @Bean public OAuth2TokenCustomizer<JwtEncodingContext> accessTokenCustomizer() { return context -> { if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) { String username = context.getPrincipal().getName(); UserEntity user = userRepository.findByName(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); Set<String> roles = new HashSet<>(); for (RoleEntity role: user.getRoles()) roles.add(role.getName()); context.getClaims().claim("username", username); context.getClaims().claim("roles", roles); // find branch String branchId = ""; context.getClaims().claim("organization", branchId); } }; } @Bean @Primary public RegisteredClientRepository registeredClientRepository() { List<RegisteredClient> registeredClients = new ArrayList<>(); List<ClientEntity> clientEntities = clientRepository.findAll(); for (ClientEntity clientEntity : clientEntities) registeredClients.add(clientEntity.maptoRegisteredClient()); registeredClients.add(publicClientPKCE()); return new InMemoryRegisteredClientRepository(registeredClients); } @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } private RegisteredClient publicClientPKCE() { return RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("public-client") .clientAuthenticationMethod(ClientAuthenticationMethod.NONE) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("http://127.0.0.1:4200") .scope(OidcScopes.OPENID) .scope(OidcScopes.PROFILE) .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(false) .requireProofKey(true) // PKCE required .build() ) .build(); } } @Configuration public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("http://127.0.0.1:4200"); config.addAllowedOrigin("http://localhost:4200"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); source.registerCorsConfiguration("/**", config); source.registerCorsConfiguration("/.well-known/**", config); return source; } }
Angular 端配置
export const authConfig: AuthConfig = { issuer: 'http://localhost:8089', // 授权服务器URL redirectUri: window.location.origin, // 重定向URI clientId: 'public-client', // Angular应用的客户端ID responseType: 'code', // 授权码流程 scope: 'openid profile', // 请求的权限范围 showDebugInformation: true, useSilentRefresh: false, // 禁用静默刷新令牌 silentRefreshTimeout: 5000, sessionChecksEnabled: true, timeoutFactor: 0.75, useHttpBasicAuth: false, disableAtHashCheck: true, strictDiscoveryDocumentValidation: false }; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { constructor(private oauthService: OAuthService, private router: Router) { this.oauthService.configure(authConfig); } ngOnInit(): void { this.oauthService .loadDiscoveryDocumentAndTryLogin() .then(() => { if (this.oauthService.hasValidAccessToken()) { console.log('登录成功'); this.router.navigate(['test']); } else { this.oauthService.initLoginFlow(); } }) .catch((error) => { console.error('登录过程出错:', error); }); } }
浏览器报错信息
Access to XMLHttpRequest at 'http://localhost:8089/.well-known/openid-configuration' from origin 'http://127.0.0.1:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control...
问题排查与解决
1. 调整过滤器链权限顺序
授权服务器的SecurityFilterChain(Order 1)优先级高于默认过滤器链,导致/.well-known/openid-configuration的预检请求被提前拦截。修改授权服务器过滤器链,直接放行预检请求和发现文档:
@Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); // 新增:放行OPTIONS预检请求和OIDC发现文档 http.authorizeHttpRequests(authorize -> authorize .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers("/.well-known/openid-configuration").permitAll() .anyRequest().authenticated() ); http.cors(Customizer.withDefaults()); // 其余原有配置不变... return http.build(); }
2. 统一CORS配置避免冲突
删除单独的CorsConfig类,直接在Security配置中集成CORS规则,确保配置生效:
http.cors(cors -> cors.configurationSource(request -> { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(Arrays.asList("http://127.0.0.1:4200", "http://localhost:4200")); config.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS", "PUT", "DELETE")); config.setAllowedHeaders(Arrays.asList("*")); config.setAllowCredentials(true); return config; }));
3. 确保重定向URI完全一致
Angular的redirectUri使用window.location.origin会生成http://127.0.0.1:4200,授权服务器中已配置该URI,不要混用localhost和127.0.0.1,保持两端地址完全匹配。
4. 验证发现文档可访问性
直接在浏览器访问http://localhost:8089/.well-known/openid-configuration,确认能正常返回JSON格式的配置内容,无拦截情况。
内容的提问来源于stack exchange,提问作者MohammadReza

