Ale*_*lex 2 java spring javafx oauth-2.0 spring-security-oauth2
我想要达到的目标
所以我有一个 Java 客户端应用程序(JavaFX + Spring-boot 混合应用程序)。你可以在这里查看它https://github.com/FAForever/downlords-faf-client。所以到目前为止,如果用户希望保持登录状态,我们会存储用户名/密码,这显然是一个非常糟糕的主意。所以现在我想存储刷新令牌,然后用它登录用户。
现在的样子 看这里
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setClientId(apiProperties.getClientId());
details.setClientSecret(apiProperties.getClientSecret());
details.setClientAuthenticationScheme(AuthenticationScheme.header);
details.setAccessTokenUri(apiProperties.getBaseUrl() + OAUTH_TOKEN_PATH);
details.setUsername(username);
details.setPassword(password);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(details);
restOperations = templateBuilder
// Base URL can be changed in login window
.rootUri(apiProperties.getBaseUrl())
.configure(restTemplate);
Run Code Online (Sandbox Code Playgroud)
到目前为止我发现了什么
我发现这restTemplate.getAccessToken().getRefreshToken()会给我我想要保存的刷新令牌,以便让用户保持登录状态。
什么我想不通
我找不到仅使用刷新令牌创建 OAuth2RestTemplate 的方法。这甚至可能吗?有人可以指出我正确的方向吗?也许链接我一些文章阅读?是这个读取正确的地方?
我不认为这是可能的OAuth2RestTemplate,但是您可以自己重新实现所需的部分。我想与您分享一个示例,用于 OAuth 密码登录到 Microsoft 的 OAuth2(Azure Active Directory)风格。它确实错过了从现有刷新令牌中获取新令牌的部分,但我在需要添加它的地方添加了一条评论。
模仿 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);
}
}
Run Code Online (Sandbox Code Playgroud)
这个拦截器可以添加到你的主要 RestTemplate 中:
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(globalOAuthTokenInterceptor);
restTemplate.setInterceptors(interceptors);
Run Code Online (Sandbox Code Playgroud)
拦截器中使用的令牌服务将令牌保存在缓存中,并根据请求检查令牌是否过期,并在需要时查询新令牌。
@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) {
// add code for fetching OAuth2 token from refresh token here
return null;
}
}
Run Code Online (Sandbox Code Playgroud)
在此代码示例中,您将使用用户名和密码登录一次,之后所有进一步登录都将使用刷新令牌。如果你想直接使用刷新令牌,就使用公共方法,否则会在内部完成。由于登录代码是专门为登录 Microsoft AAD 而编写的,因此您应该重新检查 MultiValueMap 参数。
TokenServiceProperties 很简单:
@Data
public class TokenServiceProperties {
private String aadB2bUrl;
private String aadB2bClientId;
private String aadB2bResource;
}
Run Code Online (Sandbox Code Playgroud)
如果需要,调整它们。
整个解决方案有一个小缺点:您现在需要第二个(“简单”的)来获取 OAuth 令牌,而不是通常通过依赖注入获取的 RestTemplate。在这个例子中,我们在 TokenService 的构造函数中创建它。然而,这通常是不好的风格,因为它使单元测试等变得更加困难。您还可以考虑在 TokenService 中使用合格的 bean 或使用更基本的 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"
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3565 次 |
| 最近记录: |