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

Spring Boot+React+Keycloak重定向登录页遇CORS缺失标头错误

解决方案分析:Spring Boot + React + Keycloak 认证架构选择与CORS问题修复

架构可行性说明

你期望的「由Spring Boot完全处理Keycloak认证授权,前端无感知」的架构是可以实现的,但当前遇到的CORS问题源于SPA的AJAX请求特性与传统服务器端重定向认证流程不兼容。

CORS问题根源

当React通过AJAX请求Spring的受保护接口时,Spring返回302重定向到Keycloak,但浏览器的AJAX请求会拦截跨域重定向(React域localhost:3000与Keycloak域localhost:8090跨域),且此时Keycloak的Web Origins配置不会生效——因为Keycloak并未直接接收来自React的请求,而是Spring发起的重定向触发了跨域限制。

两种可行方案

方案一:保留Spring主导认证,调整前端请求逻辑

通过修改前端请求方式和Spring配置,让浏览器通过页面跳转完成认证流程,而非AJAX自动处理重定向:

  1. Spring CORS配置(允许携带凭证)
    确保Spring允许前端域的请求携带Cookie(Session Cookie由Spring在认证成功后设置):

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("http://localhost:3000"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        config.setAllowedHeaders(List.of("*"));
        config.setAllowCredentials(true); // 关键:允许携带Cookie
    
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }
    
    // 确保SecurityFilterChain中使用该CORS配置
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(Customizer.withDefaults())
                .cors(cors -> cors.configurationSource(corsConfigurationSource())) // 指定自定义CORS配置
                .exceptionHandling(customizer -> customizer.accessDeniedHandler(customAccessDeniedHandler()))
                .authorizeHttpRequests(customizer -> customizer
                        .requestMatchers("/oauth2/login", "/oauth2/logout/success", "/oauth2/error").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(configurer -> {
                    configurer.userInfoEndpoint(customizer -> customizer.userAuthoritiesMapper(customGrantedAuthoritiesMapper()));
                    configurer.loginPage("/oauth2/login");
                    configurer.defaultSuccessUrl("http://localhost:3000", true); // 登录成功后跳回前端
                    configurer.failureUrl("/oauth2/error");
                })
                .build();
    }
    
  2. 前端请求处理(拦截重定向并跳转)
    前端AJAX请求时开启凭证携带,并在收到302响应时手动跳转到重定向地址:

    import axios from 'axios';
    
    axios.get('http://localhost:8080/api/test', { withCredentials: true })
      .catch(error => {
        if (error.response?.status === 302) {
          // 手动跳转到Keycloak登录页面
          window.location.href = error.response.headers.location;
        }
      });
    

    登录成功后,Spring会设置Session Cookie,前端后续请求携带该Cookie即可访问受保护接口。

方案二:改为前端处理Keycloak认证,向Spring传递JWT

这是SPA场景的标准最佳实践,避免跨域重定向问题,前端通过Keycloak适配器获取JWT,后端作为资源服务器验证令牌:

  1. Spring配置为资源服务器
    修改application.yml和SecurityFilterChain:

    spring:
      security:
        oauth2:
          resourceserver:
            jwt:
              issuerUri: <你的Keycloak issuer URI>
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(Customizer.withDefaults())
                .cors(Customizer.withDefaults())
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
                .build();
    }
    
  2. 前端集成Keycloak适配器
    使用@react-keycloak/web快速集成:

    npm install @react-keycloak/web keycloak-js
    

    在根组件中配置:

    import { ReactKeycloakProvider } from '@react-keycloak/web';
    import Keycloak from 'keycloak-js';
    
    const keycloak = new Keycloak({
      url: 'http://localhost:8090',
      realm: '<你的Realm名称>',
      clientId: '<你的客户端ID>'
    });
    
    function App() {
      return (
        <ReactKeycloakProvider authClient={keycloak}>
          {/* 你的应用组件 */}
        </ReactKeycloakProvider>
      );
    }
    
    export default App;
    

    请求后端时携带JWT:

    import axios from 'axios';
    import { useKeycloak } from '@react-keycloak/web';
    
    function TestComponent() {
      const { keycloak } = useKeycloak();
    
      const fetchData = async () => {
        const response = await axios.get('http://localhost:8080/api/test', {
          headers: {
            Authorization: `Bearer ${keycloak.token}`
          }
        });
        console.log(response.data);
      };
    
      return <button onClick={fetchData}>获取数据</button>;
    }
    

方案对比

方案优势劣势
Spring主导认证后端完全控制认证逻辑,前端无需了解Keycloak细节依赖Cookie跨域传递,生产环境需HTTPS配合SameSite配置,前端需处理重定向跳转
前端处理认证符合SPA最佳实践,无跨域重定向问题,扩展性强(支持多端)前端需引入认证逻辑,需维护Keycloak适配器配置

两种架构均有成功落地案例,若你坚持后端主导认证,可选择方案一;若追求更适配SPA的架构,方案二更推荐。

内容的提问来源于stack exchange,提问作者grange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 15:07:16