Spring OAuth2自定义redirect_uri处理authorization_code换取access_token咨询
Spring OAuth2授权码模式对接VK的自定义回调处理
Spring授权码模式默认流程
- 向资源服务器发起授权请求,获取
authorization_code - 授权服务器将code重定向至配置的
redirect_uri - Spring OAuth2自动捕获该code,向资源服务器请求换取
access_token,并将token存入会话
问题场景
对接VK资源服务器时,Spring默认处理逻辑异常,需自定义实现redirect_uri的回调请求处理。以下是当前配置、现有代码及具体实现方案。
配置文件
registration: vk: client-id: xxx client-secret: xxx redirect_uri: http://localhost:8080/login/oauth2/callback/vk scope: notify,friends,photos,wall,ads,groups,stats,email authorization-grant-type: authorization_code provider: vk: authorization-uri: https://oauth.vk.com/authorize token-uri: https://oauth.vk.com/access_token user-info-uri: https://api.vk.com/method/users.get?fields=screen_name,email user-name-attribute: response.screen_name
现有安全配置代码
@Configuration @EnableWebSecurity @EnableMethodSecurity public class OAuth2LoginSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/public").permitAll() .requestMatchers("/login/oauth2/callback/**").permitAll() .anyRequest().authenticated() ); return http.build(); } }
已实现的回调接口(可获取authorization_code)
@RequestMapping(value = "/login/oauth2/callback/vk") public @ResponseBody void generateReport( @RequestParam(value = "code", required = false) String code, @RequestParam(value = "state", required = false) String state, HttpServletRequest request, HttpServletResponse response) { System.out.println(); }
自定义回调处理实现方案
1. 调整安全配置
禁用Spring默认的OAuth2登录处理,避免和自定义回调逻辑冲突:
@Configuration @EnableWebSecurity @EnableMethodSecurity public class OAuth2LoginSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/public", "/login/oauth2/callback/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2.disable()); return http.build(); } }
2. 实现完整的回调处理逻辑
创建控制器处理VK的回调请求,完成code换token、token存储等操作:
@Controller public class VkOAuth2CallbackController { private final ClientRegistrationRepository clientRegistrationRepository; private final OAuth2AuthorizedClientService authorizedClientService; private final RestTemplate restTemplate; public VkOAuth2CallbackController(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) { this.clientRegistrationRepository = clientRegistrationRepository; this.authorizedClientService = authorizedClientService; this.restTemplate = new RestTemplate(); } @RequestMapping(value = "/login/oauth2/callback/vk") public String handleVkCallback(@RequestParam("code") String code, @RequestParam("state") String state, HttpServletRequest request, Authentication authentication) { // 获取VK客户端注册信息 ClientRegistration vkRegistration = clientRegistrationRepository.findByRegistrationId("vk"); if (vkRegistration == null) { throw new IllegalArgumentException("未找到VK客户端配置"); } // 构造token请求参数 MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("client_id", vkRegistration.getClientId()); params.add("client_secret", vkRegistration.getClientSecret()); params.add("code", code); params.add("redirect_uri", vkRegistration.getRedirectUri()); params.add("grant_type", "authorization_code"); // 发送请求获取access_token HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers); ResponseEntity<VkTokenResponse> tokenResponse = restTemplate.postForEntity( vkRegistration.getProviderDetails().getTokenUri(), requestEntity, VkTokenResponse.class ); if (tokenResponse.getStatusCode().is2xxSuccessful() && tokenResponse.getBody() != null) { VkTokenResponse tokenBody = tokenResponse.getBody(); // 封装OAuth2AccessToken OAuth2AccessToken accessToken = new OAuth2AccessToken( OAuth2AccessToken.TokenType.BEARER, tokenBody.getAccessToken(), null, Instant.now().plusSeconds(tokenBody.getExpiresIn()) ); // 可选:调用VK用户信息接口获取用户详情 // String userInfoUrl = vkRegistration.getProviderDetails().getUserInfoEndpoint().getUri() + "&access_token=" + tokenBody.getAccessToken(); // ResponseEntity<VkUserInfoResponse> userInfoResponse = restTemplate.getForEntity(userInfoUrl, VkUserInfoResponse.class); // 将token存入授权客户端服务,供后续业务使用 OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient( vkRegistration, authentication != null ? authentication.getName() : tokenBody.getUserId(), accessToken ); authorizedClientService.saveAuthorizedClient(authorizedClient, authentication); // 重定向至登录成功页面 return "redirect:/home"; } else { // 处理token获取失败场景 return "redirect:/login?error=vk_token_fail"; } } // VK token响应实体类 public static class VkTokenResponse { @JsonProperty("access_token") private String accessToken; @JsonProperty("expires_in") private Integer expiresIn; @JsonProperty("user_id") private String userId; private String email; // Getter & Setter public String getAccessToken() { return accessToken; } public void setAccessToken(String accessToken) { this.accessToken = accessToken; } public Integer getExpiresIn() { return expiresIn; } public void setExpiresIn(Integer expiresIn) { this.expiresIn = expiresIn; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } // 可选:VK用户信息响应实体类 public static class VkUserInfoResponse { private List<VkUser> response; public List<VkUser> getResponse() { return response; } public void setResponse(List<VkUser> response) { this.response = response; } public static class VkUser { private Integer id; @JsonProperty("screen_name") private String screenName; private String email; // Getter & Setter public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getScreenName() { return screenName; } public void setScreenName(String screenName) { this.screenName = screenName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } } }
注意事项
- 确保VK开发者后台配置的
redirect_uri与代码、配置文件中的完全一致 - VK的token响应格式与标准OAuth2略有差异,需通过
@JsonProperty映射字段 - 若需完成用户认证,可在获取用户信息后构造
Authentication对象并存入SecurityContext - 需处理网络异常、code无效、token过期等异常场景
内容的提问来源于stack exchange,提问作者Roman Anokhin
相关产品推荐
相关产品推荐

