Spring Security:oauth2Login与oauth2Client选型及AccessToken获取方案
你的需求完全可以通过Spring Security的OAuth2支持开箱即用实现,无需大量自定义开发。核心是获取存储在OAuth2AuthorizedClient中的access_token,Spring Security在oauth2Login流程中已经自动帮你完成了token的获取与存储,只是默认未暴露在Authentication的属性中。
一、核心思路
oauth2Login已经完成了用户认证、授权码交换token的流程,获取到的access_token和refresh_token会被Spring Security保存在OAuth2AuthorizedClient对象中,你可以通过以下两种方式轻松获取:
方式1:使用@RegisteredOAuth2AuthorizedClient注解直接注入
在控制器方法或服务方法中,通过该注解直接获取指定客户端的授权对象,从中提取access_token:
@Controller public class MyController { private final ReportService reportService; public MyController(final ReportService reportService) { this.reportService = reportService; } @GetMapping({"", "/"}) public String index(final Model model, // 注入对应客户端的授权对象 @RegisteredOAuth2AuthorizedClient("the-provider") OAuth2AuthorizedClient authorizedClient) { String accessToken = authorizedClient.getAccessToken().getTokenValue(); model.addAttribute("report", reportService.generateReport(accessToken)); return "index"; } }
方式2:通过OAuth2AuthorizedClientService主动查询
如果需要在服务类中获取token,可以注入OAuth2AuthorizedClientService,根据当前认证用户的信息加载授权对象:
@Service public class ReportService { private final OAuth2AuthorizedClientService authorizedClientService; private final RestTemplate restTemplate; public ReportService(OAuth2AuthorizedClientService authorizedClientService, RestTemplate restTemplate) { this.authorizedClientService = authorizedClientService; this.restTemplate = restTemplate; } public String generateReport() { // 获取当前登录用户的认证信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 加载对应客户端的授权对象 OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient( "the-provider", authentication.getName()); String accessToken = authorizedClient.getAccessToken().getTokenValue(); // 携带access_token调用第三方API HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); HttpEntity<Void> entity = new HttpEntity<>(headers); ResponseEntity<String> response = restTemplate.exchange( "https://provider.domain.com/api/reports", HttpMethod.GET, entity, String.class); return response.getBody(); } }
二、进阶:自动携带token的WebClient配置
如果想更优雅地调用API,无需手动处理token,可以配置带有OAuth2拦截器的WebClient,它会自动为请求带上当前用户的access_token,并在token过期时自动刷新:
@Configuration public class WebClientConfig { @Bean public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Filter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); return WebClient.builder() .apply(oauth2Filter.oauth2Configuration()) .build(); } @Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) { // 构建支持授权码和刷新token的客户端提供者 OAuth2AuthorizedClientProvider provider = OAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() .refreshToken() .build(); DefaultOAuth2AuthorizedClientManager manager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); manager.setAuthorizedClientProvider(provider); return manager; } }
之后在ReportService中直接使用WebClient调用API即可,无需手动处理token:
@Service public class ReportService { private final WebClient webClient; public ReportService(WebClient webClient) { this.webClient = webClient; } public String generateReport() { return webClient.get() .uri("https://provider.domain.com/api/reports") // 指定使用的客户端ID .attributes(oauth2AuthorizedClient("the-provider")) .retrieve() .bodyToMono(String.class) .block(); } }
三、关于oauth2Client的说明
你无需切换到oauth2Client,因为oauth2Login已经包含了用户登录场景下的授权码流程支持,oauth2Client更多用于客户端凭证模式、客户端侧的授权码流程等非用户登录场景。你的需求是用户登录后代表用户调用API,oauth2Login的配置已经完全满足。
内容的提问来源于stack exchange,提问作者ETLJ

