1 year ago

#64571

test-img

Andre Carrilho

Getting 401 Unauthorized when activating Keycloak policy-enforcer

I'm trying to integrate with Keycloak Authorization Services (resource, scopes, policies, permissions). I'm using version 16.1.0.

For starters I have created a realm and a client. For the client I have enabled Direct Access Grants (to allow generating tokens directly from curl), Access Type is set to confidentails and also enabled Authorization.

enter image description here

The Authorization setup is as follows:

  • I have a Products Resource to which the scopes get, post, put and delete are configured
  • I have Policies associating specific roles to them (customer can get, service rep can get, post and put and admin can get, post, put and delete)
  • Created Permissions for each of the roles/policies, ie, Customer permission has get scope and Customer policy associated. Service Rep has post/put scopes and Service Rep Policy and Admin permission has get/post/put/delete scopes and Admin Policy

I have evaluated these configs on Keycloak and they all work fine.

I then created a Spring Boot app with Spring Security and created a Products REST controller to test. Here is my config:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class KeycloakConfig extends KeycloakWebSecurityConfigurerAdapter {
    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);

        http.cors().and().csrf().disable()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
    }

    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public KeycloakConfigResolver keycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }
}

The ProductController:

@RestController
public class ProductController {
    public ProductController() {
    }

    @GetMapping("/products")
    public ResponseEntity getAll(){
        return ResponseEntity.ok("Returning all products");
    }

    @GetMapping("/product/{id}")
    public ResponseEntity getById(@PathVariable Integer id) {
        return ResponseEntity.ok("Returning product with id " + id);
    }

    @PostMapping(value = "/product")
    public ResponseEntity create(){
        return ResponseEntity.created(URI.create("http://localhost:8080/product"))
                .body("item created successfully");
    }

    @RequestMapping(value = "/product", method = RequestMethod.PUT)
    public ResponseEntity update(){
        return ResponseEntity.noContent().build();
    }

    @RequestMapping(value = "/product/{id}", method = RequestMethod.DELETE)
    public ResponseEntity delete(@PathVariable String id){
        return ResponseEntity.noContent().build();
    }
}

The application.properties is set as follows:

keycloak.realm=my-realm
keycloak.auth-server-url=http://localhost:8180/auth
keycloak.ssl-required=external
keycloak.credentials.secret=my-secret
keycloak.resource=my-client
keycloak.public-client=false

keycloak.policy-enforcer-config.enforcement-mode=ENFORCING
keycloak.policy-enforcer-config.paths[0].name=Products Resource
keycloak.policy-enforcer-config.paths[0].path=/products
keycloak.policy-enforcer-config.paths[0].methods[0].method=GET
keycloak.policy-enforcer-config.paths[0].methods[0].scopes=get

I then use curl to generate an access token:

curl -v -L -X POST 'http://localhost:8180/auth/realms/my-realm/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=my-client' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_secret=my-secret' \
--data-urlencode 'scope=openid' \
--data-urlencode 'username=my-username' \
--data-urlencode 'password=my-password'

I get the access token successfully. I use this access token to fetch the Products controller:

curl -v -L 'http://localhost:8082/products' -H 'Authorization: Bearer {access_token}'

I get the following response:

{"timestamp":"2022-01-18T10:17:38.223+00:00","status":500,"error":"Internal Server Error","path":"/products"}

I even tried getting an RPT token which permissions already included by doing the following curl:

curl -X POST \
http://localhost:8180/auth/realms/OnePortalFramework/protocol/openid-connect/token \
-H "Authorization: Bearer {access_token}" \
--data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
--data "audience=opf-sb-ms1"

And using this token I get the exact some error.

