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

React+Spring Security跨域登录请求失败问题排查

问题分析与解决方案

核心问题:请求体格式不匹配

Spring Security的formLogin默认期望接收**application/x-www-form-urlencoded类型**的表单数据(即username=xxx&password=xxx格式),但你的前端请求用了application/json格式,并且直接传递JSON对象作为请求体,导致Spring无法正确解析用户名和密码参数,因此认证失败,触发默认的错误跳转/login?error。

解决方案一:修改前端请求格式

将fetch请求改为发送表单格式数据:

fetch('http://localhost:8081/login-process', {
    credentials: 'include',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    method: "POST",
    body: new URLSearchParams({
        "username": "user",
        "password": "d4ddeb71-c76b-43f3-a0af-2f3c0b36bbec"
    })
})
.then(response => response.json())
.then((json) => console.log(json))
.catch(err => console.error(err));

解决方案二:后端配置支持JSON登录

如果希望保持JSON格式请求,可以自定义Spring Security的认证过滤器,解析JSON请求体中的用户名和密码:

  1. 自定义认证过滤器:
public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
            try {
                ObjectMapper mapper = new ObjectMapper();
                LoginRequest loginRequest = mapper.readValue(request.getInputStream(), LoginRequest.class);
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                        loginRequest.getUsername(), loginRequest.getPassword());
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
                throw new AuthenticationServiceException("解析登录请求失败", e);
            }
        }
        return super.attemptAuthentication(request, response);
    }

    // 定义登录请求的DTO
    public static class LoginRequest {
        private String username;
        private String password;

        // getter和setter
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password; }
    }
}
  1. 在Security配置中替换默认过滤器:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
    http.addFilter(corsFilter);
    
    // 替换默认的UsernamePasswordAuthenticationFilter
    JsonUsernamePasswordAuthenticationFilter jsonFilter = new JsonUsernamePasswordAuthenticationFilter();
    jsonFilter.setAuthenticationManager(authenticationManagerBean());
    jsonFilter.setLoginProcessingUrl("/login-process");
    // 配置成功/失败处理器,返回JSON响应(适配前后端分离)
    jsonFilter.setSuccessHandler((request, response, authentication) -> {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"code\":200,\"msg\":\"登录成功\"}");
    });
    jsonFilter.setFailureHandler((request, response, exception) -> {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.getWriter().write("{\"code\":401,\"msg\":\"登录失败:" + exception.getMessage() + "\"}");
    });
    http.addFilterAt(jsonFilter, UsernamePasswordAuthenticationFilter.class);
    
    http.authorizeRequests()
            .antMatchers("/user/**").authenticated()
            .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
            .anyRequest().permitAll();
}

额外优化:适配前后端分离的登录响应

原配置中的defaultSuccessUrl("/", false)会触发页面跳转,不适合前后端分离场景。建议配置自定义的successHandler和failureHandler,返回JSON格式的响应,方便前端处理登录结果(如上述解决方案二中的代码)。

注意事项

确保你的CORS配置正确:

  • 允许的Origin包含http://localhost:3000
  • 允许携带凭证(allowCredentials(true))
  • 允许的HTTP方法包含POST
  • 允许的请求头包含Content-Type

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 17:51:45