使用spring-boot-starter-oauth2-client手动调用oauth2
时间: 2025-06-18 14:04:19 浏览: 4
<think>我们使用SpringBoot的OAuth2客户端库时,通常利用其自动配置和与SpringSecurity的集成。但有时我们需要手动调用OAuth2流程,例如在非Web应用或需要更精细控制时。下面将展示如何手动使用`spring-boot-starter-oauth2-client`进行OAuth2调用。###步骤1.**添加依赖**:与之前相同,添加`spring-boot-starter-oauth2-client`依赖。2.**配置属性**:在`application.properties`中配置OAuth2提供者信息。3.**手动获取授权码**:由于授权码需要用户交互,这一步在纯后端应用中通常不可行,因此我们假设已经通过其他方式(如前端)获得了授权码。4.**使用授权码交换令牌**:使用`WebClient`(或`RestTemplate`)手动发送请求交换令牌。5.**使用令牌访问资源**:使用获取到的访问令牌访问受保护的资源。###示例代码####1.配置属性在`application.properties`中配置OAuth2客户端:```propertiesspring.security.oauth2.client.registration.my-provider.client-id=your_client_idspring.security.oauth2.client.registration.my-provider.client-secret=your_client_secretspring.security.oauth2.client.registration.my-provider.redirect-uri=http://localhost:8080/login/oauth2/code/my-providerspring.security.oauth2.client.registration.my-provider.authorization-grant-type=authorization_codespring.security.oauth2.client.registration.my-provider.scope=openid,profile,emailspring.security.oauth2.client.provider.my-provider.authorization-uri=https://example.com/oauth2/authorizespring.security.oauth2.client.provider.my-provider.token-uri=https://example.com/oauth2/tokenspring.security.oauth2.client.provider.my-provider.user-info-uri=https://example.com/userinfo```####2.创建OAuth2授权请求服务我们将创建一个服务类,用于手动交换令牌。```javaimportorg.springframework.security.oauth2.client.OAuth2AuthorizedClient;importorg.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;importorg.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;importorg.springframework.security.oauth2.client.registration.ClientRegistration;importorg.springframework.security.oauth2.client.registration.ClientRegistrationRepository;importorg.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;importorg.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;importorg.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;importorg.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;importorg.springframework.stereotype.Service;@ServicepublicclassOAuth2TokenService{privatefinalClientRegistrationRepositoryclientRegistrationRepository;privatefinalOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>accessTokenResponseClient;publicOAuth2TokenService(ClientRegistrationRepositoryclientRegistrationRepository){this.clientRegistrationRepository=clientRegistrationRepository;this.accessTokenResponseClient=newDefaultAuthorizationCodeTokenResponseClient();}publicOAuth2AccessTokenResponsegetTokenResponse(StringregistrationId,Stringcode,Stringstate,StringredirectUri){//获取客户端注册信息ClientRegistrationclientRegistration=clientRegistrationRepository.findByRegistrationId(registrationId);if(clientRegistration==null){thrownewIllegalArgumentException("InvalidregistrationId");}//构建授权请求和响应OAuth2AuthorizationRequestauthorizationRequest=OAuth2AuthorizationRequest.authorizationCode().clientId(clientRegistration.getClientId()).redirectUri(redirectUri).authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()).scopes(clientRegistration.getScopes()).state(state).build();OAuth2AuthorizationResponseauthorizationResponse=OAuth2AuthorizationResponse.success(code).redirectUri(redirectUri).state(state).build();//构建授权交换对象OAuth2AuthorizationExchangeauthorizationExchange=newOAuth2AuthorizationExchange(authorizationRequest,authorizationResponse);//创建授权码授予请求OAuth2AuthorizationCodeGrantRequestgrantRequest=newOAuth2AuthorizationCodeGrantRequest(clientRegistration,authorizationExchange);//获取令牌响应returnaccessTokenResponseClient.getTokenResponse(grantRequest);}}```####3.使用令牌访问资源我们可以使用获取到的访问令牌来调用受保护的资源:```javaimportorg.springframework.http.HttpHeaders;importorg.springframework.http.MediaType;importorg.springframework.stereotype.Service;importorg.springframework.web.reactive.function.client.WebClient;@ServicepublicclassResourceService{publicStringgetUserInfo(Stringtoken,StringuserInfoUri){returnWebClient.builder().baseUrl(userInfoUri).defaultHeader(HttpHeaders.AUTHORIZATION,"Bearer"+token).build().get().accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(String.class).block();}}```####4.控制器示例创建一个控制器来触发手动流程(假设我们已经通过其他方式获得授权码):```javaimportorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassOAuth2ManualController{@AutowiredprivateOAuth2TokenServicetokenService;@AutowiredprivateResourceServiceresourceService;@GetMapping("/manual-oauth")publicStringmanualOAuthFlow(@RequestParamStringcode,@RequestParamStringstate){//这里假设state是之前生成并保存的,用于防止CSRF攻击StringregistrationId="my-provider";StringredirectUri="http://localhost:8080/login/oauth2/code/my-provider";//交换令牌OAuth2AccessTokenResponsetokenResponse=tokenService.getTokenResponse(registrationId,code,state,redirectUri);StringaccessToken=tokenResponse.getAccessToken().getTokenValue();//使用令牌获取用户信息StringuserInfoUri="https://example.com/userinfo";returnresourceService.getUserInfo(accessToken,userInfoUri);}}```###解释1.**OAuth2TokenService**:这个服务类负责使用授权码交换访问令牌。它使用了SpringSecurityOAuth2的`DefaultAuthorizationCodeTokenResponseClient`来发送令牌请求。2.**ResourceService**:使用WebClient发送带有访问令牌的请求到资源服务器。3.**OAuth2ManualController**:提供了一个端点,接收授权码和state参数,然后手动触发OAuth2流程。注意:在实际应用中,授权码是通过用户重定向到授权服务器并返回的,因此这个示例假设你已经有了授权码。此外,state参数应该与最初生成授权请求时使用的state一致,以防止CSRF攻击。###§§相关问题§§:1.在OAuth2授权码模式中,state参数的作用是什么?2.如何在SpringBoot中配置多个OAuth2客户端?3.如何手动刷新OAuth2访问令牌?4.如何处理OAuth2流程中的错误?</think>在 Spring Boot 中使用 `spring-boot-starter-oauth2-client` 手动调用 OAuth2 流程,可以更精细地控制认证过程,而不是依赖 Spring Security 的自动配置。以下是详细的实现方法:
### 手动调用 OAuth2 的步骤
1. **添加依赖**
在 `pom.xml` 中添加必要依赖:
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
```
2. **配置 OAuth2 属性**
在 `application.properties` 中配置:
```properties
# OAuth2 客户端配置
app.oauth2.client-id=your_client_id
app.oauth2.client-secret=your_client_secret
app.oauth2.authorization-uri=https://example.com/oauth2/authorize
app.oauth2.token-uri=https://example.com/oauth2/token
app.oauth2.redirect-uri=http://localhost:8080/login/oauth2/code/custom
app.oauth2.scope=openid,profile,email
```
3. **创建 OAuth2 服务类**
手动处理授权流程的核心类:
```java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.Collections;
@Service
public class OAuth2ManualService {
@Value("${app.oauth2.client-id}")
private String clientId;
@Value("${app.oauth2.client-secret}")
private String clientSecret;
@Value("${app.oauth2.authorization-uri}")
private String authorizationUri;
@Value("${app.oauth2.token-uri}")
private String tokenUri;
@Value("${app.oauth2.redirect-uri}")
private String redirectUri;
@Value("${app.oauth2.scope}")
private String scope;
private final RestTemplate restTemplate = new RestTemplate();
// 生成授权URL
public String getAuthorizationUrl(String state) {
return UriComponentsBuilder.fromHttpUrl(authorizationUri)
.queryParam("response_type", "code")
.queryParam("client_id", clientId)
.queryParam("redirect_uri", redirectUri)
.queryParam("scope", scope)
.queryParam("state", state)
.toUriString();
}
// 使用授权码获取访问令牌
public String getAccessToken(String code, String state) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("code", code);
params.add("redirect_uri", redirectUri);
params.add("client_id", clientId);
params.add("client_secret", clientSecret);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<MultiValueMap<String, String>> request =
new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.postForEntity(
tokenUri, request, String.class);
if (response.getStatusCode() == HttpStatus.OK) {
return response.getBody(); // 返回JSON格式的令牌响应
} else {
throw new RuntimeException("Failed to get access token: " + response.getStatusCode());
}
}
// 使用访问令牌获取用户信息
public String getUserInfo(String accessToken, String userInfoUri) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(
userInfoUri, HttpMethod.GET, entity, String.class);
return response.getBody();
}
}
```
4. **创建控制器**
处理 OAuth2 回调并管理流程:
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;
@RestController
public class OAuth2ManualController {
@Autowired
private OAuth2ManualService oauth2Service;
@Value("${app.oauth2.redirect-uri}")
private String redirectUri;
// 启动授权流程
@GetMapping("/start-oauth")
public RedirectView startOAuthFlow() {
String state = "random_state_value"; // 实际应用中应生成随机值
String authorizationUrl = oauth2Service.getAuthorizationUrl(state);
return new RedirectView(authorizationUrl);
}
// OAuth2 回调端点
@GetMapping("/login/oauth2/code/custom")
public String handleCallback(
@RequestParam("code") String code,
@RequestParam("state") String state) {
// 验证state值(防止CSRF攻击)
if (!"random_state_value".equals(state)) {
throw new IllegalArgumentException("Invalid state parameter");
}
// 获取访问令牌
String tokenResponse = oauth2Service.getAccessToken(code, state);
// 获取用户信息(假设用户信息端点)
String userInfo = oauth2Service.getUserInfo(
extractAccessToken(tokenResponse),
"https://example.com/userinfo");
return "User Info: " + userInfo;
}
// 从JSON响应中提取访问令牌
private String extractAccessToken(String jsonResponse) {
// 实际实现应使用JSON解析库
return jsonResponse.split("\"access_token\":\"")[1].split("\"")[0];
}
}
```
### 关键点解释
1. **手动流程控制**:
- `startOAuthFlow` 方法生成授权URL并重定向用户
- `handleCallback` 方法处理授权服务器返回的授权码
2. **安全考虑**:
- 使用 `state` 参数防止CSRF攻击
- 验证回调中的state值是否匹配初始值
3. **令牌获取**:
- 使用 `RestTemplate` 发送POST请求获取访问令牌
- 处理JSON格式的令牌响应
4. **用户信息获取**:
- 使用Bearer令牌访问受保护资源
- 处理用户信息端点的响应
5. **配置灵活性**:
- 通过 `@Value` 注解注入配置参数
- 可轻松适配不同的OAuth2提供者
### 使用场景
这种方法适用于以下情况:
- 需要完全控制OAuth2流程
- 在非标准环境(如移动应用后端)中集成
- 需要自定义令牌处理逻辑
- 需要与多个OAuth2提供者集成
###
阅读全文
相关推荐


















