我不认为使用
OAuth2RestTemplate
可以实现此目的,但您可以自己重新实现所需的部分。我想分享一个示例,演示如何使用OAuth密码登录到Microsoft的OAuth2版本(Azure活动目录)。它缺少从现有刷新令牌获取新令牌的部分,但我在需要添加它的地方添加了一条注释。
模仿OAuthRestTemplates行为的简单方法是创建一个自定义的ClientHttpRequestInterceptor,将令牌获取委托给专用的Spring服务组件,然后将其附加到您的RestTemplate中:
@RequiredArgsConstructor
@Slf4j
public class OAuthTokenInterceptor implements ClientHttpRequestInterceptor {
private final TokenService tokenService;
@NotNull
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("Authorization", "Bearer " + tokenService.getRefreshedToken().getValue());
return execution.execute(request, body);
}
}
这个拦截器可以添加到您的主要 RestTemplate 中:
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>()
interceptors.add(globalOAuthTokenInterceptor)
restTemplate.setInterceptors(interceptors)
拦截器使用的令牌服务将令牌保存在缓存中,当请求到来时检查令牌是否过期,如果需要则查询一个新的令牌。
@Service
@Slf4j
public class TokenService {
private final TokenServiceProperties tokenServiceProperties;
private final RestTemplate simpleRestTemplate;
private OAuth2AccessToken tokenCache;
public TokenService(TokenServiceProperties tokenServiceProperties) {
this.tokenServiceProperties = tokenServiceProperties;
simpleRestTemplate = new RestTemplateBuilder().
build();
}
public OAuth2AccessToken getRefreshedToken() {
if (tokenCache == null || tokenCache.isExpired()) {
log.debug("Token expired, fetching new token");
tokenCache = refreshOAuthToken();
} else {
log.debug("Token still valid for {} seconds", tokenCache.getExpiresIn());
}
return tokenCache;
}
public OAuth2AccessToken loginWithCredentials(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("resource", tokenServiceProperties.getAadB2bResource());
map.add("client_id", tokenServiceProperties.getAadB2bClientId());
map.add("username", username);
map.add("password", password);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return simpleRestTemplate.postForObject(
tokenServiceProperties.getAadB2bUrl(),
request,
OAuth2AccessToken.class
);
}
private OAuth2AccessToken refreshOAuthToken() {
return loginWithRefreshToken(tokenCache.getRefreshToken().getValue());
}
public OAuth2AccessToken loginWithRefreshToken(String refreshToken) {
return null;
}
}
在这个代码示例中,您需要使用用户名和密码登录一次,之后所有进一步的登录都将使用刷新令牌。如果您想直接使用刷新令牌,则可以使用公共方法,否则它将在内部完成。
由于登录代码专门编写用于登录Microsoft AAD,因此您应该重新检查MultiValueMap参数。
TokenServiceProperties很简单:
@Data
public class TokenServiceProperties {
private String aadB2bUrl;
private String aadB2bClientId;
private String aadB2bResource;
}
如有必要,请进行适当的修改。
整个解决方案存在一个小缺陷:通常您可以通过依赖注入获取一个 RestTemplate,但现在您需要第二个 RestTemplate(“简单”),以获取 OAuth 令牌。在此示例中,我们在 TokenService 的构造函数中创建它。但是,这通常不是好的编程风格,因为它会使单元测试等变得更加困难。您也可以考虑使用限定符 bean 或在 TokenService 中使用更基本的 http 客户端。
另一个重要的事情要注意:我在这里使用了 spring-security-oauth2 包。如果您没有在项目中配置 Spring Security,则会触发 Spring Security 自动配置,这可能是不希望的 - 您可以通过排除不需要的包来解决这个问题,例如在 gradle 中:
implementation("org.springframework.security.oauth:spring-security-oauth2") {
because "We only want the OAuth2AccessToken interface + implementations without activating Spring Security"
exclude group: "org.springframework.security", module: "spring-security-web"
exclude group: "org.springframework.security", module: "spring-security-config"
exclude group: "org.springframework.security", module: "spring-security-core"
}
WebClient
类而不是rest template。虽然我不能告诉你如何使用它。 - Ole V.V.