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

已登录状态下/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);
    }
}

问题分析与解决

核心错误点

  1. 对HTTP Basic认证机制的误解
    HTTP Basic是无状态认证,不需要专门调用/login接口触发“登录”——它要求每次请求受保护资源时都必须携带Authorization头。你当前的/login接口只是一个空接口,调用它时的认证信息不会被Spring Security保存为会话,所以后续请求/welcome时仍会被判定为未认证。

  2. welcome()请求未携带认证头
    Angular的welcome()方法发起请求时,没有添加Authorization请求头,Spring Security无法识别用户身份,直接返回401。

  3. 冗余代码与不规范配置

  • @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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 12:30:58