如何拦截spring-boot-starter-oauth2-client的端点并获取code与state?
拦截OIDC回调端点并完成剩余流程方案
针对你使用Spring Authorization Server示例时需要拦截/login/oauth2/code/messaging-client-oidc端点、获取code和state后完成OIDC流程的需求,以下是两种实操方案及手动完成后续流程的步骤:
一、拦截回调端点的两种常用方式
方式1:自定义Controller覆盖默认回调端点
Spring Security的OIDC回调端点由框架默认处理,你可以通过自定义Controller映射相同路径,优先拦截请求并处理code和state,再交由框架完成后续流程:
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class CustomOidcCallbackController { private final OAuth2AuthorizedClientService authorizedClientService; public CustomOidcCallbackController(OAuth2AuthorizedClientService authorizedClientService) { this.authorizedClientService = authorizedClientService; } @GetMapping("/login/oauth2/code/messaging-client-oidc") public String handleCallback(@RequestParam String code, @RequestParam String state) { // 1. 自定义处理code和state(比如日志记录、存库、验证state合法性) System.out.println("获取到code: " + code); System.out.println("获取到state: " + state); // 2. 转发请求给框架默认逻辑,完成token交换、用户认证 return "forward:/login/oauth2/code/messaging-client-oidc"; } }
方式2:自定义Filter拦截请求
在Spring Security过滤器链中添加自定义Filter,在默认的OAuth2LoginAuthenticationFilter之前拦截回调请求:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component public class OidcCallbackFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestUri = request.getRequestURI(); if ("/login/oauth2/code/messaging-client-oidc".equals(requestUri)) { String code = request.getParameter("code"); String state = request.getParameter("state"); // 自定义处理code和state System.out.println("拦截到code: " + code); System.out.println("拦截到state: " + state); } // 继续执行后续过滤器,让框架完成默认流程 filterChain.doFilter(request, response); } }
然后在Security配置中注册该Filter,确保优先级高于默认的OIDC处理Filter:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.OAuth2LoginAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { private final OidcCallbackFilter oidcCallbackFilter; public SecurityConfig(OidcCallbackFilter oidcCallbackFilter) { this.oidcCallbackFilter = oidcCallbackFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2Login(oauth2 -> oauth2 .clientRegistrationRepository(clientRegistrationRepository()) .authorizedClientService(authorizedClientService()) ) // 将自定义Filter放到默认OIDC处理Filter之前 .addFilterBefore(oidcCallbackFilter, OAuth2LoginAuthenticationFilter.class); return http.build(); } // 注入示例中已有的clientRegistrationRepository和authorizedClientService }
二、获取code和state后手动完成剩余OIDC流程
如果不想依赖框架自动处理,可手动完成code换token、获取用户信息的步骤:
1. 手动用code交换token
使用OAuth2AuthorizedClientManager完成token交换:
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; // 在Service/Controller中注入相关Bean private final OAuth2AuthorizedClientManager authorizedClientManager; private final ClientRegistrationRepository clientRegistrationRepository; public OAuth2AuthorizedClient exchangeCodeForToken(String code, String state) { // 获取客户端注册信息 ClientRegistration client = clientRegistrationRepository.findByRegistrationId("messaging-client-oidc"); // 构造授权请求与响应 OAuth2AuthorizationRequest authRequest = OAuth2AuthorizationRequest.authorizationCode() .clientId(client.getClientId()) .redirectUri(client.getRedirectUri()) .scope(client.getScopes()) .state(state) .build(); OAuth2AuthorizationResponse authResponse = OAuth2AuthorizationResponse.success(code) .state(state) .build(); OAuth2AuthorizationExchange authExchange = new OAuth2AuthorizationExchange(authRequest, authResponse); // 发起token交换请求 OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("messaging-client-oidc") .authorizationExchange(authExchange) .principal(/* 当前用户主体,可传入匿名用户 */) .build(); // 返回包含access_token、id_token的授权客户端对象 return authorizedClientManager.authorize(authorizeRequest); }
2. 用access_token获取用户信息
调用授权服务器的userinfo端点获取用户详情:
import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.web.client.RestTemplate; public Object fetchUserInfo(OAuth2AccessToken accessToken, ClientRegistration client) { RestTemplate restTemplate = new RestTemplate(); String userInfoUri = client.getProviderDetails().getUserInfoEndpoint().getUri(); // 添加Bearer Token请求头 restTemplate.getInterceptors().add((request, body, execution) -> { request.getHeaders().setBearerAuth(accessToken.getTokenValue()); return execution.execute(request, body); }); // 发送请求获取用户信息,可自定义实体类接收返回数据 return restTemplate.getForObject(userInfoUri, Object.class); }
三、关键注意事项
- state验证:必须对比之前存储的state值(如存在session或缓存),确保一致性,防止CSRF攻击。
- 优先级控制:自定义Controller/Filter的优先级必须高于框架默认逻辑,否则无法拦截请求。
- token管理:手动处理流程时,需自行维护token的存储、过期刷新等逻辑,框架自动处理更省心。
内容的提问来源于stack exchange,提问作者hezf
相关产品推荐
相关产品推荐

