I'am trying to make custom SPI with custom REST endpoint, which should authenticate and authorise incoming requests by evaluating permissions on requested resources. With help of debugger I found out, that I should use class TokenEndpoint.java and call method permissionGrant() inside my REST-handler method, but when I try to create instance of TokenEndpoint, I've got error with REASTEASY and Keycloak crashes. Do you have any examples, how can I do this?
I would suggest to have a look at the following project: keycloak-avatar-minio-extension.
First you have to implement a RealmResourceProdiverFactory
and a RealmResourceProcider
.
Secondly, you need a resource that is returned when the getResource()
in your RealmResourceProvider
is triggered.
Your resource is a class in which you define your endpoint. To check the authorization you can create a method like this:
private AuthenticationManager.AuthResult resolveAuthentication(KeycloakSession session) {
AppAuthManager appAuthManager = new AppAuthManager();
RealmModel realm = session.getContext().getRealm();
AuthenticationManager.AuthResult authResult = appAuthManager.authenticateIdentityCookie(session, realm);
if (authResult != null) {
return authResult;
}
return null;
}
This method is called in the constructor and sets the private final AuthenticationManager.AuthResult auth;
variable inside your Resource.
Now, inside your endpoint implementation you can simply check if auth
is not null, or, if needed, do more sophisticated stuff like inspecting the user or the token which is available in your auth
variable.
As others, I needed this in a custom rest endpoint that we put in our keycloak instance.
I solved this problem in steps:
- Get resources of a given type
- Get Permission evaluator for those resources
- Evaluate, filter and return response
Here is the code:
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.ws.rs.GET;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.common.DefaultEvaluationContext;
import org.keycloak.authorization.common.UserModelIdentity;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.permission.evaluator.Evaluators;
import org.keycloak.authorization.permission.evaluator.PermissionEvaluator;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager.AuthResult;
public class DemoResource {
private final KeycloakSession session;
private final AuthResult auth;
public DemoResource(KeycloakSession session) {
this.session = session;
this.auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate();
}
@GET
@Path("/demoresources")
@Produces(MediaType.APPLICATION_JSON)
public Set<Resource> listDemoResources() {
if (this.auth == null || this.auth.getToken() == null) {
throw new NotAuthorizedException("Bearer");
}
String clientId = ""; // Client id which resources are defined.
String resourceType = ""; // Get resources by type.
final RealmModel realm = this.session.getContext().getRealm();
final AuthorizationProvider authorizationProvider = this.session.getProvider(AuthorizationProvider.class);
final ClientModel client = this.session.clientStorageManager().getClientByClientId(realm, clientId);
final ResourceServer resourceServer = authorizationProvider
.getStoreFactory()
.getResourceServerStore()
.findById(client.getId());
final Evaluators evaluators = authorizationProvider.evaluators();
final AuthorizationRequest request = new AuthorizationRequest();
request.setSubjectToken(this.auth.getToken().toString());
// Get resources by type and put them in a map
final Map<String, Resource> resourceMap = authorizationProvider
.getStoreFactory()
.getResourceStore()
.findByType(resourceType, resourceServer.getId())
.stream()
.collect(Collectors.toMap(Resource::getId, r -> r));
// Generate a permission evaluator for all resources of given type
final PermissionEvaluator permissionEvaluator = evaluators
.from(
resourceMap
.entrySet()
.stream()
.map(r -> new ResourcePermission(r.getValue(), Collections.emptyList(), resourceServer))
.collect(Collectors.toList()),
new DefaultEvaluationContext(new UserModelIdentity(realm, this.auth.getUser()), this.session));
// Evaluate permission and put them in a result set.
final Collection<Permission> permissions = permissionEvaluator.evaluate(resourceServer, request);
final Set<Resource> resources = new HashSet<>();
for (final Permission permission : permissions) {
if (resourceMap.containsKey(permission.getResourceId())) {
resources.add(resourceMap.get(permission.getResourceId()));
}
}
return resources;
}
}
Provider
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resource.RealmResourceProvider;
public class DemoProvider implements RealmResourceProvider {
private KeycloakSession session;
public DemoProvider(KeycloakSession session) {
this.session = session;
}
@Override
public void close() {
}
@Override
public Object getResource() {
return new DemoResource(this.session);
}
}
ProviderFactory
import org.keycloak.Config.Scope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resource.RealmResourceProviderFactory;
public class DemoProviderFactory implements RealmResourceProviderFactory {
public static final String ID = "demo";
@Override
public RealmResourceProvider create(KeycloakSession session) {
return new DemoProvider(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
}
Try to look at these java examples from the documentation. https://www.keycloak.org/docs/latest/authorization_services/index.html#_authorization_quickstarts
I recently have been confronted with the same issue. Generally, Val's answer is correct. In order to get his code to work, we had to add dependency to keycloak-services module. You need both compile-time dependency (do not add jar into the ear libs) and module dependency. Good example of how to do this is https://github.com/dteleguin/beercloak
The spots to look at:
- https://github.com/dteleguin/beercloak/blob/master/beercloak-ear/src/main/application/META-INF/jboss-deployment-structure.xml
- https://github.com/dteleguin/beercloak/blob/master/beercloak-module/src/main/java/beercloak/resources/AbstractAdminResource.java - setup method
- https://github.com/dteleguin/beercloak/blob/master/beercloak-module/src/main/java/beercloak/providers/BeerResourceProvider.java - getResource method
In new versions authentication does not work in constructor, it has to be done in getResource()
method.
© 2022 - 2024 — McMap. All rights reserved.