Angular 14发送POST请求时出现401状态码问题排查
Spring Boot Basic Auth在Postman正常,Angular请求返回401问题解决
问题场景
启用安全机制的Spring Boot后端,向/snippetcollection发送POST请求时,Postman配置Basic Auth凭证可获得201创建成功响应,但Angular代码发起请求时始终返回401未授权错误。
相关代码
Spring Boot安全配置
@Configuration public class SecurityConfig { @Bean public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http.cors().configurationSource(new CorsConfigurationSource() { @Override public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(Collections.singletonList("http://localhost:4200")); config.setAllowedMethods(Collections.singletonList("*")); config.setAllowCredentials(true); config.setAllowedHeaders(Collections.singletonList("*")); config.setMaxAge(3600L); return config; } }).and().csrf().ignoringAntMatchers("/register", "/snippetcollection") .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and().authorizeRequests() .antMatchers("/snippetcollection").hasAnyRole(Role.FREE_USER.name(), Role.PRO_USER.name()) .antMatchers("/dashboard").hasAnyRole(Role.FREE_USER.name(), Role.PRO_USER.name()) .antMatchers("/profile").hasAnyRole(Role.FREE_USER.name(), Role.PRO_USER.name()) .antMatchers("/tags").hasAnyRole(Role.FREE_USER.name(), Role.PRO_USER.name()) .antMatchers("/shared").hasRole(Role.PRO_USER.name()) .antMatchers("/settings").hasAnyRole(Role.FREE_USER.name(), Role.PRO_USER.name()) .antMatchers("/user").authenticated() .antMatchers("/home", "/documentation", "/pricing", "/register", "/share", "/login").permitAll() .and().httpBasic() .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
Angular代码
HTTP拦截器
@Injectable() export class AjaxhttpInterceptor implements HttpInterceptor { user = new User(); constructor(private router: Router) {} intercept(req: HttpRequest<any>, next: HttpHandler) { let httpHeaders = new HttpHeaders(); if (sessionStorage.getItem('userdetails')) { this.user = JSON.parse(sessionStorage.getItem('userdetails')!); } if (this.user && this.user.password && this.user.email) { httpHeaders = httpHeaders.append( 'Authorization', 'Basic ' + window.btoa(this.user.email + ':' + this.user.password) ); } let xsrf = sessionStorage.getItem('XSRF-TOKEN'); if (xsrf) { httpHeaders = httpHeaders.append('X-XSRF-TOKEN', xsrf); } httpHeaders = httpHeaders.append('X-Requested-With', 'XMLHttpRequest'); const xhr = req.clone({ headers: httpHeaders, }); return next.handle(xhr).pipe( tap((err: any) => { if (err instanceof HttpErrorResponse) { if (err.status !== 401) { return; } this.router.navigate(['/dashboard']); } }) ); } }
服务类
export class DashboardService { constructor(private http: HttpClient) {} createSnippetCollection(snippetcollection: SnippetCollection) { return this.http.post( environment.rooturl + '/snippetcollection', snippetcollection, { observe: 'response', withCredentials: true } ); } }
组件调用
this.dashboardService .createSnippetCollection({ title: 'noasdsadasde export link', description: 'Cerasdasdvus Lorem ipsom dolor sadasdamet', programmingLanguage: 'nasdasdde', }) .subscribe((responseData) => { console.log(responseData); });
问题排查与解决
1. 密码存储与凭证生成问题
Angular中用window.btoa(user.email + ':' + user.password)生成Basic Auth凭证,要求password是明文密码。但Spring Boot用BCryptPasswordEncoder加密存储密码,若sessionStorage中存的是加密后的密码,生成的Authorization头必然无效。
解决方式:
- 登录接口返回用户信息时,不要返回加密密码,仅保留email等标识
- 登录成功后直接生成完整的Auth凭证并存入sessionStorage:
// 登录成功时执行 const authToken = 'Basic ' + window.btoa(email + ':' + plainPassword); sessionStorage.setItem('authToken', authToken); - 拦截器中直接读取该凭证添加到请求头:
const authToken = sessionStorage.getItem('authToken'); if (authToken) { httpHeaders = httpHeaders.append('Authorization', authToken); }
2. 拦截器逻辑漏洞
原拦截器仅当user对象同时存在password和email时才添加Auth头,若sessionStorage中的userdetails未存储password(比如登录后只存了用户基本信息),则不会携带凭证。
修改拦截器:移除对user.password的依赖,直接使用预先生成的authToken(如上一步)。
3. 角色权限匹配问题
Spring Security的hasAnyRole方法会自动给角色名添加ROLE_前缀。例如Role.FREE_USER.name()若为FREE_USER,则系统实际校验的是ROLE_FREE_USER权限。
解决方式:
- 方式一:改用
hasAnyAuthority(不会自动添加前缀):.antMatchers("/snippetcollection").hasAnyAuthority(Role.FREE_USER.name(), Role.PRO_USER.name()) - 方式二:确保用户实体中存储的角色带有
ROLE_前缀(如ROLE_FREE_USER)。
4. 调试验证步骤
- 打开浏览器开发者工具的Network面板,查看POST请求的Request Headers,确认是否包含正确的
Authorization: Basic xxx头 - 查看Response Headers中的
WWW-Authenticate字段,获取具体未授权原因 - 查看Spring Boot后台日志,日志会输出权限校验失败的详细信息,快速定位问题
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

