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

如何拦截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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 16:45:20