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

升级至Spring Security 6及SAS 1.0.1遇401错误:会话问题排查

问题:Spring Authorization Server 1.0.1(Spring Security 6.x)升级后重定向至/oauth2/authorize出现401

背景

将代码从Spring Authorization Server 0.3.0升级至1.0.1(同步升级至Spring Security 6.x),原有自定义流程要求先验证JWT令牌,再进入主OAuth流程,该流程在旧版本中运行正常。

原有配置(Spring Security 5 + Authorization Server 0.3.0)

@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
        throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
    return http.build();
}

@Bean
@Order(2)
public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests(
                    authorize ->
                            authorize
                                    .antMatchers(publicRoutes.toArray(new String[0]))
                                    .permitAll()
                                    .anyRequest()
                                    .authenticated())
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    http.csrf().disable();
    http.headers().frameOptions().disable();

    return http.build();
}

原有/login端点逻辑

@GetMapping("/login")
public String login(
        @RequestParam("response_type") String responseType,
        @RequestParam("client_id") String clientId,
        @RequestParam("scope") String scope,
        @RequestParam("state") String state,
        @RequestParam("redirect_uri") String redirectUri,
        @RequestParam("nonce") String nonce,
        Authentication authentication,
        HttpServletRequest httpServletRequest,
        HttpSession httpSession) {
   
    return String.format(
            "redirect:%s/oauth2/authorize?response_type=%s&client_id=%s&scope=%s&state=%s&nonce=%s&redirect_uri=%s",
            serverFqdn, responseType, clientId, scope, state, nonce, redirectUri);
}

升级后的尝试及问题

升级后调用/oauth2/authorize出现401错误,了解到Spring Security 6会话持久化机制有变化,尝试修改配置:

@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
        throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

    http.getConfigurer( OAuth2AuthorizationServerConfigurer.class)
            .oidc( Customizer.withDefaults());
    http
    .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.ALWAYS )
    .and()
    .securityContext(securityContext -> securityContext
        .requireExplicitSave( false )
        .securityContextRepository( patchedDelegatingSecurityContextRepository )
// or can this be any securityContextRepository that's here.. I assumed it needs to be a bean?
    );
    return http.build();
}

同时修改/login方法尝试手动保存会话:

@GetMapping("/login")
public String login(
        @RequestParam("response_type") String responseType,
        @RequestParam("client_id") String clientId,
        @RequestParam("scope") String scope,
        @RequestParam("state") String state,
        @RequestParam("redirect_uri") String redirectUri,
        @RequestParam("nonce") String nonce,
        Authentication authentication,
        HttpServletRequest httpServletRequest,
        HttpSession httpSession) {
   
  SecurityContext context = securityContextHolderStrategy.getContext();
  context.setAuthentication(authentication);
  SecurityContextHolder.setContext(context);
  securityContextHolderStrategy.setContext( context );
  sessionSecurityContextRepository.saveContext(context, httpServletRequest,response);

    return String.format(
            "redirect:%s/oauth2/authorize?response_type=%s&client_id=%s&scope=%s&state=%s&nonce=%s&redirect_uri=%s",
            serverFqdn, responseType, clientId, scope, state, nonce, redirectUri);
}

但会话仍未正确保存或被读取,询问遗漏的配置项。


解决方案

1. 为两个SecurityFilterChain配置共享的SecurityContextRepository

Spring Security 6中,不同的SecurityFilterChain默认使用独立的SecurityContextRepository,导致/login端点所在链保存的会话无法被授权服务器链读取。需显式配置共享实例:

首先定义共享Bean:

@Bean
public SecurityContextRepository securityContextRepository() {
    return new HttpSessionSecurityContextRepository();
}

然后修改两个过滤器链配置:

@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, SecurityContextRepository securityContextRepository)
        throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .oidc(Customizer.withDefaults());

    http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
        .securityContext(securityContext -> securityContext
            .securityContextRepository(securityContextRepository)
            .requireExplicitSave(false)); // 由框架自动处理上下文保存,无需显式调用

    return http.build();
}

@Bean
@Order(2)
public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http, SecurityContextRepository securityContextRepository) throws Exception {
    // 注意:Spring Security 6中authorizeRequests已被authorizeHttpRequests替代,antMatchers替换为requestMatchers
    http.authorizeHttpRequests(
                    authorize ->
                            authorize
                                    .requestMatchers(publicRoutes.toArray(new String[0]))
                                    .permitAll()
                                    .anyRequest()
                                    .authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
            .csrf(csrf -> csrf.disable())
            .headers(headers -> headers.frameOptions(frame -> frame.disable()))
            .securityContext(securityContext -> securityContext
                .securityContextRepository(securityContextRepository)); // 绑定共享的上下文仓库

    return http.build();
}

2. 简化/login端点的会话处理

只要共享的SecurityContextRepository配置正确,无需手动调用saveContext,框架会自动将SecurityContext同步到会话中。简化后的/login方法:

@GetMapping("/login")
public String login(
        @RequestParam("response_type") String responseType,
        @RequestParam("client_id") String clientId,
        @RequestParam("scope") String scope,
        @RequestParam("state") String state,
        @RequestParam("redirect_uri") String redirectUri,
        @RequestParam("nonce") String nonce,
        Authentication authentication) {
    // Authentication已由oauth2ResourceServer的JWT验证逻辑自动填充,无需手动处理上下文
    return String.format(
            "redirect:%s/oauth2/authorize?response_type=%s&client_id=%s&scope=%s&state=%s&nonce=%s&redirect_uri=%s",
            serverFqdn, responseType, clientId, scope, state, nonce, redirectUri);
}

3. 统一会话Cookie配置

确保两个过滤器链使用相同的会话Cookie名称,避免因Cookie不一致导致会话无法共享:
在两个SecurityFilterChain配置中添加:

http.sessionManagement(session -> session
        .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
        .sessionCookie(cookie -> cookie.name("JSESSIONID"))); // 使用统一的Cookie名称

4. 验证/oauth2/authorize的访问规则

Spring Authorization Server 1.0.x中,/oauth2/authorize端点默认要求已认证用户,需确保会话中的Authentication能被授权服务器链正确识别。若存在自定义拦截器,需排查是否拦截了合法的会话请求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 07:44:56