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

Spring Security OAuth2官方授权码示例因Cookie冲突无法运行求助

解决Spring Security OAuth2授权码模式同应用内Cookie冲突问题

我之前也踩过这个一模一样的坑!当你按照《Spring Boot and OAuth2》教程在同一个应用里同时启用@EnableAuthorizationServer和@EnableOAuth2Client时,默认的会话Cookie(JSESSIONID)会因为授权服务器和客户端共享同一个Servlet容器上下文而直接冲突——登录授权服务器后的会话会覆盖客户端的会话,导致授权回调时根本找不到之前发起授权请求的会话状态,最终整个授权流程直接卡壳。

核心问题根源

说白了就是俩“角色”(授权服务器+OAuth2客户端)挤在同一个应用里,却共用同一个会话Cookie名字,用户完成授权登录后,返回客户端时的会话已经不是最开始发起授权请求的那个了,OAuth2客户端自然没法匹配上对应的授权请求,报错也就理所当然了。

具体解决方案:给俩角色分配合独立的会话Cookie

我们要做的就是把授权服务器和客户端的会话配置彻底分开,给它们各自设置专属的Cookie名字和路径,让它们的会话互不干扰。

步骤1:拆分WebSecurity配置,别在主类里堆逻辑

别再让主类继承WebSecurityConfigurerAdapter了,单独搞两个配置类,分别管客户端和授权服务器的安全规则,还要指定不同的优先级(order),确保规则不会打架。

客户端安全配置类

@Configuration
@Order(100) // 优先级要高于授权服务器的配置,先匹配客户端的请求
public class ClientWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private OAuth2ClientContext oauth2ClientContext;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .antMatcher("/**")
            .authorizeRequests()
                .antMatchers("/", "/login**", "/webjars/**")
                .permitAll() // 开放首页、登录页和静态资源
                .anyRequest()
                .authenticated()
            .and()
            .logout()
                .logoutSuccessUrl("/")
                .permitAll()
            .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .sessionFixation().migrateSession()
                // 给客户端会话设置专属Cookie名字
                .and()
                .cookieConfigurer()
                .sessionCookieName("CLIENT_JSESSIONID")
                .sessionCookiePath("/");
    }
}

授权服务器安全配置类

@Configuration
@Order(200)
public class AuthServerWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // 只匹配授权服务器相关的端点
            .requestMatchers()
                .antMatchers("/oauth/authorize", "/oauth/token", "/oauth/check_token", "/login", "/logout")
            .and()
            .authorizeRequests()
                .anyRequest().authenticated()
            .and()
            .formLogin() // 启用表单登录
                .permitAll()
            .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .sessionFixation().migrateSession()
                // 给授权服务器会话设置专属Cookie名字
                .and()
                .cookieConfigurer()
                .sessionCookieName("AUTH_JSESSIONID")
                .sessionCookiePath("/oauth/");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 这里替换成你自己的用户认证逻辑,比如从数据库读用户
        auth.inMemoryAuthentication()
            .withUser("user")
            .password("{noop}password")
            .roles("USER");
    }

    // 授权服务器需要用到AuthenticationManager,必须暴露成Bean
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

步骤2:精简主类配置

主类只保留最核心的注解和业务逻辑,把配置类的活儿交给刚才创建的两个类:

@SpringBootApplication
@RestController
@EnableOAuth2Client
@EnableAuthorizationServer
public class SocialApplication {

    @RequestMapping({ "/user", "/me" })
    public Map<String, String> user(Principal principal) {
        Map<String, String> map = new LinkedHashMap<>();
        map.put("name", principal.getName());
        return map;
    }

    public static void main(String[] args) {
        SpringApplication.run(SocialApplication.class, args);
    }
}

步骤3:补全授权服务器的客户端配置(可选但必要)

如果你的应用里还没配置授权服务器的客户端信息,得加个配置类:

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("client") // 客户端ID
            .secret("{noop}secret") // 客户端密钥,{noop}表示不加密
            .authorizedGrantTypes("authorization_code") // 启用授权码模式
            .scopes("read", "write") // 授权范围
            .redirectUris("http://localhost:8080/login/oauth2/code/client"); // 回调地址
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()");
    }
}

验证效果

启动应用后,访问http://localhost:8080/user,会自动跳转到授权服务器的登录页。登录成功后,打开浏览器的开发者工具看Cookie,会发现同时存在CLIENT_JSESSIONID和AUTH_JSESSIONID两个Cookie,分别对应客户端和授权服务器的会话,这下就不会再出现Cookie冲突导致的授权失败问题了!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:30:45