Keycloak custom SPI REST Endpoint with authorization
Asked Answered
F

4

10

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?

Fancier answered 1/2, 2019 at 15:53 Comment(0)
F
6

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.

Fellers answered 18/9, 2019 at 8:28 Comment(1)
Is it possible to get the authorization info of another client with this?Suckle
R
4

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;
    }
}
Retainer answered 21/5, 2021 at 12:26 Comment(0)
A
0

Try to look at these java examples from the documentation. https://www.keycloak.org/docs/latest/authorization_services/index.html#_authorization_quickstarts

Agrippina answered 1/2, 2019 at 20:15 Comment(3)
Thanks for your reply, but I need to modify Keycloak itself without using Keycloak client. I need custom REST endpoint like in this example keycloak.org/docs/latest/server_development/… . I can authenticate user and even get his roles, but I need also authorization to get permissions on resourses (Authorization tab in Client settings).Fancier
@VyacheslavSamsonov we are facing the same issue if you are able to solve the issue it will be of great help if you could please share your solution with the community, we are also trying to secure a custom rest endpoint ..using an access tokenClad
This response does not answer the question.Brunt
P
0

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:

  1. https://github.com/dteleguin/beercloak/blob/master/beercloak-ear/src/main/application/META-INF/jboss-deployment-structure.xml
  2. https://github.com/dteleguin/beercloak/blob/master/beercloak-module/src/main/java/beercloak/resources/AbstractAdminResource.java - setup method
  3. 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.

Postal answered 21/7, 2020 at 7:59 Comment(2)
Hi. I have tried AuthenticationManager.AuthResult authResult = appAuthManager.authenticateIdentityCookie(session, realm); and new AppAuthManager.BearerTokenAuthenticator(session).authenticate(); and my session is there but authResult is always null! Any one know why? This is inside a RealmResourceProvider and I set my bearer tokenVichyssoise
user4447899 Had the same problem. Solved it. Cause: used a token generated for another realm than I was checking the authentication for.Fulcher

© 2022 - 2024 — McMap. All rights reserved.