Spring Security结合Angular登录页面无法正常工作问题排查
Spring+Angular登录功能异常问题解决
问题概述
开发Spring+Angular应用,采用Spring Security实现登录功能。Postman发送POST请求到localhost:8080/auth,携带form-data格式的username和password可正常登录,但Angular自定义登录页面请求时出现以下问题:
- POST请求返回302状态码,Spring返回
login?error页面并提示"Invalid credentials" - GET请求返回404,Postman发送GET请求也得到相同结果
- Postman认证成功后可正常发送其他请求,但Angular中认证后其他请求仍返回302并跳转到Spring Security登录页
Angular登录代码
loginUser(username: string | undefined, password: string | undefined) { const headers = new HttpHeaders(username ? { authorization : 'Basic ' + btoa(username + ':' + password) } : {}); console.log(username) console.log(password) this.http.post(this.uri + "/auth", {headers}).subscribe(response => { console.log(response); }); }
Spring WebSecurityConfig配置
@Configuration @AllArgsConstructor @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private final AppUserService userService; private final BCryptPasswordEncoder bCryptPasswordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues()) .and() .csrf().disable() //to delete .authorizeRequests() .antMatchers("/v*/registration/**", "/login", "/auth") .permitAll() .anyRequest() .authenticated() .and() .formLogin() .loginProcessingUrl("/auth") .usernameParameter("username") .passwordParameter("password") .permitAll() .and().logout().permitAll().and() .httpBasic() ; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(daoAuthenticationProvider()); } @Bean public DaoAuthenticationProvider daoAuthenticationProvider(){ DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setPasswordEncoder(bCryptPasswordEncoder); provider.setUserDetailsService(userService); return provider; } }
问题根源
- Angular请求格式错误:
Spring Security的formLogin配置预期接收form-data/x-www-form-urlencoded格式的username和password参数,但你的代码使用了Basic Auth请求头,且POST请求体错误地传递了{headers}对象,未将用户名密码正确发送给后端。 - Spring配置冲突与重定向问题:
同时开启formLogin和httpBasic导致认证逻辑冲突;formLogin默认在认证失败或未认证时重定向到登录页面(302状态码),不符合前后端分离场景,Angular无法处理该重定向,导致后续请求仍处于未认证状态。
解决方案
1. 修改Angular登录代码,发送正确表单请求
调整请求格式,发送x-www-form-urlencoded或form-data格式的表单数据,匹配Spring Security的formLogin预期:
import { HttpHeaders, HttpClient } from '@angular/common/http'; loginUser(username: string | undefined, password: string | undefined) { if (!username || !password) return; // 构造form-data格式数据 const formData = new FormData(); formData.append('username', username); formData.append('password', password); this.http.post(this.uri + "/auth", formData).subscribe({ next: (response) => { console.log('登录成功', response); // 可在此保存认证状态至localStorage等 }, error: (err) => { console.error('登录失败', err); } }); }
2. 优化Spring Security配置,适配前后端分离
修改配置,关闭重定向逻辑,改为返回JSON响应,同时明确认证规则:
import org.springframework.http.HttpStatus; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @Configuration @AllArgsConstructor @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private final AppUserService userService; private final BCryptPasswordEncoder bCryptPasswordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(corsFilter(), SessionManagementFilter.class) .csrf().disable() .authorizeRequests() .antMatchers("/v*/registration/**", "/auth") .permitAll() .anyRequest() .authenticated() .and() .formLogin() .loginProcessingUrl("/auth") .usernameParameter("username") .passwordParameter("password") // 登录成功返回JSON .successHandler((request, response, authentication) -> { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"status\":\"success\",\"message\":\"登录成功\"}"); }) // 登录失败返回JSON .failureHandler((request, response, exception) -> { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"status\":\"error\",\"message\":\"无效的用户名或密码\"}"); }) .permitAll() .and() .logout() .logoutSuccessHandler((request, response, authentication) -> { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"status\":\"success\",\"message\":\"退出成功\"}"); }) .permitAll() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(daoAuthenticationProvider()); } @Bean public DaoAuthenticationProvider daoAuthenticationProvider(){ DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setPasswordEncoder(bCryptPasswordEncoder); provider.setUserDetailsService(userService); return provider; } // 精细配置CORS,替代默认规则 @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOriginPattern("*"); // 生产环境建议指定具体域名 config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }
3. 确保后续请求携带认证信息
登录成功后,Spring Security会创建会话,Angular后续请求需开启withCredentials以携带会话Cookie:
// 全局配置HttpClient拦截器,自动添加withCredentials import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http'; @Injectable() export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler) { const authReq = req.clone({ withCredentials: true }); return next.handle(authReq); } } // 在app.module.ts的providers中注册拦截器 { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
内容的提问来源于stack exchange,提问作者nika932
相关产品推荐
相关产品推荐

