java - 使用 Keycloak 保护类型安全的微配置文件 RestClientBuilder
问题描述
我有以下设置:
Keycloak 9.0.0 在端口 8180 上运行
Spring Boot 服务器应用程序在端口 8080 上运行
演示客户端应用程序CxfTypeSafeClientBuilder
用于访问服务器服务
Keycloak - Spring Boot 交互工作正常,我可以从 Keycloak 接收令牌,如果我将令牌作为Authorization
标头传递,演示服务正在验证令牌。
我应该如何配置CxfTypeSafeClientBuilder
/RestClientBuilder
来处理我从 Keycloak 实例获得的 JWT 令牌?我是否必须建立自己的ClientResponseFilter
,如果是的话如何处理过期的令牌?
有没有我没有找到的现有实现/标准?
JAX-RS 网络服务接口:
@Path("/demo")
public interface IDemoService {
@GET
@Path("/test")
String test();
}
简单的 Spring Security 配置:
http.cors().and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionAuthenticationStrategy(sessionAuthenticationStrategy()).and().authorizeRequests().antMatchers("/**")
.authenticated();
编辑:从服务器获取初始访问和刷新令牌的新解决方法:
AccessTokenResponse tokens = AuthUtil.getAuthTokens("http://localhost:8180/auth", "share-server", "test", "test", "share-server-service-login");
String accessToken = tokens.getToken();
String refreshToken = tokens.getRefreshToken();
客户端进行服务调用,直到令牌过期:
URI apiUri = new URI("http://localhost:8080/services/");
RestClientBuilder client = new CxfTypeSafeClientBuilder().baseUri(apiUri).register(new TokenFilter(accessToken, refreshToken));
IDemoService service = client.build(IDemoService.class);
for (int i = 0; i < 200; i++) {
System.out.println("client: " + new Date() + " " + service.test());
Thread.sleep(10000);
}
TokenFilter 在访问令牌过期之前一直有效:
public static class TokenFilter implements ClientRequestFilter, ClientResponseFilter {
private String accessToken;
private String refreshToken;
public TokenFilter(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
@Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
if (responseContext.getStatus() == 401 && "invalid_token".equals(responseContext.getStatusInfo().getReasonPhrase())) {
// maybe handle send the refresh token... probalby should be handled earlier using the 'expires' value
}
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
if (accessToken != null && !accessToken.isEmpty()) {
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer" + " " + accessToken);
}
}
}
解决方案
找到了一个更好的解决方案,仅依赖于keycloak-authz-client:
String serverUrl = "http://localhost:8180/auth";
String realm = "share-server";
String clientId = "share-server-service-login";
String clientSecret = "e70752a6-8910-4043-8926-03661f43398c";
String username = "test";
String password = "test";
Map<String, Object> credentials = new HashMap<>();
credentials.put("secret", clientSecret);
Configuration configuration = new Configuration(serverUrl, realm, clientId, credentials, null);
AuthzClient authzClient = AuthzClient.create(configuration);
AuthorizationResource authorizationResource = authzClient.authorization(username, password);
URI apiUri = new URI("http://localhost:8080/services/");
RestClientBuilder client = new CxfTypeSafeClientBuilder().baseUri(apiUri).register(new TokenFilter(authorizationResource));
IDemoService service = client.build(IDemoService.class);
for (int i = 0; i < 200; i++) {
System.out.println("client: " + new Date() + " " + service.test());
Thread.sleep(10000);
}
authorizationResource.authorize()
将org.keycloak.authorization.client.util.TokenCallable.call()
在后台使用验证令牌到期时间并在必要时自动刷新令牌。
所以
String accessToken = authorize.getToken();
将始终是当前有效的令牌。
@Priority(Priorities.AUTHENTICATION)
public static class TokenFilter implements ClientRequestFilter {
private AuthorizationResource authorizationResource;
public TokenFilter(AuthorizationResource authorizationResource) {
this.authorizationResource = authorizationResource;
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
AuthorizationResponse authorize = authorizationResource.authorize();
String accessToken = authorize.getToken();
System.out.println(accessToken);
if (accessToken != null && !accessToken.isEmpty()) {
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer" + " " + accessToken);
}
}
}
推荐阅读
- r - 计算滞后一年的天数差异
- julia - 如何有效地利用 Julia 中的“构建”步骤?
- typescript - 为什么 Typescript 允许为接口定义返回不正确的普通对象
- node.js - 我可以同时运行 IIS 和 Node.JS 吗?
- javascript - lodash 隐式给出“_ 不是函数”错误
- r - 如何在 ggridges 中向 ridgeplot 添加垂直颜色渐变?
- c# - 如何在 C# 中将“YYYY-MM-DDThh:mmTZD”转换为 yyyy-MM-dd hh:mm:ss
- sql - 子查询访问中无法识别表别名
- android - 此 SIGSEGV 错误消息是否意味着问题发生在异步任务中
- ios - 在 IOS 13 Safari 上禁用捏缩放