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

Spring Security配置defaultSuccessUrl外部URL引发CORS错误

问题描述

后端基于Spring Boot + Spring Security运行在localhost:8080,前端React运行在localhost:9000,二者通过HTTP请求通信。已配置Spring Security的CORS规则,前端对后端的GET、POST等请求均正常,但当Spring Security认证成功后重定向到外部URL(如前端首页localhost:9000)时出现CORS错误,重定向到后端本地URL(如/或/bookmarks)则无问题。需求是实现用户在React自定义登录页认证成功后,跳转到React首页。

JS登录请求

前端发送给Spring的模拟登录fetch请求:

const login = async () => {
  const response = await fetch("http://localhost:8080/login", {
    method: "POST",
    credentials: "include",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
      username: "admin",
      password: "admin",
    }),
  });
  return response;
};

login()
  .then((response) => console.log(response))
  .catch((err) => console.log(err));

HTTP请求/响应详情

从localhost:8080重定向后向localhost:9000发送的请求:

-- 通用信息 --
请求URL: http://localhost:9000/
请求方法: GET
状态码: 304 Not Modified
远程地址: :80

-- 响应头 --
Accept-Ranges: bytes
Connection: keep-alive
Date: Tue, 02 May 2023 20:29:24 GMT
ETag: W/"15d-ORaJAU+aH9t5HZ/clgri7Q6Qsaw"
X-Powered-By: Express

-- 请求头 --
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=F37609EF95D9A21AA777DD208F8BF3FF
Host: localhost:9000
If-None-Match: W/"15d-ORaJAU+aH9t5HZ/clgri7Q6Qsaw"
Origin: null
Referer: http://localhost:9000/
sec-ch-ua: "Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36

浏览器控制台错误

Access to fetch at 'localhost:9000/' (redirected from 'localhost:8080/login') from origin 'localhost:9000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Spring Security配置

@Configuration
public class SecurityConfig {
        @Bean
        public SecurityFilterChain configuration(HttpSecurity http) throws Exception {
                return http
                                .csrf(csrf -> csrf.disable())
                                .cors(Customizer.withDefaults())
                                // 请求授权规则
                                .authorizeHttpRequests(authorize -> authorize
                                                .requestMatchers("/login").permitAll()
                                                .anyRequest().authenticated())
                                // 登录页面配置
                                .formLogin(form -> form
                                                .loginPage("http://localhost:9000/auth/login")
                                                .loginProcessingUrl("/login")
                                                .defaultSuccessUrl("http://localhost:9000", true)
                                                .permitAll())

                                .httpBasic(Customizer.withDefaults())
                                .build();
        }
}

CORS配置

@Configuration
public class CorsConfig {
    @Bean
    CorsConfiguration corsConfiguration() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:9000/"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("Content-Type", "X-XSRF-TOKEN", "Authorization"));
        configuration.setAllowCredentials(true);
        return configuration;
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration());
        return source;
    }
}

已尝试的方法与推测

  • 将CORS配置中的允许源、方法、头设为*,无效
  • 直接从React应用发送GET请求到localhost:9000无错误
  • 问题可能源于认证后响应的Origin头为null,导致浏览器判定跨域
  • defaultSuccessUrl设为后端本地URL无问题,推测默认认证成功处理器不支持外部URL,需自定义
  • 前端使用React Router,排除路由问题
  • 访问后端受保护端点时可成功重定向到登录页,无错误

解决方案

核心原因

前端通过fetch发送登录请求时,后端返回的重定向响应会被浏览器CORS机制拦截:跨域重定向导致请求的Origin为null,而前端服务器未返回Access-Control-Allow-Origin头,触发CORS错误。

解决步骤

1. 自定义认证成功处理器,返回JSON响应而非重定向

创建自定义处理器,认证成功时返回包含跳转URL的JSON,由前端控制页面跳转:

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        // 返回前端跳转地址
        String json = "{\"redirectUrl\":\"http://localhost:9000\"}";
        response.getWriter().write(json);
    }
}

2. 更新Spring Security配置,替换默认成功处理器

修改SecurityConfig,用自定义处理器替代defaultSuccessUrl:

@Configuration
public class SecurityConfig {

    @Autowired
    private CustomAuthenticationSuccessHandler successHandler;

    @Bean
    public SecurityFilterChain configuration(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf.disable())
                .cors(Customizer.withDefaults())
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/login").permitAll()
                        .anyRequest().authenticated())
                .formLogin(form -> form
                        .loginPage("http://localhost:9000/auth/login")
                        .loginProcessingUrl("/login")
                        .successHandler(successHandler) // 使用自定义处理器
                        .permitAll())
                .httpBasic(Customizer.withDefaults())
                .build();
    }
}

3. 前端修改登录逻辑,手动处理页面跳转

更新fetch请求逻辑,拿到后端返回的跳转URL后,通过window.location或React Router跳转:

const login = async () => {
  try {
    const response = await fetch("http://localhost:8080/login", {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({
        username: "admin",
        password: "admin",
      }),
    });
    if (response.ok) {
      const data = await response.json();
      // 跳转到前端首页
      window.location.href = data.redirectUrl;
      // 使用React Router可替换为:navigate(data.redirectUrl);
    }
  } catch (err) {
    console.log(err);
  }
};

login();

4. 规范CORS配置的允许源(可选)

移除允许源URL末尾的斜杠,避免匹配问题:

configuration.setAllowedOrigins(Arrays.asList("http://localhost:9000"));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 08:12:49