I debugged the app and it fails when the keycloak adapter tries to get the permissions from the server (PolicyEnforcer.java, line 174 resource = protectedResource.findByName(resourceName);). Apparently it does not have a token (it doesn't use the token I passed when making the ProductsController request) and just fails.

Stacktrace if it helps:

java.lang.RuntimeException: Failed to obtain policy enforcer
    at org.keycloak.adapters.KeycloakDeployment.getPolicyEnforcer(KeycloakDeployment.java:553) ~[keycloak-adapter-core-16.1.0.jar:16.1.0]
    at org.keycloak.adapters.AuthenticatedActionsHandler.corsRequest(AuthenticatedActionsHandler.java:108) ~[keycloak-adapter-core-16.1.0.jar:16.1.0]
    at org.keycloak.adapters.AuthenticatedActionsHandler.handledRequest(AuthenticatedActionsHandler.java:54) ~[keycloak-adapter-core-16.1.0.jar:16.1.0]
    at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter.doFilter(KeycloakAuthenticatedActionsFilter.java:69) ~[keycloak-spring-security-adapter-16.1.0.jar:16.1.0]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter.doFilter(KeycloakSecurityContextRequestFilter.java:92) ~[keycloak-spring-security-adapter-16.1.0.jar:16.1.0]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter.successfulAuthentication(KeycloakAuthenticationProcessingFilter.java:214) ~[keycloak-spring-security-adapter-16.1.0.jar:16.1.0]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:232) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) ~[keycloak-spring-security-adapter-16.1.0.jar:16.1.0]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.3.10.jar:5.3.10]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.3.10.jar:5.3.10]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.3.10.jar:5.3.10]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.5.2.jar:5.5.2]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.3.10.jar:5.3.10]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.3.10.jar:5.3.10]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.10.jar:5.3.10]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.10.jar:5.3.10]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.3.10.jar:5.3.10]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.3.10.jar:5.3.10]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:711) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:461) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:385) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:313) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:403) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:249) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:344) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:144) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
    at java.base/java.lang.Thread.run(Thread.java:831) ~[na:na]
Caused by: java.lang.RuntimeException: Could not find resource
    at org.keycloak.authorization.client.util.Throwables.retryAndWrapExceptionIfNecessary(Throwables.java:91) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.resource.ProtectedResource.find(ProtectedResource.java:232) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.resource.ProtectedResource.findByName(ProtectedResource.java:131) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.adapters.authorization.PolicyEnforcer.configureDefinedPaths(PolicyEnforcer.java:174) ~[keycloak-adapter-core-16.1.0.jar:16.1.0]
    at org.keycloak.adapters.authorization.PolicyEnforcer.configurePaths(PolicyEnforcer.java:160) ~[keycloak-adapter-core-16.1.0.jar:16.1.0]
    at org.keycloak.adapters.authorization.PolicyEnforcer.<init>(PolicyEnforcer.java:76) ~[keycloak-adapter-core-16.1.0.jar:16.1.0]
    at org.keycloak.adapters.KeycloakDeploymentBuilder$1.call(KeycloakDeploymentBuilder.java:154) ~[keycloak-adapter-core-16.1.0.jar:16.1.0]
    at org.keycloak.adapters.KeycloakDeploymentBuilder$1.call(KeycloakDeploymentBuilder.java:147) ~[keycloak-adapter-core-16.1.0.jar:16.1.0]
    at org.keycloak.adapters.KeycloakDeployment.getPolicyEnforcer(KeycloakDeployment.java:551) ~[keycloak-adapter-core-16.1.0.jar:16.1.0]
    ... 64 common frames omitted
Caused by: org.keycloak.authorization.client.util.HttpResponseException: Unexpected response from server: 401 / Unauthorized
    at org.keycloak.authorization.client.util.HttpMethod.execute(HttpMethod.java:95) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.util.HttpMethodResponse$2.execute(HttpMethodResponse.java:50) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.util.TokenCallable.clientCredentialsGrant(TokenCallable.java:123) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.util.TokenCallable.obtainTokens(TokenCallable.java:154) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.util.TokenCallable.call(TokenCallable.java:64) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.resource.ProtectedResource.createFindRequest(ProtectedResource.java:296) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.resource.ProtectedResource.access$300(ProtectedResource.java:38) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.resource.ProtectedResource$5.call(ProtectedResource.java:225) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.resource.ProtectedResource$5.call(ProtectedResource.java:222) ~[keycloak-authz-client-16.1.0.jar:16.1.0]
    at org.keycloak.authorization.client.resource.ProtectedResource.find(ProtectedResource.java:230) ~[keycloak-authz-client-16.1.0.jar:16.1.0]

Does anyone have ideas to what this could be?

spring

scope

authorization

keycloak

policy

0 Answers

Your Answer

Accepted video resources