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

Angular对接Spring Security会话认证失败问题排查

问题:Spring会话认证失效,受保护接口始终要求登录

问题背景

我有一个Angular前端和Spring后端应用。目前登录时能获取到JSessionId Cookie,但注册时无法获取;请求受保护接口时,尽管前端已带上Cookie,却始终弹出「请登录」弹窗。

登录时,UserService打印的用户认证信息如下:

UsernamePasswordAuthenticationToken [Principal=User(userId=1, name=Maksym Riabov, username=MRiabov, password={bcrypt}$2a$10$W0XJRQdfxV5XXORkr2bTluIHvFetIVBzmVp51l39T5zLCQk12RV1i, company=null, enabled=true), Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ADMIN]]

这里的SessionId为null,这是为什么?

已确认的配置

  • Angular所有请求都添加了{withCredentials: true}
  • 已查阅官方文档并照搬示例代码,问题仍未解决

相关代码

登录/注册控制器

@GetMapping("/login")
public ResponseEntity<String> login() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    return ResponseEntity.ok("123123");
}

@PostMapping("/register")
public ResponseEntity<Map<String, String>> register(@RequestBody UserRegisterDto userDto) {
    //todo check if name taken
    User user = userMapper.toEntity2(userDto);
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    user.setEnabled(true);
    //todo remove
    Authority authority = authorityRepository.save(new Authority("ADMIN"));
    user.setAuthorities(Set.of(authority));
    //todo REMOVE!!!!

    User savedUser = userRepository.save(user);
    System.out.println("registration works!");

    return ResponseEntity.ok(Map.of("result",authority.getAuthority().getAuthority()));
}

受保护接口(触发登录弹窗)

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/create")
public ResponseEntity<OnboardingPathDto> createOnboardingPath() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    // erased a bit of code here
    return ResponseEntity.ok().build();
}

Spring Security配置类

@Component
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
@RequiredArgsConstructor
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
        http
                .csrf().disable().cors().disable()
                .authorizeHttpRequests() 
                .anyRequest().permitAll() //todo this is unsafe
                .and().sessionManagement(session -> session.
                        sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                        .maximumSessions(1))//to force only one session per user
     //here I tried sessionManagement to do something, but did it do something?
                .rememberMe((rememberMe) -> rememberMe.userDetailsService(userDetailsService))
                .httpBasic(); 
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(DaoAuthenticationProvider daoAuthenticationProvider) throws Exception {
        return new ProviderManager(daoAuthenticationProvider);
    }

    @Bean
    public DaoAuthenticationProvider prov(PasswordEncoder passwordEncoder, UserDetailsService userDetailService) throws Exception {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        daoAuthenticationProvider.setUserDetailsService(userDetailService);
        return daoAuthenticationProvider;
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {//to force only one session per user
        return new HttpSessionEventPublisher();
    }
}

核心疑问

为什么Spring已经配置了会话认证,却无法通过会话完成认证?问题出在哪里?

补充问题

  1. 直接将Session传递给Angular(非Cookie方式)是否存在安全风险?目前我依赖Cookie实现会话。
  2. 我打算改用OAuth2认证,是否更合适?

问题分析与解决方案

1. SessionId为null的原因

你看到的SessionId=null是因为认证时还没创建HTTP会话。WebAuthenticationDetails里的SessionId是认证请求发起时已存在的会话ID,但如果认证过程中才首次创建会话,这个值就会是null——这不代表会话没创建,只是认证细节对象初始化时还没拿到会话ID。你可以在登录接口里主动获取request.getSession().getId(),就能看到实际的会话ID。

2. 受保护接口认证失效的核心问题

你的Security配置有几个关键错误:

  • anyRequest().permitAll()会绕过所有安全校验:包括方法级的@PreAuthorize。因为Spring Security的过滤器链是先执行authorizeHttpRequests,如果所有请求都被允许,后续的方法级安全校验会被忽略(过滤器链不会触发认证流程,导致方法校验时SecurityContext里没有认证信息)。
  • HttpBasic认证的会话绑定问题:HttpBasic默认是每次请求都携带用户名密码,而不是依赖会话。你虽然配置了sessionCreationPolicy(IF_REQUIRED),但因为permitAll的存在,登录接口的请求不会触发HttpBasic的认证流程——你写的/login接口只是获取当前认证信息,但并没有实际完成登录动作!HttpBasic的认证是由Spring Security的过滤器自动处理的,不需要自己写登录接口。

修复步骤:

(1)修正Security配置,正确拦截受保护请求

把anyRequest().permitAll()改成仅放行登录、注册等公开接口,其余请求需要认证:

http
    .csrf().disable()
    .cors() // 建议不要禁用CORS,而是配置CorsConfigurationSource确保Cookie跨域传递
    .and()
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/login", "/register").permitAll()
        .anyRequest().authenticated()
    )
    .sessionManagement(session -> session
        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
        .maximumSessions(1)
    )
    .httpBasic()
    .and()
    .rememberMe(rememberMe -> rememberMe.userDetailsService(userDetailsService));

跨域场景需补充CORS配置,否则Cookie无法传递:

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("http://localhost:4200")); // 你的Angular地址
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
    config.setAllowedHeaders(List.of("*"));
    config.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}
(2)移除自定义的/login接口

HttpBasic认证由Spring Security自动处理,当请求需要认证的接口时,浏览器会自动弹出登录弹窗,或者前端在请求时携带Authorization: Basic <base64编码的用户名:密码>头。自定义的/login接口没有实际完成认证,无需保留。

(3)确保方法级安全生效

因为你已经配置了@EnableMethodSecurity,修正过滤器链后,方法上的@PreAuthorize("hasRole('ADMIN')")会在过滤器认证通过后,进一步校验权限。

3. 补充问题解答

  • 非Cookie方式传递Session的安全风险:直接传递Session ID(比如放在请求头、URL参数里)风险很高——URL参数会被记录在浏览器历史、服务器日志里;请求头如果没有HTTPS加密,也会被拦截窃取。Cookie是最安全的会话传递方式,只要配置HttpOnly和Secure属性(Spring Security默认会设置),能有效防止XSS和CSRF攻击。
  • 是否改用OAuth2:OAuth2适合分布式系统、第三方登录场景,或者需要令牌授权的多客户端场景(比如小程序、APP)。如果你的应用是单体应用,Session认证足够简单高效;如果未来要做微服务扩展,OAuth2是更好的选择。

内容的提问来源于stack exchange,提问作者MRiabov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 16:46:09