You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3 OAuth2对接Angular 16遭CORS跨域拦截问题求助

Spring Boot 3 + Angular 16 认证对接问题

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 06:24:58