凭证在Spring端可用但Angular端登录出现401错误的解决方法
问题背景
我在Angular应用中尝试登录时,总是触发401 Unauthorized错误,但使用完全相同的凭证,直接在Spring端(比如Postman调用接口)却能正常完成登录验证。跟着教程开发后尝试了多种方案都没解决,希望能找到问题根源。
相关信息收集
请求头详情
Request URL: http://localhost:8080/ Request Method: GET Status Code: 401 Remote Address: [::1]:8080 Referrer Policy: strict-origin-when-cross-origin Access-Control-Allow-Headers: access_token, authorization, content-type Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS, DELETE Access-Control-Allow-Origin: * Access-Control-Max-Age: 4200 Cache-Control: no-cache, no-store, max-age=0, must-revalidate Connection: keep-alive Content-Length: 0 Date: Sat, 18 Sep 2021 16:40:45 GMT Expires: 0 Keep-Alive: timeout=60 Pragma: no-cache Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers WWW-Authenticate: Basic realm="Realm" WWW-Authenticate: Basic realm="Realm" X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block
Spring Security配置代码
@Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CorsConfiguration myCorsFilter; @Override public void configure(WebSecurity web) throws Exception { // 允许无需认证即可访问登录API web.ignoring().antMatchers("/login").antMatchers(HttpMethod.OPTIONS, "/**"); // 允许OPTIONS请求类型 } //CORS配置 @Override protected void configure(HttpSecurity http) throws Exception { /* http.cors().and().csrf(). disable() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**") .permitAll() .anyRequest() .fullyAuthenticated() .and() .httpBasic();*/ http.addFilterBefore(myCorsFilter, ChannelProcessingFilter.class); http.cors(); http.csrf().disable(); http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and() .httpBasic(); } protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.inMemoryAuthentication() .withUser("dave") .password("{noop}dave").roles("USER"); } }
自定义CorsFilter代码
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class CorsConfiguration implements Filter { /** * 用于HTTP请求和响应的CORS过滤器 */ public void CORSFilter() { } /** * 对每个HTTP请求执行过滤操作 */ @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request = (HttpServletRequest) req; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "4200"); response.setHeader("Access-Control-Allow-Headers", "access_token, authorization, content-type"); if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(req, res); } } /** * 销毁方法 */ @Override public void destroy() { } /** * 初始化CORS过滤器 */ @Override public void init(FilterConfig arg0) throws ServletException { } }
排查与解决步骤
我梳理了几个核心问题点,按优先级排查:
1. 请求路径与方法完全错误
看你的请求头,Angular发送的是GET请求到根路径/,但你的Spring Security明明配置了web.ignoring().antMatchers("/login")——也就是只有/login接口不需要认证,根路径/属于/**的范围,会被要求完全认证,自然返回401。
- 修复:立即修正Angular端的登录请求,确保请求URL是
http://localhost:8080/login,并且使用POST方法(登录请求用GET不安全,Spring Security默认的登录处理也针对POST)。
2. 配置中的转义引号导致路径匹配失效
你的配置代码里所有路径用的是"/login"这种HTML转义的引号,这会导致Spring无法正确识别路径,/login接口根本没被加入到忽略认证的列表里,所以登录请求也会被拦截返回401。
- 修复:把所有
"替换成正常的双引号",比如web.ignoring().antMatchers("/login")。
3. CORS配置冲突
你同时启用了Spring Security自带的http.cors()和自定义的CorsConfiguration Filter,这两种配置会互相干扰,可能导致请求头异常或者拦截顺序错误。
- 修复:二选一即可,推荐用Spring Security原生的CORS配置更稳妥:
- 删除自定义的
CorsConfigurationFilter类 - 在Spring Security配置里添加CORS配置源:
@Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("POST", "PUT", "GET", "OPTIONS", "DELETE")); configuration.setAllowedHeaders(Arrays.asList("access_token", "authorization", "content-type")); configuration.setMaxAge(4200L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } - 修改
configure(HttpSecurity)方法中的CORS配置:http.cors().configurationSource(corsConfigurationSource()) .and().csrf().disable() .authorizeRequests() .antMatchers("/login").permitAll() // 直接在这里放行登录接口,比WebSecurity更清晰 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .anyRequest().fullyAuthenticated() .and().httpBasic();
- 删除自定义的
4. Basic认证的凭证传递方式
如果你的登录是用Basic认证,Angular端需要把用户名和密码用Base64编码后放在Authorization头里,格式是Authorization: Basic dGFzOmRhc2=(这里dGFzOmRhc2=是tas:das的Base64编码)。不过因为你已经放行/login接口,登录请求不需要带这个头,只需要把用户名密码放在POST请求的体里即可。
内容的提问来源于stack exchange,提问作者InvasivePlague

