我正在使用Apache CXF访问JAX-RS服务。通过Keycloak(OpenID Connect / OAuth2)完成用户身份验证。BearerAuthSupplier
正在自动处理refreshTokens
,并接收新的accessTokens
。但是,当会话过期或注销(例如,由admin注销)并收到401错误时,我该如何处理?我猜测必须有一种方法可以提供凭据,然后再使用它们进行实际的服务调用。
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";
String tokenUri = serverUrl + "/realms/" + realm + "/protocol/openid-connect/token";
Consumer consumer = new Consumer(clientId);
ResourceOwnerGrant grant = new ResourceOwnerGrant(username, password);
ClientAccessToken initial = OAuthClientUtils.getAccessToken(tokenUri, consumer, grant, true);
BearerAuthSupplier supplier = new BearerAuthSupplier();
supplier.setAccessToken(initial.getTokenKey());
supplier.setRefreshToken(initial.getRefreshToken());
supplier.setConsumer(consumer);
supplier.setAccessTokenServiceUri(tokenUri);
HTTPConduitConfigurer httpConduitConfigurer = new HTTPConduitConfigurer() {
@Override
public void configure(String name, String address, HTTPConduit c) {
c.setAuthSupplier(supplier);
}
};
Bus bus = BusFactory.getThreadDefaultBus();
bus.setExtension(httpConduitConfigurer, HTTPConduitConfigurer.class);
URI apiUri = new URI("http://localhost:8080/services/");
RestClientBuilder client = new CxfTypeSafeClientBuilder().baseUri(apiUri);
IDemoService service = client.build(IDemoService.class);
for (int i = 0; i < 200; i++) {
System.out.println("client: " + new Date() + " " + service.test());
Thread.sleep(5 * 60 * 1000);
}
javax.ws.rs.WebApplicationException:HTTP 401未经授权
WARNUNG: Interceptor for {http://service.server.share.scodi.ch/}IDemoService has thrown exception, unwinding now
org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException: OAuthServiceException invoking http://localhost:8080/services/demo/test: OAuthError[error='invalid_grant', errorDescription='Session not active', errorUri='null']
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.mapException(HTTPConduit.java:1400)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1389)
at org.apache.cxf.io.AbstractWrappedOutputStream.close(AbstractWrappedOutputStream.java:77)
at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)
at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:671)
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:63)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
at org.apache.cxf.jaxrs.client.AbstractClient.doRunInterceptorChain(AbstractClient.java:701)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl.doRunInterceptorChain(MicroProfileClientProxyImpl.java:165)
at org.apache.cxf.jaxrs.client.ClientProxyImpl.doChainedInvocation(ClientProxyImpl.java:899)
at org.apache.cxf.jaxrs.client.ClientProxyImpl.invoke(ClientProxyImpl.java:345)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl.invokeActual(MicroProfileClientProxyImpl.java:439)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl.access$000(MicroProfileClientProxyImpl.java:70)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl$Invoker.call(MicroProfileClientProxyImpl.java:458)
at org.apache.cxf.microprofile.client.cdi.CDIInterceptorWrapper$BasicCDIInterceptorWrapper.invoke(CDIInterceptorWrapper.java:43)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl.invoke(MicroProfileClientProxyImpl.java:435)
at com.sun.proxy.$Proxy19.test(Unknown Source)
at ch.scodi.share.server.ClientTest.main(ClientTest.java:78)
Caused by: org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException: OAuthError[error='invalid_grant', errorDescription='Session not active', errorUri='null']
at org.apache.cxf.rs.security.oauth2.client.OAuthClientUtils.getAccessToken(OAuthClientUtils.java:321)
at org.apache.cxf.rs.security.oauth2.client.OAuthClientUtils.refreshAccessToken(OAuthClientUtils.java:244)
at org.apache.cxf.rs.security.oauth2.client.OAuthClientUtils.refreshAccessToken(OAuthClientUtils.java:235)
at org.apache.cxf.rs.security.oauth2.client.BearerAuthSupplier.refreshAccessToken(BearerAuthSupplier.java:103)
at org.apache.cxf.rs.security.oauth2.client.BearerAuthSupplier.getAuthorization(BearerAuthSupplier.java:63)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.authorizationRetransmit(HTTPConduit.java:1529)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.processRetransmit(HTTPConduit.java:1461)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleRetransmits(HTTPConduit.java:1435)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1565)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1371)
... 16 more
javax.ws.rs.WebApplicationException: HTTP 401 Unauthorized
at org.apache.cxf.microprofile.client.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:33)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl.checkResponse(MicroProfileClientProxyImpl.java:183)
at org.apache.cxf.jaxrs.client.ClientProxyImpl.handleResponse(ClientProxyImpl.java:1002)
at org.apache.cxf.jaxrs.client.ClientProxyImpl.doChainedInvocation(ClientProxyImpl.java:907)
at org.apache.cxf.jaxrs.client.ClientProxyImpl.invoke(ClientProxyImpl.java:345)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl.invokeActual(MicroProfileClientProxyImpl.java:439)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl.access$000(MicroProfileClientProxyImpl.java:70)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl$Invoker.call(MicroProfileClientProxyImpl.java:458)
at org.apache.cxf.microprofile.client.cdi.CDIInterceptorWrapper$BasicCDIInterceptorWrapper.invoke(CDIInterceptorWrapper.java:43)
at org.apache.cxf.microprofile.client.proxy.MicroProfileClientProxyImpl.invoke(MicroProfileClientProxyImpl.java:435)
at com.sun.proxy.$Proxy19.test(Unknown Source)
at ch.scodi.share.server.ClientTest.main(ClientTest.java:78)
[OAuth客户端应始终被编写为在调用API时检查401响应,并刷新令牌并在可能的情况下使用新令牌重试API请求,否则触发重新认证。
我倾向于通过以下两个类来管理此问题:
以上示例是针对Web UI的,但可以为任何OAuth客户端流或技术堆栈编写。
这是我想出的解决方案,请登录以下用例:
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";
String tokenUri = serverUrl + "/realms/" + realm + "/protocol/openid-connect/token";
ResourceOwnerGrant grant = new ResourceOwnerGrant(username, password);
AuthSupplier supplier = new AuthSupplier(tokenUri, clientId, clientSecret, grant);
HTTPConduitConfigurer httpConduitConfigurer = new HTTPConduitConfigurer() {
@Override
public void configure(String name, String address, HTTPConduit c) {
// don't do authentication on token URI
if (!tokenUri.equals(address)) {
c.setAuthSupplier(supplier);
}
}
};
Bus bus = BusFactory.getThreadDefaultBus();
bus.setExtension(httpConduitConfigurer, HTTPConduitConfigurer.class);
... REST-Client Code ...
public static class AuthSupplier extends BearerAuthSupplier {
private WebClient webClient = null;
private AccessTokenGrant grant;
public AuthSupplier(String tokenUri, String clientId, String clientSecret, AccessTokenGrant grant) {
grant = grant;
webClient = WebClient.create(tokenUri);
setAccessTokenServiceUri(tokenUri);
setConsumer(new Consumer(clientId, clientSecret));
}
@Override
public String getAuthorization(AuthorizationPolicy authPolicy, URI currentURI, Message message, String fullHeader) {
String authorization = null;
try {
authorization = super.getAuthorization(authPolicy, currentURI, message, fullHeader);
} catch (OAuthServiceException e) {
System.out.println(e.getError().getState() + " " + e.getMessage());
}
if (authorization == null) {
// refresh token expired or session expired (Stale token) or session was logged-out (manually, service-restart), do new login
login();
// try to get authorization again after login
authorization = super.getAuthorization(authPolicy, currentURI, message, null);
}
return authorization;
}
private void login() {
ClientAccessToken accessToken = OAuthClientUtils.getAccessToken(webClient, getConsumer(), grant, false);
setAccessToken(accessToken.getTokenKey());
setRefreshToken(accessToken.getRefreshToken());
}
}