Home > Net >  Keycloak Spring Implementation combining Roles
Keycloak Spring Implementation combining Roles

Time:01-18

I have a Problem that i cannot solve after researching alot.

I have a Keyloak with Clients(Application) and Roles that secure the Application.

Inside my Application i check with .hasRole()-Method if the Role of the User or other Application matches with the defined Role. Everything works excepted.

The Problem is i want to combine Roles and check them in the Application.

To access my Application the user should have the role 'read' AND 'write'.

In Spring the hasRole()-Method checks only one Role at a Time. The hasAnyRole()-Method checks if one of the Roles matches.

Is there any Method like say hasAllRoles? Which checks if all the Roles match?

One request is to solve that Problem only with Configuration but the implemented Method in the Application is hasRole() so i except that there is no possible way of solving this with only configuration on Keycloak or Application.properties inside the Application

CodePudding user response:

The easiest solution is probably to find a way to explain to the person who decided "to solve that Problem only with Configuration" that using @PreAuthorize meta-data is a better solution :

  • controllers code is not "polluted" with access-control: annotation are not inside methods body and are evaluated only if enabled in spring security (which makes no difference with access-control in conf)
  • access-control rules are close to access-point definition, which makes it much more readable (easy to grasp what is applied to a specific route and HTTP verb)
  • SpEL is much more powerful than config request matchers. You can even define your own DSL to write expressions like @PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") (this taken from the most advanced of my tutorials).
  • access-control can decorate any method of any @Component (including @Service and @Repository) and not only to public methods of @Controller decorated with @RequestMapping or one of its declination.

With SpEL, you can do what you need (read and write) plus define rules based on the accessed resource itself (who created it, what it is linked to,...)

To give a try:

  • add @EnableMethodSecurity to your security conf class
  • only define with request matchers what is accessible to anonymous and what requires authentication
  • add @PreAuthorize("hasRole('read') and hasRole('write')") just above your controller method

CodePudding user response:

In addition to what @ch4mp explained, I'd like to offer a few other principles to keep in mind.

First, your question. You can do hasAllRoles in two ways. The first is with AuthorizationManagers.allOf and the second is with SpEL:

http.authorizeHttpRequests((authorize) -> authorize
    .requestMatchers("/endpoint").access(
        allOf(hasAuthority("read"), hasAuthority("write"))
    )
)

and

@PreAuthorize("hasAuthority('read') and hasAuthority('write')")

Read on for some additional recommendations relative to your comment:

The Problem is i want to combine Roles and check them in the Application.

A Bean

A nice way to extract authorization logic into a component is to reference an authorization bean in your expression.

For example, you can do:

@Component("authz")
public final class MyAuthorizationDecider {
    public boolean check(MethodSecurityExpressionOperations operations) {
        // ... place authorization logic here
    }
}

And then you can do:

@PreAuthorize("@authz.check(#root)")

(If I'm not mistaken, you can still use @ch4mp's library with this approach, simply calling the library's DSL from a Java method instead of within a SpEL expression.)

Hierarchies

It's also the case that some permissions imply others. It may be the case for you that message:write implies message:read. In such a case, your expressions can be simplified by codifying this relationship in a RoleHierarchy instance.

At Login Time

At times, it can be helpful to map authorities at login time. For example, the role of USER might translate into message:read and ADMIN into message:read and message:write. It might go the other way as well. If the client granted message:read and message:write, perhaps this translates into a single permission of message:redact.

If you perform this translation at login time, it can allow for fewer computations at request time and a single location to reason about authorities being granted.

For example, instead of doing

@PreAuthorize("hasAuthority('message:read') and hasAuthority('message:write')")

or

.authorizeHttpRequests((authorize) -> authorize
    .requestMatchers("/message/redact").access(
        allOf(hasAuthority("message:read"), hasAuthority("message:write"))
    )
)

you'd do:

@PreAuthorize("hasAuthority('message:redact')")

or

.authorizeHttpRequests((authorize) -> authorize
    .requestMatchers("/message/redact").hasAuthority("message:redact")
)

Since you are using Keycloak, in this case, you'd consider a custom JwtGrantedAuthoritiesConverter if you are a Resource Server or GrantedAuthoritiesMapper if you are a Client to map the granted authorities from the Jwt to authorities that map to what you are doing in your app.

  • Related