'How do i pass a Bearer Token dynamically with Quarkus Microprofile Rest Client?
I'm trying to call a Keycloak Admin REST endpoint to create a keycloak user in my code and it requires the request to have a Bearer token added to the Authorization header. I need a way to pass the token dynamically to the rest client.
I have to call another service to generate the token.
Here's what I've tried and the error that i got. What could be wrong or is there a better way to do this?
SignUpService.java
@ApplicationScoped
public class SignUpService {
private static final Logger LOG = Logger.getLogger(SignUpService.class);
@Inject
@RestClient
KeycloakClientAdmin keycloakClientAdmin;
public Response create(KeycloakUserDTO keyCloakUser) {
...
try {
keycloakClientAdmin.createKeycloakUser(keyCloakUser);
} catch (Exception e) {
LOG.info("Unable to create new user");
e.printStackTrace();
}
}
return Response.ok().build();
}
KeycloakClientAdmin.java
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RegisterRestClient(configKey = "config.api.keycloak.admin")
@RegisterClientHeaders(KeycloakClientHeader.class)
public interface KeycloakClientAdmin {
@POST
@Path("/admin/realms/{realm}/users")
public void createKeycloakUser(keycloakUserDTO user);
}
KeycloakClient.java
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@RegisterRestClient(configKey = "config.api.keycloak.token")
public interface KeycloakClient {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Path("/realms/master/protocol/openid-connect/token")
public TokenRepresentation createToken(MultivaluedMap<String, String> formMap);
}
KeycloakClientHeader.java
The token was succesfully created here,I was able to log it, but from the error(status code 401) i got it looks like it wasn't added to the header. The stack trace is at the end.
@ApplicationScoped
public class KeycloakClientHeader implements ClientHeadersFactory{
private static final Logger LOG = Logger.getLogger(KeycloakClientHeader.class);
@Inject
@RestClient
KeycloakClient keycloakClient;
@Override
public MultivaluedMap<String, String> update(
MultivaluedMap<String, String> mm1,MultivaluedMap<String, String> mm2) {
MultivaluedMap<String, String> result = new MultivaluedMapImpl<>();
MultivaluedMap<String, String> tokenRequest = new MultivaluedMapImpl<>();
tokenRequest.add("client_id", "admin-cli");
tokenRequest.add("username", admin);
tokenRequest.add("password", password);
tokenRequest.add("grant_type", "password");
String token = null;
try {
token = keycloakClient.createToken(tokenRequest).getAccess_token();
LOG.info("Token: " + token);
} catch (Exception e) {
LOG.info("Unable to create token");
e.printStackTrace();
}
result.add("Authorization", "Bearer " + token);
return result;
}
}
application.properties file
config.api.keycloak.token/mp-rest/url=http://localhost:9000
config.api.keycloak.token/mp-rest/scope=javax.inject.Singleton
config.api.keycloak.admin/mp-rest/url=http://localhost:9000
config.api.keycloak.admin/mp-rest/scope=javax.inject.Singleton
The error:
Unable to create new user
--
org.jboss.resteasy.client.exception.ResteasyWebApplicationException: Unknown error, status code 401
--
at org.jboss.resteasy.client.exception.WebApplicationExceptionWrapper.wrap(WebApplicationExceptionWrapper.java:107)
--
at org.jboss.resteasy.microprofile.client.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:21)
--
at org.jboss.resteasy.microprofile.client.ExceptionMapping$HandlerException.mapException(ExceptionMapping.java:41)
--
at org.jboss.resteasy.microprofile.client.ProxyInvocationHandler.invoke(ProxyInvocationHandler.java:153)
--
at com.sun.proxy.$Proxy157.createKeycloakUser(Unknown Source)
--
at farm.everyfarmer.mobile.useraccount.service.SignUpService.create(SignUpService.java:94)
--
at farm.everyfarmer.mobile.useraccount.service.SignUpService_Subclass.create$$superforward1(SignUpService_Subclass.zig:137)
--
at farm.everyfarmer.mobile.useraccount.service.SignUpService_Subclass$$function$$2.apply(SignUpService_Subclass$$function$$2.zig:33)
--
at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
--
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
--
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49
--
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
--
at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
--
at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
--
at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
--
at farm.everyfarmer.mobile.useraccount.service.SignUpService_Subclass.create(SignUpService_Subclass.zig:293)
--
at farm.everyfarmer.mobile.useraccount.service.SignUpService_ClientProxy.create(SignUpService_ClientProxy.zig:157)
--
at farm.everyfarmer.mobile.useraccount.resource.SignUpResource.createAcccount(SignUpResource.java:45)
--
at farm.everyfarmer.mobile.useraccount.resource.SignUpResource_Subclass.createAcccount$$superforward1(SignUpResource_Subclass.zig:94)
--
at farm.everyfarmer.mobile.useraccount.resource.SignUpResource_Subclass$$function$$1.apply(SignUpResource_Subclass$$function$$1.zig:33)
--
at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
--
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
--
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
--
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
--
at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
--
at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
--
at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
--
at farm.everyfarmer.mobile.useraccount.resource.SignUpResource_Subclass.createAcccount(SignUpResource_Subclass.zig:158)
--
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
--
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
--
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
--
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
--
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
--
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
--
at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
--
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
--
at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
--
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
--
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
--
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
--
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:408)
--
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:69)
--
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
--
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
--
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
--
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
--
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
--
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
--
at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
--
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:138)
--
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:93)
--
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$13.runWith(VertxCoreRecorder.java:536)
--
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
--
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
--
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
--
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
--
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
--
at java.base/java.lang.Thread.run(Thread.java:829)
Solution 1:[1]
Your mistake is having double space, instead of 1 in bearer:
HttpHeaders.AUTHORIZATION, "Bearer " + tokens.getAccessToken());
vs
result.add("Authorization", "Bearer " + token); <-- Has double space
Fix: result.add("Authorization", "Bearer " + token);
The specification is clear that it should be 1 Single Space:
2.1. Authorization Request Header Field
When sending the access token in the "Authorization" request header field defined by HTTP/1.1 [RFC2617], the client uses the "Bearer"
authentication scheme to transmit the access token.For example:
GET /resource HTTP/1.1 Host: server.example.com Authorization: Bearer mF_9.B5f-4.1JqMThe syntax of the "Authorization" header field for this scheme
follows the usage of the Basic scheme defined in Section 2 of
[RFC2617]. Note that, as with Basic, it does not conform to the
generic syntax defined in Section 1.2 of [RFC2617] but is compatible
with the general authentication framework being developed for HTTP 1.1 [HTTP-AUTH], although it does not follow the preferred practice outlined therein in order to reflect existing deployments. The syntax for Bearer credentials is as follows:
**b64token = 1*( ALPHA / DIGIT /
"-" / "." / "_" / "~" / "+" / "/" ) *"="
credentials = "Bearer" 1*SP b64token**
Solution 2:[2]
Alternatively, you could just add the keycloak-admin-client module dependency to your project and you don't have to worry about managing most of what you are doing yourself. It is done for you.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | JCompetence |
| Solution 2 | Arthur |
