Reputation: 51
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?
@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();
}
@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);
}
@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);
}
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;
}
}
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
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)
Upvotes: 3
Views: 6256
Reputation: 608
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.
Upvotes: 1
Reputation: 8413
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.1JqM
The 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**
https://datatracker.ietf.org/doc/html/rfc6750#section-2.1
Upvotes: 0