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

