Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions articles/flow/integrations/spring/oauth2.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,80 @@
The [methodname]`oauth2LoginPage(String)` method is a shortcut that defaults the post-logout redirect URL to `{baseUrl}`.


[[keycloak-role-mapping]]
[role="since:com.vaadin:vaadin@V25.4"]
== Keycloak Role Mapping

Keycloak puts the roles of a user into the access token rather than into the ID token, and it does so in the `realm_access` and `resource_access` claims, which aren't part of the OpenID Connect specification. Spring Security maps neither of them, so `@RolesAllowed("admin")` and `hasRole("admin")` don't match a Keycloak role named `admin`, and every user is rejected.

Mapping those roles is opt-in per security filter chain. Enable it by calling the [methodname]`keycloakRoleMapping()` method of [classname]`VaadinSecurityConfigurer`:

.Enable Keycloak Role Mapping
[source,java]
----
<source-info group="VaadinSecurityConfigurer"></source-info>
@Configuration
class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
configurer.oauth2LoginPage("/oauth2/authorization/keycloak")
.keycloakRoleMapping(); // (1)
});
return http.build();
}
}
----

<1> Decodes the access token of the authenticated user and maps its roles to granted authorities.

Views and services can then be guarded with the plain Keycloak role name:

[source,java]
----
@Route("admin")
@RolesAllowed("admin") // Matches the Keycloak realm role "admin"
public class AdminView extends VerticalLayout {
}
----

The mapping grants the following authorities:

- The realm roles from the `realm_access` claim of the access token.
- The roles that the `resource_access` claim grants for the client ID of the current client registration. Roles that it grants for other clients are ignored.
- The scopes of the access token, prefixed with `SCOPE_`, as the default user service grants them.
- The [classname]`OidcUserAuthority` of the authenticated user, built from the ID token and the userinfo claims, as the default user service grants it.

Check failure on line 159 in articles/flow/integrations/spring/oauth2.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'userinfo'? Raw Output: {"message":"[Vale.Spelling] Did you really mean 'userinfo'?","location":{"path":"articles/flow/integrations/spring/oauth2.adoc","range":{"start":{"line":159,"column":97},"end":{"line":159,"column":105}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Realm roles and client roles both become role authorities that use the role prefix of the application, which is `ROLE_` unless a [classname]`GrantedAuthorityDefaults` bean defines another prefix.

[NOTE]
====
The [methodname]`keycloakRoleMapping()` method works only together with [methodname]`oauth2LoginPage()` and its overloads. Without a login page for OAuth2 authentication, it logs a warning and has no effect.
====

Verifying the access token requires the JSON Web Key Set (JWKS) of the provider, which a client registration either resolves from its `issuer-uri` or takes from an explicitly configured `jwk-set-uri`. If the registration has no JWKS URI, or if the access token isn't a JWT that the application can decode, the login still succeeds, but the user is mapped without any roles. Both cases are logged at debug level.

When the mapping is enabled, the configurer builds an [classname]`OidcUserService` for the OAuth2 login and shares it with the [classname]`HttpSecurity` instance, so it can be retrieved with `http.getSharedObject(OidcUserService.class)`.


=== Mapping Roles on a Custom User Service

Enabling the mapping replaces the [classname]`OidcUserService` of the filter chain. An application that builds its own user service should therefore leave [methodname]`keycloakRoleMapping()` off and install the [classname]`KeycloakOidcUserMapper` converter on that service instead:

[source,java]
----
var oidcUserService = new OidcUserService();
oidcUserService.setOidcUserConverter(new KeycloakOidcUserMapper());
----

The mapper prefixes roles with `ROLE_`. To apply another prefix -- for example, to match a [classname]`GrantedAuthorityDefaults` bean -- pass it to the constructor:

[source,java]
----
new KeycloakOidcUserMapper("AUTHORITY_");
----

Configure the resulting user service on the OAuth2 login as described in the https://docs.spring.io/spring-security/reference/servlet/oauth2/login/advanced.html[Spring Security documentation].


[discussion-id]`EF8F6AC3-BE67-4BE2-9A78-C371C1D4B9FD`
9 changes: 9 additions & 0 deletions articles/flow/security/vaadin-security-configurer.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ The following beans are shared by this configurer (if not already shared):
* `VaadinRolePrefixHolder` — Holds role prefix accessible outside an active request
* `VaadinDefaultRequestCache` — A request cache implementation which ignores requests that are not for routes
* `VaadinSavedRequestAwareAuthenticationSuccessHandler` — A strategy that uses an available VaadinSession for retrieving the security context
* `ClientRegistrationRepository` — The repository of the OAuth2 client registrations of the application
* `OidcUserService` — The service that loads the authenticated user, shared only when Keycloak role mapping is enabled with `keycloakRoleMapping()`

==== Configuration Methods

Expand Down Expand Up @@ -105,6 +107,13 @@ public VaadinSecurityConfigurer oauth2LoginPage(String oauth2LoginPage, String p

Configures the login page for OAuth2 authentication and the post-logout redirect URI.

[source,java]
----
public VaadinSecurityConfigurer keycloakRoleMapping()
----

[since:com.vaadin:vaadin@V25.4]#Enables mapping of Keycloak realm and client roles to Spring Security granted authorities (disabled by default).# Keycloak puts the roles of a user into the access token, so they aren't part of the authenticated user by default. Enabling the mapping decodes the access token and maps its roles, which makes `@RolesAllowed("admin")` and `hasRole("admin")` match a Keycloak role named `admin`. Works only together with `oauth2LoginPage(String)` and its overloads. See <<{articles}/flow/integrations/spring/oauth2#keycloak-role-mapping,Keycloak Role Mapping>> for details.

===== Logout Configuration

[source,java]
Expand Down
Loading