能否用Spring Cloud Gateway与Spring Authorization Server实现无状态OAuth2授权码模式?
核心逻辑
要实现无状态的授权码流程,核心是禁用网关会话存储,通过自定义回调处理逻辑直接将令牌返回给客户端,而非依赖Session保存认证信息。同时让网关以OAuth2客户端身份对接Spring Authorization Server,完成授权码的获取与令牌交换。
具体实现步骤
1. 依赖配置
添加必要的Maven依赖,确保网关具备OAuth2客户端能力,同时引入授权服务器组件(若自行搭建):
<!-- Spring Cloud Gateway --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- OAuth2 Client 核心依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <!-- Spring Authorization Server(自行搭建授权服务器时需引入) --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> </dependency>
2. 网关OAuth2客户端配置
在application.yml中配置客户端信息,指向你的Spring Authorization Server:
spring: security: oauth2: client: registration: spring: # 与示例中的客户端标识对应 client-id: your-client-id client-secret: your-client-secret authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/spring" # 网关回调地址 scope: openid,profile provider: spring: authorization-uri: http://auth-server:9000/oauth2/authorize # 授权服务器授权端点 token-uri: http://auth-server:9000/oauth2/token # 令牌交换端点 user-info-uri: http://auth-server:9000/userinfo user-name-attribute: sub cloud: gateway: routes: - id: login-redirect uri: no://op predicates: - Path=/login filters: - RedirectTo=302, /oauth2/authorization/spring # 访问/login自动跳转到授权入口
3. 自定义令牌返回逻辑(实现无状态)
默认OAuth2客户端会将令牌存入Session,我们需要自定义认证成功处理器,直接将令牌以JSON形式返回给客户端,同时禁用Session:
自定义认证成功处理器
@Component public class StatelessTokenHandler implements AuthenticationSuccessHandler { private final OAuth2AuthorizedClientService clientService; public StatelessTokenHandler(OAuth2AuthorizedClientService clientService) { this.clientService = clientService; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) auth; OAuth2AuthorizedClient authorizedClient = clientService.loadAuthorizedClient( oauthToken.getAuthorizedClientRegistrationId(), oauthToken.getName()); // 组装令牌响应内容 Map<String, Object> tokenResp = new HashMap<>(); tokenResp.put("access_token", authorizedClient.getAccessToken().getTokenValue()); tokenResp.put("token_type", authorizedClient.getAccessToken().getTokenType().getValue()); tokenResp.put("expires_in", authorizedClient.getAccessToken().getExpiresAt().getEpochSecond() - Instant.now().getEpochSecond()); if (authorizedClient.getRefreshToken() != null) { tokenResp.put("refresh_token", authorizedClient.getRefreshToken().getTokenValue()); } // 返回JSON格式令牌 response.setContentType(MediaType.APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getWriter(), tokenResp); } }
网关安全配置(禁用Session)
@Configuration public class GatewaySecurityConfig { private final StatelessTokenHandler tokenHandler; public GatewaySecurityConfig(StatelessTokenHandler tokenHandler) { this.tokenHandler = tokenHandler; } @Bean public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) { http .csrf(ServerHttpSecurity.Csrf::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 强制无状态 .authorizeExchange(exchanges -> exchanges .pathMatchers("/login", "/oauth2/authorization/**", "/login/oauth2/code/**").permitAll() .anyExchange().authenticated() ) .oauth2Login(oauth2 -> oauth2 .authorizationRequestRepository(new CookieOAuth2AuthorizationRequestRepository()) // 用Cookie存储授权请求,替代Session .authenticationSuccessHandler(tokenHandler) // 绑定自定义令牌返回处理器 ); return http.build(); } }
4. Spring Authorization Server基础配置(自行搭建时)
确保授权服务器配置的客户端信息与网关一致,支持授权码流程:
@Configuration @EnableAuthorizationServer public class AuthServerConfig { @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("your-client-id") .clientSecret("{noop}your-client-secret") // 生产环境需使用加密密码 .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("http://gateway:8080/login/oauth2/code/spring") // 与网关回调地址一致 .scope("openid") .scope("profile") .build(); return new InMemoryRegisteredClientRepository(client); } // 可补充用户认证、JWT令牌生成等其他基础配置 }
流程验证
- 访问网关
/login端点,自动重定向到/oauth2/authorization/spring,随后跳转至授权服务器的登录页面 - 输入合法用户凭证后,授权服务器回调网关的
/login/oauth2/code/spring端点 - 网关通过自定义处理器完成授权码到令牌的交换,直接返回JSON格式的令牌给客户端,全程无会话创建,保持无状态特性
内容的提问来源于stack exchange,提问作者vishal
相关产品推荐
相关产品推荐

