已登录状态下/welcome端点始终返回401状态码,求排查原因
问题描述
我有两个端点:/login 和 /welcome。用户仅在通过身份验证后才能访问/welcome端点。我的问题是,即使此前/login端点返回200状态码,浏览器中/welcome的状态码仍始终为401。
Angular代码
public login(): Observable<any> { const headers = new HttpHeaders({ Authorization: 'Basic ' + window.btoa('linda:pass') }) return this.http.get(environment.apiBaseUrl + 'login', { headers, responseType: 'text' as 'json' }) } public welcome(): Observable<any> { return this.http.get(environment.apiBaseUrl + 'welcome'); }
Spring Boot代码
LoginController类
@RestController public class LoginController { @CrossOrigin @GetMapping("/welcome") public ResponseEntity<String> welcome() { return ResponseEntity.ok("Welcome!"); } @CrossOrigin @GetMapping("/login") public void login() {} }
Config配置类
@Configuration @EnableWebSecurity public class Config { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(10); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests(auth -> auth .antMatchers("/login").permitAll() .antMatchers("/welcome").hasRole("ADMIN") ) .httpBasic(); return http.build(); } @Bean public InMemoryUserDetailsManager userDetailsManager() { var linda = User.builder() .username("linda") .password(passwordEncoder().encode("pass")) .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(linda); } }
问题分析与解决
核心错误点
对HTTP Basic认证机制的误解
HTTP Basic是无状态认证,不需要专门调用/login接口触发“登录”——它要求每次请求受保护资源时都必须携带Authorization头。你当前的/login接口只是一个空接口,调用它时的认证信息不会被Spring Security保存为会话,所以后续请求/welcome时仍会被判定为未认证。welcome()请求未携带认证头
Angular的welcome()方法发起请求时,没有添加Authorization请求头,Spring Security无法识别用户身份,直接返回401。冗余代码与不规范配置
@CrossOrigin注解重复添加,Spring Security可以统一配置跨域,无需每个Controller单独设置。/login接口完全多余,不符合HTTP Basic认证的使用逻辑。
修复步骤
1. 修改Angular代码,让welcome()请求携带认证头
把认证头逻辑抽出来复用,确保受保护请求都携带认证信息:
// 封装认证头生成方法,避免重复代码 private getAuthHeaders(): HttpHeaders { return new HttpHeaders({ Authorization: 'Basic ' + window.btoa('linda:pass') }); } // 这个login方法可以直接删除,因为HTTP Basic不需要单独登录接口 public login(): Observable<any> { return this.http.get(environment.apiBaseUrl + 'login', { headers: this.getAuthHeaders(), responseType: 'text' as 'json' }); } public welcome(): Observable<any> { // 必须携带认证头才能访问受保护的/welcome return this.http.get(environment.apiBaseUrl + 'welcome', { headers: this.getAuthHeaders() }); }
2. 简化Spring Boot代码
删除冗余的/login接口
修改LoginController,只保留核心的/welcome接口:
@RestController public class LoginController { @GetMapping("/welcome") public ResponseEntity<String> welcome() { return ResponseEntity.ok("Welcome!"); } }
统一配置跨域与安全规则
在Spring Security中整合跨域配置,同时去掉冗余的/login放行规则:
@Configuration @EnableWebSecurity public class Config { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(10); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 统一配置跨域,生产环境请限制allowedOrigins为具体域名 .cors(cors -> cors.configurationSource(request -> { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("*")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); config.setAllowedHeaders(List.of("*")); config.setAllowCredentials(true); return config; })) .authorizeRequests(auth -> auth .antMatchers("/welcome").hasRole("ADMIN") .anyRequest().authenticated() ) .httpBasic(); return http.build(); } @Bean public InMemoryUserDetailsManager userDetailsManager() { var linda = User.builder() .username("linda") .password(passwordEncoder().encode("pass")) .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(linda); } }
补充说明
如果需要实现“登录后保持会话”的效果(比如不用每次请求都带认证头),HTTP Basic并不适合,建议改用Spring Security的表单登录或Session认证机制。HTTP Basic的核心是无状态,每个请求都必须携带认证信息。
内容的提问来源于stack exchange,提问作者xRay
相关产品推荐
相关产品推荐

