Reputation: 1438
I try to use Keycloak with spring boot but i'm facing problem. Authentication works fine with the adapter but not the authorizations.
This is my configuration :
keycloak.realm = master
keycloak.auth-server-url = http://127.0.0.1:8080/auth
keycloak.ssl-required = none
keycloak.resource = pactng
keycloak.credentials.secret = **************************
keycloak.use-resource-role-mappings = true
keycloak.principal-attribute=preferred_username
keycloak.bearer-only = true
keycloak.policy-enforcer-config.userManagedAccess=org.keycloak.representations.adapters.config.PolicyEnforcerConfig.UmaProtocolConfig
keycloak.policy-enforcer-config.lazyLoadPaths=true
keycloak.policy-enforcer-config.paths[0].path=/*
keycloak.policy-enforcer-config.paths[0].methods[0].method=GET
keycloak.policy-enforcer-config.paths[0].methods[0].scopes[0]=urn:pactng:scopes:read
keycloak.policy-enforcer-config.paths[0].methods[1].method=POST
keycloak.policy-enforcer-config.paths[0].methods[1].scopes[0]=urn:pactng:scopes:create
keycloak.policy-enforcer-config.paths[0].methods[2].method=PUT
keycloak.policy-enforcer-config.paths[0].methods[2].scopes[0]=urn:pactng:scopes:update
keycloak.policy-enforcer-config.paths[0].methods[3].method=PATCH
keycloak.policy-enforcer-config.paths[0].methods[3].scopes[0]=urn:pactng:scopes:update
keycloak.policy-enforcer-config.paths[0].methods[4].method=DELETE
keycloak.policy-enforcer-config.paths[0].methods[4].scopes[0]=urn:pactng:scopes:delete
I'm not defining role here has i want authorization to be dynamically computed by the adapter based on the request and permission contain inside the access token.
And this is my KeycloakConfig for spring security :
@KeycloakConfiguration
public class KeycloakConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Map keycloak role to spring ROLE_<ROLE>
* @param auth
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defined the strategy use by keycloak for user session. We need authorization system so we use a session for a confidential client.
* Can be NullAuthenticatedSessionStrategy for bearerClient
* @return
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* Use spring boot application.properties instead of keycloak.json to retrieve
* connexion informations
*/
@Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/*").authenticated()
.anyRequest().permitAll();
}
}
I test by retrieving token with the following request. UMA 2 is activated as it's not seem's possible to have authorizations enable without it with this adapter.
# Get Access Token for a user.
POST http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token
{
client_id "<CLIENT_ID>"
client_secret "<CLIENT_SECRET>"
username "<USERNAME>"
password "<USER_PASSWORD>"
grant_type "password"
}
This give me an access token that i use in the following request.
# Try to get access to the resource endpoint
GET http://localhost:8081/api/v1/<resources>
Headers: authorization: Bearer <ACCESS_TOKEN>
This give me a 401 WWW-Authenticate following the UMA2 specification with a ticket. I then contact keycloak to get a RPT :
# Get RPT from Keycloak
POST http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token
Headers: authorization: Bearer <ACCESS_TOKEN>
{
grant_type: "urn:ietf:params:oauth:grant-type:uma-ticket"
ticket: <TICKET>
}
Keycloak give me a RPT which is a access token with permissions includes inside it. I then retry to get the ressource but with this RPT
# Try to get access to the resource endpoint
GET http://localhost:8081/api/v1/<resources>
Headers: authorization: Bearer <RPT>
But instead of having access to the resource, the adapter give me again a 401 WWW-Authenticate.
I digged into the code and found that the KeycloakSecurityContext is always null inside AbstractPolicyEnforcer:
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
EnforcementMode enforcementMode = getEnforcerConfig().getEnforcementMode();
// Always return null
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
This make the code to be trapped every time inside this portion of code :
if (securityContext == null) {
if (!isDefaultAccessDeniedUri(request)) {
if (pathConfig != null) {
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
return createEmptyAuthorizationContext(true);
} else {
challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
}
} else {
handleAccessDenied(httpFacade);
}
}
return createEmptyAuthorizationContext(false);
}
So my question, after this long explanation, is why is this securityContext not populated? Am i missing something?
Upvotes: 5
Views: 6150
Reputation: 1241
@Scandinave I add more detail about your answer: Must config proxy enforcer with 'keycloak' prefix in our spirng-profile for Bean default init KeycloakSecurityContext. After that, when you want to overwrite KeycloakResolverConfig for multiple realm setting, just init KeycloakDeployment object with Enforcer config base on 'Client realm' sent from header of http request and replace into remaining existed Keycloak deployment:
class Xxx implements KeycloakConfigResolver {
@Override
public KeycloakDeployment resolve(HttpFacade.Request request) {
String realm = request.getHeader(KEY);
KeycloakDeployment deployment = cache.get(realm); // cache is inmemory cache...
if (null == deployment) {
var config = getConf(realm); // init at bootstrap all objects copy look like KeycloakSpringBootProperties class but prefix yaml by 'realm' name
if (config == null) throw new SecurityException(String.format("PolicyEnforcer for realm %s is null", realm));
// Create new KeycloakDeployment from AdapterConfig
deployment = KeycloakDeploymentBuilder.build(config);
cache.put(realm, deployment);
}
return deployment;
}
}
In your yaml:
## Default for init KeycloakSecurityContext
keycloak:
policy-enforcer-config:
enforcement-mode: ENFORCING
lazy-load-paths: true
paths:
- path: "/api/shipping/docs/*"
enforcement-mode: DISABLED
- path: "/api/shipping/outbound/*"
enforcement-mode: DISABLED
- path: "/api/shipping/internal/*"
enforcement-mode: ENFORCING
securityConstraints:
- authRoles: '*'
securityCollections:
- patterns:
- /api/shipping/test/*
- /api/shipping/internal/*
auth-server-url: https://your.keycoak.vn
realm: master
bearer-only: 'true'
use-resource-role-mappings: false
ssl-required: external
resource: internal-vn
credentials:
secret: xxx
## Prefix KeycloakDeployment 1
external-policy:
policy-enforcer-config:
enforcement-mode: ENFORCING # ENFORCING, PERMISSIVE, DISABLED
paths:
- path: "/error"
enforcement-mode: DISABLED
- path: "/api/shipping/docs/*"
enforcement-mode: DISABLED
- path: "/api/shipping/outbound/*"
enforcement-mode: DISABLED
- path: "/api/shipping/internal/*"
enforcement-mode: ENFORCING
- path: "/api/shipping/test/testing-role"
enforcement-mode: ENFORCING
securityConstraints:
- authRoles: "*"
securityCollections:
- patterns:
- "/*"
auth-server-url: https://your.keycoak.vn
realm: ctv-vn
bearer-only: 'true'
use-resource-role-mappings: true
ssl-required: external
resource: customer-service
credentials:
secret: xxx
## Prefix KeycloakDeployment 2
internal-policy:
policy-enforcer-config:
enforcement-mode: ENFORCING
lazy-load-paths: true
paths:
- path: "/api/shipping/docs/*"
enforcement-mode: DISABLED
- path: "/api/shipping/outbound/*"
enforcement-mode: DISABLED
- path: "/api/shipping/internal/*"
enforcement-mode: PERMISSIVE
securityConstraints:
- authRoles: '*'
securityCollections:
- patterns:
- /api/shipping/test/*
- /api/shipping/internal/*
auth-server-url: https://your.keycoak.vn
realm: master
bearer-only: 'true'
use-resource-role-mappings: false
ssl-required: external
resource: internal-vn
credentials:
secret: xxx
Upvotes: 0
Reputation: 1438
Ok, I found the solution. It's seems that this adapter want a securityContraints on a role to do it's job. So i add this to make it works:
keycloak.securityConstraints[0].authRoles[0]=*
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/*
This basically tell it to handle all role on all paths.
P.S Stay with JAVA 11 because it's not compatible with latest JDK version.
Upvotes: 6