diff --git a/docs/developer-guide/integrations/auth.md b/docs/developer-guide/integrations/auth.md index 5dae0c0eb3..94d1370a32 100644 --- a/docs/developer-guide/integrations/auth.md +++ b/docs/developer-guide/integrations/auth.md @@ -44,7 +44,7 @@ Where: - when set to `true`, the refresh token is used and the session extends every time the session timeout is met !!! note - `sessionTimeout` and `autorefresh` in `mapstore.properties` are valid for the default session storage. If you are using openID or keycloak, they will not be used. + `sessionTimeout` and `autorefresh` in `mapstore.properties` are valid for the default session storage. If you are using OpenID Connect (including Keycloak or Google), they will not be used — token lifetimes are controlled by the IdP and the `{provider}OAuth2Config.cacheExpirationMinutes` setting in `mapstore-ovr.properties`. Additionally, on the client side, in order to configure the interval in which is session `refresh` action is fired, one can use the `tokenRefreshInterval` property. It can be configured via `localConfig.json -> tokenRefreshInterval`, the value is in milliseconds. diff --git a/docs/developer-guide/integrations/users/keycloak-sso-impl.md b/docs/developer-guide/integrations/users/keycloak-sso-impl.md deleted file mode 100644 index a4c99db4df..0000000000 --- a/docs/developer-guide/integrations/users/keycloak-sso-impl.md +++ /dev/null @@ -1,113 +0,0 @@ -# SSO Workflow in Keycloak - -Here in this section some details about keycloak SSO integration - -## Desired workflow - -If keycloak SSO is configured, we want to implement the following workflow. - -```mermaid -graph LR - A[Open Page]-->B{Have MapStore session?}; - B -- Yes --> C{Have keycloak session?} - B -- No --> D{Have keycloak session?} - C -- Yes --> G[Ok, Monitor Logout] - C -- No --> F[Do Logout] - D -- Yes -->J[Do Login] - D -- No --> K[Ok, Monitor Login] -``` - -The keycloakJS library implements the following workflow: - -```mermaid -graph LR - Init(init) --> TokenProvided{token provided on init?} - TokenProvided -- Yes --> MonitorLogout - MonitorLogout -- timeout --> C1{Changed} - C1 -- False --> MonitorLogout - C1 -- true --> onAuthLogout - TokenProvided -- No --> CheckSSO[check-sso] - CheckSSO --> C2{changed} - C2 -- False --> InitResolve[Init promise resolves] - C2 -- True --> Adapter.login - Adapter.login --> InitResolve -``` - -MapStore can: - -- Re-run `init` -- Intercept `onAuthLogout` -- Implement adapter methods `login`, `logout`. -- Intercept `init` promise resolve with `.then` - -!!! note - `changed` is the variable emitted by an internal iframe managed by the keycloak JS API. - This technique allows to intercept logout events, anyway refreshing tokens or intercepting login, after first attempt - doesn't seem to work well and has some limitations because of security reasons. - In particular in the current implementation with `openID` sync with GeoStore we need to - workaround partially the logic of the library to make the tokens work in sync. - -## Implementation - -The SSO integration in MapStore will reuse the entry points of the JS lib together with the existing openID integration in keycloak, implementing the following workflows: - -### Initialization - -At the initial page load, we check if the `authenticationProviders` contains a `sso` entry (only keycloak) - -```mermaid -graph LR - MapStoreStart --> SSOConfigured{Is SSO Configured?} - SSOConfigured -- Yes --> LoadJS{Load JS} - LoadJS --> Init - SSOConfigured -- No --> DoNothing -``` - -- `LoadJS`: loads `keycloak.js`, that includes the JS support to keycloak, from keycloak instance (only once) -- `Init` is initialized by MapStore with the current config, adding MapStore's `access_token` and `refresh_token`, if present, from openID login. - -### Monitoring phase - -After initialization, we may receive different events or cases. These are the possible cases: - -#### Case 1 - Login From MapStore - -If MapStore is not logged in, the user can click on login button and be redirected to keycloak login form. -After that, the init flow will pass the MapStore tokens to the JS interface. They will be used to check session logout. - -!!! note - If MapStore user is logged in, the `init`, we may not initially have the token ready. For this reason, on LOGIN_SUCCESS, we re-init the application, - or sync operation is triggered from `Adapter.login` to refresh the tokens. - -#### Case 2 - Login from keycloak - -If MapStore is not logged in, the `init` function do a `check-sso` operation and finish. -In order to monitor the login on MapStore, we implemented a timer to re-init trigger anytime the `check-sso` resolves with not authenticated. - -```mermaid -graph LR -Init --> InitResolve -InitResolve --> SessionFound{Session catch in keycloak?} -SessionFound -- No --> MonitorLoginTimeout(Timeout of 10 sec) -MonitorLoginTimeout --> Init -SessionFound -- Yes --> Adapter.login -``` - -!!! note - Implementation is using `messageReceiveTimeout` as timeout, the same timeout variable of the keycloak JS library for monitoring logout - -#### Case 3 - Logout from keycloak - -In this case the library that receives a valid keycloak token monitors the logout autonomously. - -#### Case 4 - Logout from MapStore - -Logout from MapStore, a bug in keycloak API doesn't correctly check the internal iframe (`changed` option event), and there is no possibility to trigger it, until you visit the keycloak page. -This condition after logout can not be distinguished from a external login (from keycloak) detection. So refreshing the page before the token on client is naturally expired will cause a redirect to -Login page, because MapStore find there is an active session on keycloak. -In order to avoid this, an hack is necessary. MapStore loads an iframe immediately after logout to allow the cookie session to be catch and to apply the proper reset. - -### Refresh token - -By default keycloak has 5 minutes long lifetime for token, 30 minutes for refresh token. -Anyway this can be configured. For this reason, the keycloak support schedules a refresh based on the current token expiration, restarting from `init`, scheduling a refresh as half of time between expiring time and now. (e.g The token expires 2 minutes from now, a refresh is scheduled in 1 minute). diff --git a/docs/developer-guide/integrations/users/keycloak.md b/docs/developer-guide/integrations/users/keycloak.md deleted file mode 100644 index 2e78b4d13b..0000000000 --- a/docs/developer-guide/integrations/users/keycloak.md +++ /dev/null @@ -1,176 +0,0 @@ -# Keycloak Integrations - -## General - -MapStore supports various Keycloak integration features: - -- [**OpenID support**](openId.md#keycloak): Allows to login to MapStore using a keycloak account. -- [**Single sign on**](#single-sign-on-integration): Enhances the OpenID support by detecting a session in the keycloak realm and automatically login/logout from MapStore -- [**Direct user integration**](#direct-user-integration): Enhances the OpenID support making MapStore use keycloak as unique Identity Manager System (IdM), replacing the MapStore DB with Keycloak REST API. - -## OpenID - -**Keycloak OpenID support** allows to use a keycloak instance as Identity Provider (IdP) via OpenID Connect (OIDC), so that the user can login to MapStore using an existing account in keycloak. - -You can find details about how to configure it in the dedicated [**"OpenID Connect" page section dedicated to keycloak**](openId.md#keycloak) - -## Single sign on integration - -MapStore provides an integration with the keycloak **Single Sign On** (SSO) system, that allows to **automatically login/logout** in MapStore when you login/logout from another application in the same keycloak realm, an vice-versa. - -In order to enable the SSO in keycloak you have to: - -- Have already configured the [openID for keycloak](openId.md#keycloak). -- Create a keycloak client in the same realm of openID integration above. -- Configure SSO in MapStore's `localConfig.json` - -### Configure the OpenID integration - -- See here [openID integration](openId.md#keycloak). - -### Configure keycloak client - -After configuring the open [openID integration](openId.md#keycloak), you will have a keycloak client called `mapstore-server`. -In order to enable SSO you have to create **another** new Client on keycloak. In this guide we will name it `mapstore-client`. - - - - -- Configure it as `Public` -- Insert in **"Valid Redirect URIs"** your MapStore base root, with a `*` at the end (e.g. `https://my.mapstore.site.com/mapstore/*`) -- Insert in **"Web Origins"** your MapStore base domain name. (e.g. `https://my.mapstore.site.com`) - - - -- Click on Save button, then open the *Installation* tab, select the `Keycloak OIDC JSON` format, and copy the JSON displayed below. - - - -### Configure SSO in MapStore - -After configuring the open [openID integration](openId.md#keycloak), you will have an entry named `keycloak` in `authenticationProviders`. -In this entry, you will have to add `"sso":{"type":"keycloak"}` and `config: ""`. - -e.g. - -```json -{ - "authenticationProviders": [ - { - "type": "openID", - "provider": "keycloak", - "config": { - "realm": "master", - "auth-server-url": "http://localhost:8080/", - "ssl-required": "external", - "resource": "mapstore-client", - "public-client": true, - "confidential-port": 0 - }, - "sso": { - "type": "keycloak" - } - } - ], -} -``` - -Here implementation details about [keycloak login workflow](keycloak-sso-impl.md#sso-workflow-in-keycloak). - -## Direct user integration - -By default MapStore can integrate openID login with Keycloak and also supports integration with Keycloak SSO. - -By default users that login with Keycloak are created on the database and their Keycloak roles inserted as MapStore UserGroup. -Anyway MapStore can interact with Keycloak REST API to provide a direct integration without persisting anything on the MapStore's database. -This provides a stricter integration between the applications, allowing the assignment of roles and groups directly from keycloak, and avoiding any synchronization issue. - -In this scenario the integration MapStore replaces the user and user-group database tables with the keycloak REST API. - -!!! note - This integration disables reading and writing to the users' and groups' database and replaces it with the Keycloak REST API, with read-only support. - For this reason we suggest to disable the `UserManager`, `GroupManager` plugins, and remove the `authenticationProviders` entry of type `geostore`, if any, because the standard login with username and password is not allowed for the db users. - In case of integration with GeoServer, also GeoServer should be connected to Keycloak for users, and not to the MapStore database. - -### Configure direct integration with keycloak - -To enable the direct integration with keycloak you will have to: - -1. Create a dedicated client for keycloak. -2. Configure `mapstore-ovr.properties` -3. Activate the functionality via system property - -#### 1. Create a dedicated client for keycloak - -- Create **another** client on keycloak, in the same realm of `mapstore-server` and `mapstore-client` (where present) called `mapstore-users`: - - - - -- Configure it with: - -- **Access Type**: `public` -- **Implicit Flow Enabled** Set to on **On** -- **Valid Redirect URIs** with your app base URL, with an ending `*`, e.g. `http://localhost:8080/*`. - - - -And click on Save. - -#### 2. Configure `mapstore-ovr.properties` - -The `autoCreateUser` option must be set to false in `mapstore-ovr.properties`. - -```properties -keycloakOAuth2Config.autoCreateUser=false -``` - -Moreover in `mapstore-ovr.properties` you have to add the following information (replacing `` with your base keycloak base url): - -```properties -## Keycloak as User and UserGroup repository -keycloakRESTClient.serverUrl= -keycloakRESTClient.realm=master -keycloakRESTClient.username=admin -keycloakRESTClient.password=admin -keycloakRESTClient.clientId=mapstore-users -``` - -Where: - -- `serverUrl`: URL of keycloak, (e.g. `http://localhost:8080` or `https://mysite.com/`) -- `realm`: the realm where the client has been created -- `username`, `password`: credentials of a user with the role to `view-users`.1 - -!!! note - 1 In order to query the keycloak REST API, you need to have in your realm at least one user with - `realm-admin` role permission. Usually the administrator of the realm has these permission. To associate these - permissions to a new user dedicated to this purpose, you have to open "Role Mappings" tab of keycloak and in "Client - Roles" select `realm-management` (or in master realm select `master-realm`) and add to selected `realm-admin`. - - -#### 3. Activate the functionality via system property - -In order to activate the integration in your instance, you will need to set the [Java System Property](https://www.ibm.com/docs/en/sdk-java-technology/7?topic=customization-how-specify-javalangsystem-property) `security.integration` with the value `keycloak-direct`. - -One easy and usual way to configure this system property in Tomcat is using the `JAVA_OPTS`. Like you do with `datadir.location`, you can set it by adding to `JAVA_OPTS` variable the entry `-Dsecurity.integration=keycloak-direct`. - -!!! note - For old projects or in case you can not set the system property, you can anyway configure it by adding this section to your `geostore-spring-security.xml` file. - - ```xml - - - - - - - - - - - ``` diff --git a/docs/developer-guide/integrations/users/openId.md b/docs/developer-guide/integrations/users/openId.md index d75ca5db5d..f1e1e0a047 100644 --- a/docs/developer-guide/integrations/users/openId.md +++ b/docs/developer-guide/integrations/users/openId.md @@ -2,7 +2,7 @@ MapStore allows to integrate with [OpenID connect](https://openid.net/connect/) services. This allows to use external services to authenticate users in MapStore. This is useful when you have to integrate MapStore with an existing authentication system, or when you want to use a third-party service to authenticate users. -## Customizing logo an text in Login Form +## Customizing logo and text in Login Form For details about the configuration for a specific service, please refer to the corresponding section below. If you want to customize the icon and/or text displayed, you can refer to the documentation of the [LoginPlugin](https://mapstore.geosolutionsgroup.com/mapstore/docs/api/plugins#plugins.Login). @@ -13,96 +13,138 @@ You can add additional providers to the list (e.g., `openid`), and they will be !!! info If only one OpenID entry is present in `authenticationProviders` (and no `geostore` entry available), clicking on the login entry in the login menu will redirect directly to the OpenID provider login page configured, without showing the login window. If more than one entry is present in the `authenticationProviders` list, the the login window will be provided in the MapStore UI to be able choose the desired one for the authentication. -## Supported OpenID services +## Generic OpenID Connect configuration -MapStore provides a generic OpenID connect provider (`oidc`) that can be used to configure any OpenID Connect service (Google, Microsoft, Keycloak, Facebook, Github, etc.). In addition, MapStore provides specific configurations for some services. This means that you can configure MapStore to use the following services out of the box: +MapStore integrates with any [OpenID Connect](https://openid.net/connect/)-compliant provider using a generic OIDC layer. You can configure **one or more providers simultaneously** - each identified by a name that determines the `provider` field in `localConfig.json` and the property prefix in `mapstore-ovr.properties`. -- OpenID connect -- Google (specific) -- Keycloak (specific) +For each provider you want to enable, you have to: -For each service you want to include, you have to: +- configure a client on the identity provider (e.g., create a new client in Keycloak, a credential in Google Console, an app registration in Azure AD, etc.) +- add the provider name to `oidc_providers` and configure `{provider}OAuth2Config.*` properties in `mapstore-ovr.properties` +- add a corresponding entry to `authenticationProviders` in `localConfig.json` -- configure the service (e.g., create a new application on Google Console, create a new client on Keycloak, etc.) -- properly configure the backend (in `mapstore-ovr.properties`) -- modify the `localConfig.json` file by adding a proper entry to the `authenticationProviders` +### Configure OpenID provider client -Moreover the keycloak provider allows to configure advanced features like the **Single Sign On (SSO)** with other applications and **direct user database integration** as for [LDAP](ldap.md#ldap-integration-with-mapstore). +This step depends on the specific OpenID provider so, please, refer to the specific documentation of the OpenID provider you are using. See the [Provider examples](#provider-examples) section below for step-by-step guides for Microsoft Azure, Google, and Keycloak. -!!! note - For the moment the generic `oidc` provider does not support multiple instances. This means that you can configure only **one** generic `oidc` provider. You can configure the specific providers (e.g., `google`, `keycloak`) in addition to the generic `oidc` provider, but not multiple instances of the generic `oidc` provider. So for instance you can configure a generic `oidc` provider (e.g. connected to Github) plus a `google` and a `keycloak` provider, but not two generic `oidc` providers. - -### OpenID connect (experimental) - -In order to configure the generic OpenID provider with a service of your choice you have to: - -- Configure the OpenID provider client -- Configure MapStore back-end -- Configure the MapStore front-end - -#### Configure OpenID provider client - -This step depends on the specific OpenID provider so, please, refer to the specific documentation of the OpenID provider you are using. You can find some examples about how to configure a generic OpenID provider with Microsoft Azure in the following sections. The same information are valid for other OpenID providers, like Google, Keycloak, etc. You have to get the following information: - **Client ID**: the client id. This is the client id that must be present on the OpenID provider - **Client Secret**: the client secret. This is the client secret for the client id on the OpenID provider - **Discovery URL**: the discovery URL. This is the URL that contains all the information for the specific service. -#### Configure the MapStore back-end +### Configure the MapStore back-end In order to configure the generic OpenID provider you have to: - create/edit `mapstore-ovr.properties` file (in data-dir or class path) to configure the generic provider this way: ```properties -# enables the keycloak OpenID Connect filter +# Register "oidc" as an OIDC provider +oidc_providers=oidc + +# Enable the OpenID Connect filter oidcOAuth2Config.enabled=true -# note: this is the client id have to be present on the OpenID provider +# Client credentials from the OpenID provider oidcOAuth2Config.clientId=mapstore-server oidcOAuth2Config.clientSecret= -# the discovery URL -oidcOAuth2Config.discoveryUrl=http://keycloak:8080/auth/realms/mapstore/.well-known/openid-configuration oidcOAuth2Config.sendClientSecret=true -# create the user if not present + +# Discovery URL: the .well-known/openid-configuration endpoint of the provider +oidcOAuth2Config.discoveryUrl=https:///.well-known/openid-configuration + +# Create the user on first login if not already present oidcOAuth2Config.autoCreateUser=true -oidcOAuth2Config.redirectUri=http://localhost:8080/mapstore/rest/geostore/openid/oidc/callback -# Internal redirect URI (you can set it to relative path like this `../../..` to make this config work across domain) -oidcOAuth2Config.internalRedirectUri=http://localhost:8080/mapstore -# user name attribute (default is `email`) + +# Redirect URIs +oidcOAuth2Config.redirectUri=http:///mapstore/rest/geostore/openid/oidc/callback +# Internal redirect URI (can be relative path, e.g. `../../..` to work across domains) +oidcOAuth2Config.internalRedirectUri=http:///mapstore + +# User name attribute (default: email) # oidcOAuth2Config.principalKey=email -# Optional role claims, if a claim contains roles, you can map them to MapStore roles. (roles can be only ADMIN or USER) + +# Scopes to request (if omitted, MapStore uses the ones from the discovery document) +# oidcOAuth2Config.scopes=openid,email,profile + +# Role mapping: claim name that carries roles, and mapping to MapStore roles (ADMIN or USER) # oidcOAuth2Config.rolesClaim=roles -# Optional group claims, if a claim contains groups, you can map them to MapStore groups. +# oidcOAuth2Config.roleMappings=admin:ADMIN,user:USER +# Default role when no mapping matches +# oidcOAuth2Config.authenticatedDefaultRole=USER + +# Group mapping: claim name that carries groups, and mapping to MapStore groups # oidcOAuth2Config.groupsClaim=groups -# Enables global logout from SSO, if properly confugred. false by default +# oidcOAuth2Config.groupMappings=my-idp-group:my-mapstore-group +# If an IdP role/group name contains ':' or ',', escape it with a backslash. +# In this .properties file the backslash must be doubled: +# oidcOAuth2Config.groupMappings=landscape\\:read:landscape_read +# oidcOAuth2Config.dropUnmapped=false + +# Groups always assigned to every authenticated user, in addition to claim-derived ones. +# Created automatically if they do not exist; not subject to groupMappings/dropUnmapped. +# oidcOAuth2Config.defaultGroups=oidc-users + +# Global logout (RP-initiated logout): invoke IdP logout on MapStore logout # oidcOAuth2Config.globalLogoutEnabled=true -# Optional scopes parameter, that allows to customize the scopes to reqeuest. If empty, MapStore will use the one present in the discovery document -# oidcOAuth2Config.scopes=email,profile +# URI to redirect to after logout (optional, required by some providers) +# oidcOAuth2Config.postLogoutRedirectUri=https:///mapstore/ + +# PKCE (Proof Key for Code Exchange) recommended for public clients +# oidcOAuth2Config.usePKCE=false + +# Bearer token validation strategy: jwt (default), introspection, or auto +# oidcOAuth2Config.bearerTokenStrategy=jwt + +# Maximum token age in seconds for bearer JWT validation (0 = disabled) +# oidcOAuth2Config.maxTokenAgeSecs=0 +# Access type for authorization (set to "offline" to request a refresh token, e.g. for Google) +# oidcOAuth2Config.accessType=offline ``` -- `oidcOAuth2Config.clientId`: the client id. This is the client id that must be present on the OpenID provider -- `oidcOAuth2Config.clientSecret`: the client secret. This is the client secret for the client id on the OpenID provider -- `oidcOAuth2Config.discoveryUrl`: the discovery URL. This is the URL that contains all the information for the specific service. -- `oidcOAuth2Config.sendClientSecret`: if `true`, the client secret will be sent to the OpenID provider. If `false`, the client secret will not be sent. -- `oidcOAuth2Config.autoCreateUser`: if `true`, the user will be created, if not present, in the MapStore database. If `false`, the user will not be created (useful if the user is managed by an external service like Keycloak or LDAP). -- `oidcOAuth2Config.redirectUri`: the redirect URI. This is the URI that the OpenID provider, and it must be the effective URI of the MapStore application, with the path `/rest/geostore/openid/oidc/callback`. -- `oidcOAuth2Config.internalRedirectUri`: the internal redirect URI. This is the URI that the MapStore will redirect to after the login. It must be the effective URI of the MapStore application. -- `oidcOAuth2Config.principalKey`: (*optional*) the user name attribute. This is the attribute that will be used as user name. The default is `email`. -- `oidcOAuth2Config.rolesClaim`: (*optional*) the role claims. If a claim contains roles, you can map them to MapStore roles. The roles can be only `ADMIN` or `USER`. If the claim is not present, the default role will be `USER`. -- `oidcOAuth2Config.groupsClaim`: (*optional*) the group claims. If a claim contains groups, you can map them to MapStore groups. If the claim is not present, no group will be assigned (except the default `everyone` group). -- `oidcOAuth2Config.globalLogoutEnabled`: (*optional*): if true (and the server supports it) invokes global logout on MapStore logout -- `oidcOAuth2Config.scopes`: (*optional*): allows to customize the scopes to request. If empty, MapStore will use the one present in the discovery document. -- `oidcOAuth2Config.maxRetry`: (*optional*) the maximum number of retry attempts for the OpenID Connect authentication process. Default is `3`. -- `oidcOAuth2Config.initialBackoffDelay`: (*optional*) the initial delay (in milliseconds) before retrying the OpenID Connect authentication process. Default is `1000` (1 second). -- `oidcOAuth2Config.backoffMultiplier`: (*optional*) the multiplier to apply to the delay for each retry attempt. Default is `2.0`. +- `oidcOAuth2Config.enabled`: must be `true` to activate this provider. +- `oidcOAuth2Config.clientId`: the client id registered on the OpenID provider. +- `oidcOAuth2Config.clientSecret`: the client secret for the client id. +- `oidcOAuth2Config.discoveryUrl`: the `.well-known/openid-configuration` URL of the provider. All endpoints are auto-discovered from this URL at startup. If the discovery endpoint is unreachable at startup (e.g. the IdP is behind a firewall not accessible from the server), you can omit `discoveryUrl` and set each endpoint manually: + +```properties +oidcOAuth2Config.authorizationUri=https:///protocol/openid-connect/auth +oidcOAuth2Config.accessTokenUri=https:///protocol/openid-connect/token +oidcOAuth2Config.checkTokenEndpointUrl=https:///protocol/openid-connect/userinfo +oidcOAuth2Config.idTokenUri=https:///protocol/openid-connect/certs +oidcOAuth2Config.logoutUri=https:///protocol/openid-connect/logout +oidcOAuth2Config.revokeEndpoint=https:///protocol/openid-connect/revoke +oidcOAuth2Config.introspectionEndpoint=https:///protocol/openid-connect/token/introspect +``` + +These values correspond to the standard fields in the discovery document and are the same endpoints your browser would resolve via the discovery URL at runtime. + +- `oidcOAuth2Config.sendClientSecret`: if `true`, the client secret is sent to the token endpoint (required for `client_secret_post`). +- `oidcOAuth2Config.autoCreateUser`: if `true`, the user is created on first login if not already in the MapStore database. Set to `false` when users are managed by an external service like LDAP. +- `oidcOAuth2Config.redirectUri`: callback URI registered on the provider; must end with `/rest/geostore/openid/oidc/callback` (replace `oidc` with the provider name if using a different name). +- `oidcOAuth2Config.internalRedirectUri`: the URI MapStore redirects to after login. Can be relative (e.g., `../../..`). +- `oidcOAuth2Config.principalKey`: (*optional*) claim used as the MapStore username. Default is `email`. +- `oidcOAuth2Config.scopes`: (*optional*) comma-separated scopes to request. If omitted, the ones from the discovery document are used. +- `oidcOAuth2Config.rolesClaim`: (*optional*) claim name containing roles to map to MapStore roles (`ADMIN` or `USER`). +- `oidcOAuth2Config.roleMappings`: (*optional*) comma-separated `idp-role:MAPSTORE_ROLE` pairs. +- `oidcOAuth2Config.authenticatedDefaultRole`: (*optional*) role assigned when no `roleMappings` entry matches. Allowed values: `USER` or `ADMIN`. +- `oidcOAuth2Config.groupsClaim`: (*optional*) claim name containing groups to map to MapStore user groups. +- `oidcOAuth2Config.groupMappings`: (*optional*) comma-separated `idp-group:mapstore-group` pairs. Escape `:` and `,` in group names with a backslash (doubled in `.properties` files: `\\:`). +- `oidcOAuth2Config.dropUnmapped`: (*optional*) when `false` (default), **all** groups found in the claim are created in MapStore and assigned to the user, even if they have no entry in `groupMappings`. When `true`, only groups that have an explicit entry in `groupMappings` are assigned; unmatched ones are ignored. +- `oidcOAuth2Config.defaultGroups`: (*optional*) comma-separated group names always assigned to every authenticated user, in addition to the claim-derived ones. These groups are created automatically if they do not exist and are not subject to `groupMappings`/`dropUnmapped`. +- `oidcOAuth2Config.globalLogoutEnabled`: (*optional*) if `true`, invokes RP-initiated logout on the IdP when the user logs out of MapStore. +- `oidcOAuth2Config.postLogoutRedirectUri`: (*optional*) URI to redirect to after IdP logout; required by some providers (e.g., Keycloak). +- `oidcOAuth2Config.usePKCE`: (*optional*) enables Authorization Code Flow with PKCE. Recommended for public clients. Default is `false`. +- `oidcOAuth2Config.bearerTokenStrategy`: (*optional*) strategy for validating bearer JWTs: `jwt` (default, JWKS signature verification), `introspection` (RFC 7662 token introspection, required for opaque tokens), or `auto` (try JWT first, fall back to introspection). +- `oidcOAuth2Config.maxTokenAgeSecs`: (*optional*) maximum accepted age in seconds for bearer JWTs. `0` disables the check. +- `oidcOAuth2Config.accessType`: (*optional*) passed as the `access_type` parameter in the authorization request. Set to `offline` to request a refresh token (required by Google). !!! note - The only mandatory claim is the `email` or what you indicated in `oidcOAuth2Config.principalKey`. The `rolesClaim` and `groupsClaim` configurations are optional. If you don't need to map roles or groups, you can omit them. At the moment, there is no mapping for roles and groups for the generic OIDC provider. If you need to map roles and groups, you can use the `keycloak` provider. + The only mandatory claim is `email` (or whatever you set in `oidcOAuth2Config.principalKey`). Role and group mapping are optional. All OIDC providers, including Keycloak, Google, and Azure, use the same generic configuration. -#### Configure the MapStore front-end +### Configure the MapStore front-end - Add an entry for `oidc` in `authenticationProviders` inside `localConfig.json` file. @@ -122,46 +164,166 @@ oidcOAuth2Config.internalRedirectUri=http://localhost:8080/mapstore } ``` -You can customize the `title` to be displayed in the login form, add an `imageURL` or use only one `authenticationProviders`, removing the `geostore` entry, if you want to use only the OpenID provider. In this case the user will be redirected directly to the OpenID provider without showing the login form. +!!! note + You can: + - customize the `title` to be displayed in the login form + - add an `imageURL` to show a custom icon in the login form. For example, to use a base64-encoded SVG image: + + ```json + { + "type": "openID", + "provider": "oidc", + "title": "My custom identity provider", + "imageURL": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3R5bGU9ImZpbGw6I2ZmMDAwMDsiIC8+PC9zdmc+" + } + ``` + + - use only one `authenticationProviders`, removing the `geostore` entry, if you want to use only the OpenID provider. In this case the user will be redirected directly to the OpenID provider without showing the login form. + - `showAccountInfo` is `false` by default for OpenID provider. To allow users to view their account details in MapStore, add `"showAccountInfo": true` to the provider entry: + + ```json + { + "type": "openID", + "provider": "oidc", + "showAccountInfo": true + } + ``` + +## Multiple simultaneous providers + +You can run multiple OIDC providers at the same time by listing them in `oidc_providers` (comma-separated) and configuring each with its own `{provider}OAuth2Config.*` prefix. Each provider's `redirectUri` **must** include the provider name in the path. + +Example: Keycloak + Google + +```properties +# Both providers active simultaneously +oidc_providers=keycloak,google + +# Keycloak +keycloakOAuth2Config.enabled=true +keycloakOAuth2Config.clientId=mapstore-server +keycloakOAuth2Config.clientSecret= +keycloakOAuth2Config.discoveryUrl=https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration +keycloakOAuth2Config.redirectUri=https://my.mapstore.site.com/mapstore/rest/geostore/openid/keycloak/callback +keycloakOAuth2Config.internalRedirectUri=https://my.mapstore.site.com/mapstore/ +keycloakOAuth2Config.autoCreateUser=true + +# Google +googleOAuth2Config.enabled=true +googleOAuth2Config.clientId= +googleOAuth2Config.clientSecret= +googleOAuth2Config.discoveryUrl=https://accounts.google.com/.well-known/openid-configuration +googleOAuth2Config.redirectUri=https://my.mapstore.site.com/mapstore/rest/geostore/openid/google/callback +googleOAuth2Config.internalRedirectUri=https://my.mapstore.site.com/mapstore/ +googleOAuth2Config.autoCreateUser=true +googleOAuth2Config.accessType=offline +``` + +`localConfig.json`: both providers listed: + +```json +{ + "authenticationProviders": [ + { + "type": "openID", + "provider": "keycloak", + "title": "Keycloak" + }, + { + "type": "openID", + "provider": "google", + "title": "Google" + }, + { + "type": "basic", + "provider": "geostore" + } + ] +} +``` + +!!! note + Use `oidc_providers` (underscore) not `oidc.providers` (dot). The dot form conflicts with Spring's `PropertyOverrideConfigurer` which interprets `oidc.providers` as bean `oidc`, property `providers`. + +## Provider examples + +The following sections show complete configurations for three common providers. Each is a specific instance of the [generic OIDC layer](#generic-openid-connect-configuration) described above — the configuration structure is the same across all providers. -#### Example with Microsoft Azure +### Microsoft Azure (Entra ID) -Microsoft Azure provides OpenID Connect support. This is an example of how to configure MapStore to use Microsoft Azure as an OpenID provider. The same steps can be applied to other OpenID providers. Please refer to the specific documentation of the OpenID provider you are using. [Here](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) the one for Microsoft Azure. +Microsoft Azure (Entra ID) provides OpenID Connect support. Please refer to the [Microsoft Azure documentation](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) for details. + +#### Configure Azure app registration Here a quick summary of the steps to configure Microsoft Azure as an OpenID provider and get the information needed to configure MapStore: 1. Create a new application ![Create azure application](img/azure-1.jpg) 2. Set the proper valid redirect URLs ![set redirect URL](img/azure-2.jpg) to: `https:///mapstore/rest/geostore/openid/oidc/callback` 3. Create and copy the client secret ![Create and copy client secret](img/azure-3.jpg) -4. Add optional claims if needed ![Add optional claims](img/azure-4.jpg) +4. Add optional claims (e.g. `email`, `family_name`, `given_name`) in **Token configuration** if needed 5. Copy endpoints and data to configure MapStore ![Copy endpoints](img/azure-5.jpg) -These steps are based on the [Microsoft Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app). -Please refer the official documentation for any detail or additional configuration. +From the Azure app registration you need three values: -Here below an example of how to fill MapStore configuration files with the data above: (in this example we are using **MapStore data directory** ) +| Value | Where to find it | Used as | +| --- | --- | --- | +| **Application (client) ID** | App registration → Overview | `oidcOAuth2Config.clientId` | +| **Client secret value** | App registration → Certificates & secrets → New client secret | `oidcOAuth2Config.clientSecret` | +| **OpenID Connect metadata document** | App registration → Endpoints → OpenID Connect metadata document | `oidcOAuth2Config.discoveryUrl` | + +!!! note + If you want to assign groups to users from Azure, you also need to add the `GroupMember.Read.All` scope to the API permissions and grant admin consent. + To do it you need to go to API permissions → Add a permission → Microsoft Graph → Application permissions → Delegated Permission → select `GroupMember.Read.All` → Add permissions, then click on **Grant admin consent**. + +#### Configure MapStore back-end for Azure `mapstore-ovr.properties`: ```properties -# enables the OpenID Connect filter -oidcOAuth2Config.enabled=true +oidc_providers=azure + +azureOAuth2Config.enabled=true +azureOAuth2Config.clientId= +azureOAuth2Config.clientSecret= +azureOAuth2Config.sendClientSecret=true +azureOAuth2Config.discoveryUrl= +azureOAuth2Config.autoCreateUser=true +azureOAuth2Config.redirectUri=https:///mapstore/rest/geostore/openid/azure/callback +azureOAuth2Config.internalRedirectUri=https:///mapstore +note: GroupMember.Read.All scope is required to get group info in the token for group mapping +azureOAuth2Config.scopes=openid,email,profile -# note: this is the client id you have created in Keycloak -oidcOAuth2Config.clientId= -oidcOAuth2Config.clientSecret= +``` + +If you need to assign groups to users from Azure, add the `GroupMember.Read.All` scope to the API permissions and grant admin consent. This allows group information to be included in the token for group mapping, and enable `msGraphEnabled` and `msGraphGroupsEnabled` to get group info from Microsoft Graph. + +```properties +# replace this property +azureOAuth2Config.scopes=openid,email,profile,GroupMember.Read.All +# add these properties to enable group retrieval from Microsoft Graph +azureOAuth2Config.msGraphEnabled=true +azureOAuth2Config.msGraphGroupsEnabled=true -# DISCOVERY_URL something like https://login.microsoftonline.com/abc-dfe-ghi-123-345-567-789/v2.0/.well-known/openid-configuration -oidcOAuth2Config.discoveryUrl= -oidcOAuth2Config.sendClientSecret=true -# create the user if not present -oidcOAuth2Config.autoCreateUser=true -# Here you have to set your redirect URI (here is configured for localhost) -oidcOAuth2Config.redirectUri=http://localhost:8080/mapstore/rest/geostore/openid/oidc/callback -# Internal redirect URI (you can set it to relative path like this `../../..` to make this config work across domain) -oidcOAuth2Config.internalRedirectUri=http://localhost:8080/mapstore ``` +!!! note + There is a limit of 200 groups/roles returned in the token by Azure. If a user belongs to more than 200 groups, the `groups` claim is omitted and replaced with an overage indicator. From GeoStore 2.7 (planned for MapStore 2026.03.00) there are additional properties to enable retrieving groups from Microsoft Graph as a workaround for this limitation, but it still requires the `GroupMember.Read.All` scope and admin consent. + + ```properties + # These properties will be available from geostore 2.7 + # This forces to have groups resolved from Microsoft Graph also if the groups claim is present in the token, to avoid the issue of Azure AD not emitting group claims when a user belongs to too many groups (over 200). With this setting, group mapping will work even for users with a large number of groups, as all group information will be retrieved from Microsoft Graph instead of relying on the token claims. + # oidcOAuth2Config.msGraphAlwaysResolveGroups=false + # This allows to customize the URL, if changes in the future + # oidcOAuth2Config.msGraphEndpoint= + # This allows to enable role retrieval from Microsoft Graph, if you use App Roles for role mapping instead of group claims. This is also recommended to avoid the issue of Azure AD not emitting role claims when a user belongs to too many groups (over 200), as role information will be retrieved from Microsoft Graph instead of relying on the token claims. + # oidcOAuth2Config.msGraphRolesEnabled=false + ``` + +!!! note + This is only an example, azure allows complex configurations with different types of credentials, role and group mapping, different tenants... Please refer to the Microsoft Azure documentation for more details. + +#### Configure MapStore front-end for Azure + `configs/localConfig.json.patch` ( *with a custom title and an image with the Microsoft logo* to show in the login form) ```json @@ -185,9 +347,78 @@ oidcOAuth2Config.internalRedirectUri=http://localhost:8080/mapstore ] ``` +#### Role mapping via Azure App Roles + +The recommended approach for mapping MapStore roles in Azure is to use **App Roles** defined in the application registration. This avoids the group GUID and 200-group-overage limitations of the `groups` claim and produces a clean `roles` claim directly in the ID token. + +**Step 1**: Create an App Role in the Azure app registration + +1. In the App Registration → **App roles → Create app role** +2. Set **Display name** and **Value** to `ADMIN` (the value is what appears in the token) +3. Set **Allowed member types** to `Users/Groups` +4. Enable the role and save + +**Step 2**: Assign the role to users or groups + +1. Go to **Enterprise Applications** → select your application → **Users and groups → Add user/group** +2. Select the user (or group) and assign the `ADMIN` role + +!!! warning + Assigning App Roles to groups requires an **Azure AD P1 or P2** (Entra ID P1/P2) license. Without it, roles can only be assigned to individual users. + +With this configuration the ID token will contain a `roles` claim like: + +```json +{ + "roles": ["ADMIN"], + "email": "user@example.com" +} +``` + +**Step 3**: Configure MapStore to read the `roles` claim + +Add role mapping to `mapstore-ovr.properties`: + +```properties +# Map the Azure 'roles' claim to MapStore roles +oidcOAuth2Config.rolesClaim=roles +oidcOAuth2Config.roleMappings=ADMIN:ADMIN +# Users without a matching role get USER by default +oidcOAuth2Config.authenticatedDefaultRole=USER +``` + +#### Group mapping in Azure + +By default the `groups` claim in the Azure ID token contains **Object ID GUIDs**, not display names. To emit display names instead, edit the application **Manifest** (App Registration → Manifest) and set: + +```json +{ + "groupMembershipClaims": "ApplicationGroup", + "optionalClaims": { + "idToken": [{ + "name": "groups", + "additionalProperties": ["cloud_displayname"] + }] + } +} +``` + +!!! note + `groupMembershipClaims` can be set to `"SecurityGroup"` or `"All"` to include broader group sets, but display names via `cloud_displayname` are only emitted for groups **explicitly assigned to the application**. Groups not assigned to the app may still appear as GUIDs regardless of this setting. + +!!! warning + If a user belongs to more than 200 groups, Azure omits the `groups` claim entirely and replaces it with an overage indicator. In that case group-based mapping will not work. This is one of the key reasons to prefer App Roles over group claims where possible. + +Once display names are emitted, configure MapStore to read them: + +```properties +oidcOAuth2Config.groupsClaim=groups +oidcOAuth2Config.groupMappings=: +``` + ### Google -The Google OpenID Connect provider allows to use Google as an authentication provider. This is useful when you want to use Google as an authentication provider for your application. +Google OpenID Connect is configured as a named OIDC provider using the generic OIDC layer. #### Create Oauth 2.0 credentials on Google Console @@ -209,31 +440,37 @@ Please follow the [Google documentation](https://developers.google.com/identity/ After the setup, you will have to: -- create/edit the `mapstore-ovr.properties` file (in data-dir or class path) to configure the Google OpenID integration by inserting in particular the `clientId` and the `clientSecret` obtained from Google Console. You have also to set the `autoCreateUser` to `true` to create the user if not present in the MapStore database: - -Here an example of the configuration, documented inline: +- create/edit the `mapstore-ovr.properties` file (in data-dir or class path) to configure the Google OpenID integration. Use the provider name `google` so that the callback URL matches: ```properties +# Register "google" as an OIDC provider +oidc_providers=google -# enables the google OpenID Connect filter +# Enable the Google OpenID Connect filter googleOAuth2Config.enabled=true -#clientId and clientSecret +# Client credentials from Google Developer Console googleOAuth2Config.clientId= googleOAuth2Config.clientSecret= -# create the user if not present +# Create the user if not already present in MapStore googleOAuth2Config.autoCreateUser=true -# Redirect URL (needs to be configured in the Google Console too) -googleOAuth2Config.redirectUri=https:///mapstore/rest/geostore/openid/google/callback -# Internal redirect URI (you can set it to relative path like this `../../..` to make this config work across domain) -googleOAuth2Config.internalRedirectUri=https:///mapstore/ +# Redirect URL (must be registered in Google Console too) +googleOAuth2Config.redirectUri=https:///mapstore/rest/geostore/openid/google/callback +# Internal redirect URI (can be relative, e.g. `../../..` to work across domains) +googleOAuth2Config.internalRedirectUri=https:///mapstore/ -## discoveryUrl: contains all the information for the specific service. +# Discovery URL for Google's OIDC endpoints googleOAuth2Config.discoveryUrl=https://accounts.google.com/.well-known/openid-configuration + +# Request a refresh token (required for long-lived sessions with Google) +googleOAuth2Config.accessType=offline ``` +!!! note + If you also configure another provider (e.g. the generic `oidc`), list all providers: `oidc_providers=oidc,google`. + #### Configure MapStore front-end for Google OpenID - Add an entry for `google` in `authenticationProviders` inside `localConfig.json` file. @@ -255,70 +492,84 @@ googleOAuth2Config.discoveryUrl=https://accounts.google.com/.well-known/openid-c ### Keycloak -[Keycloak](https://www.keycloak.org/) is an open source identity and access management application widely used. MapStore has the ability to integrate with keycloak: - -- Using the standard OpenID Connect (OIDC) protocol to login/logout in MapStore -- Supporting Single Sign On (SSO) with other applications. -- Mapping keycloak roles to MapStore groups, as well as for ldap. - -In this section you can see how to configure keycloak as a standard OpenID provider. For other advanced functionalities, you can see the [dedicated section of the documentation](keycloak.md#keycloak-integrations) +[Keycloak](https://www.keycloak.org/) is an open source identity and access management application widely used. MapStore integrates with Keycloak via the generic OIDC layer. -#### Configure keycloak Client +#### Configure Keycloak Client -Create a new Client on keycloak. In this guide we will name it `mapstore-server` (because if you need to configure SSO, we may need another key to call `mapstore-client`) +Create a new Client on Keycloak. In this guide we will name it `mapstore-server` (if you also need SSO, you will create a second client called `mapstore-client`). -- Configure it as `Confidential` setting the Redirect-URL with your MapStore base root, with a `*` at the end (e.g. `https://my.mapstore.site.com/mapstore/*`) +- Configure it as `Confidential`, setting the Redirect URL to your MapStore base root with a `*` at the end (e.g. `https://my.mapstore.site.com/mapstore/*`) -- Click on Save button, then open the *Installation* tab, select the `Keycloak OIDC JSON` format, and copy the JSON displayed below. +Note the **realm** and **auth-server-url** from Keycloak, you will use them to build the `discoveryUrl`. - +#### Configure MapStore back-end for Keycloak OpenID -### Configure MapStore back-end for Keycloak OpenID - -Create/edit `mapstore-ovr.properties` file (in data-dir or class path) to configure the keycloak provider this way: +Create/edit `mapstore-ovr.properties` file (in data-dir or class path) to configure the Keycloak provider: ```properties -# enables the keycloak OpenID Connect filter +# Register "keycloak" as an OIDC provider +oidc_providers=keycloak + +# Enable the Keycloak OpenID Connect filter keycloakOAuth2Config.enabled=true -# Configuration -keycloakOAuth2Config.jsonConfig= +# Client credentials from Keycloak +keycloakOAuth2Config.clientId=mapstore-server +keycloakOAuth2Config.clientSecret= +keycloakOAuth2Config.sendClientSecret=true +# Discovery URL: /realms//.well-known/openid-configuration +keycloakOAuth2Config.discoveryUrl=https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration # Redirect URLs -# - Redirect URL: need to be configured to point to your application at the path /rest/geostore/openid/keycloak/callback -# e.g. `https://my.mapstore.site.com/mapstore/mapstore/rest/geostore/openid/keycloak/callback` keycloakOAuth2Config.redirectUri=https://my.mapstore.site.com/mapstore/rest/geostore/openid/keycloak/callback -# - Internal redirect URL when logged in (typically the home page of MapStore, can be relative) keycloakOAuth2Config.internalRedirectUri=https://my.mapstore.site.com/mapstore/ -# Create user (if you are using local database, this should be set to true) +# Create user on first login (set to false if users come from LDAP or another external source) keycloakOAuth2Config.autoCreateUser=true -# Comma separated list of : +# Scopes to request: explicitly set to avoid requesting offline_access by default, +# which requires the Keycloak client/user to have the offline_access role configured. +keycloakOAuth2Config.scopes=openid,profile,email + +# Role mapping: dot-notation path to the claim carrying roles, and mapping to MapStore roles (ADMIN or USER) +keycloakOAuth2Config.rolesClaim=realm_access.roles keycloakOAuth2Config.roleMappings=admin:ADMIN,user:USER +# Default role when no mapping matches +keycloakOAuth2Config.authenticatedDefaultRole=USER -# Comma separated list of : -keycloakOAuth2Config.groupMappings=MY_KEYCLOAK_ROLE:MY_MAPSTORE_GROUP,MY_KEYCLOAK_ROLE2:MY_MAPSTORE_GROUP2 +# Group mapping: claim name that carries groups, and mapping to MapStore groups +keycloakOAuth2Config.groupsClaim=groups +keycloakOAuth2Config.groupMappings=MY_KEYCLOAK_GROUP:MY_MAPSTORE_GROUP,MY_KEYCLOAK_GROUP2:MY_MAPSTORE_GROUP2 +# false (default) = all groups from the claim are created and assigned; true = only mapped ones +keycloakOAuth2Config.dropUnmapped=false -# Default role, when no mapping has matched -keycloakOAuth2Config.authenticatedDefaultRole=USER +# Groups always assigned to every authenticated user (created on the fly if missing) +# keycloakOAuth2Config.defaultGroups=keycloak-users + +# RP-initiated global logout +# keycloakOAuth2Config.globalLogoutEnabled=true +# keycloakOAuth2Config.postLogoutRedirectUri=https://my.mapstore.site.com/mapstore/ ``` -- `keycloakOAuth2Config.jsonConfig`: insert the JSON copied, removing all the spaces -- `keycloakOAuth2Config.redirectUri`: need to be configured to point to your application at the path `/rest/geostore/openid/keycloak/callback`, e.g. `https://my.mapstore.site.com/mapstore/rest/geostore/openid/keycloak/callback` -- `keycloakOAuth2Config.internalRedirectUri` can be set to your application root, e.g. `https://my.mapstore.site.com/mapstore/` -- `keycloakOAuth2Config.autoCreateUser`: true if you want MapStore to insert a Keycloak authenticated user on the DB. UserGroups will be inserted as well and kept in synch with the roles defined for the user in Keycloak. The option **must be set to false if MapStore is using a read-only external service for users and groups** (i.e. Keycloak or LDAP). -- `keycloakOAuth2Config.forceConfiguredRedirectURI`: optional, if `true`, forces the redirect URI for callback to be equal to teh redirect URI. This is useful if you have problems logging in behind a proxy, or in dev mode. -- `keycloakOAuth2Config.roleMappings`: comma separated list of mappings with the following format ``keycloak_admin_role:ADMIN,keycloak_user_role:USER``. These mappings will be used to map Keycloak roles to MapStore roles. Allowed values `USER` or `ADMIN`. -- `keycloakOAuth2Config.authenticatedDefaultRole`: where the role has not been assigned by the mappings above, the role here will be used. Allowed values `USER` or `ADMIN`. -- `keycloakOAuth2Config.groupMappings`: comma separated list of mappings with the following format ``keycloak_role_name:mapstore_group_name,keycloak_role_name2:mapstore_group_name2``. These mappings will be used to map Keycloak roles to MapStore groups. -- `keycloakOAuth2Config.dropUnmapped`: when set to false, MapStore will drop Keycloak roles that are not matched by any mapping role and group mapping. When set to true all the unmatched Keycloak roles will be added as MapStore UserGroups. +- `oidc_providers=keycloak`: registers the `keycloakOAuth2Config` bean; required in geostore 2.6+. +- `keycloakOAuth2Config.discoveryUrl`: the Keycloak OIDC discovery URL - `/realms//.well-known/openid-configuration`. This replaces the old `keycloakOAuth2Config.jsonConfig`. +- `keycloakOAuth2Config.redirectUri`: must end with `/rest/geostore/openid/keycloak/callback`. +- `keycloakOAuth2Config.internalRedirectUri`: the MapStore home page URI after login. +- `keycloakOAuth2Config.autoCreateUser`: `true` to create DB users on first login; `false` when using LDAP or external user stores. +- `keycloakOAuth2Config.roleMappings`: comma-separated `keycloak-role:MAPSTORE_ROLE` pairs. Allowed MapStore values: `USER` or `ADMIN`. +- `keycloakOAuth2Config.authenticatedDefaultRole`: role applied when no `roleMappings` entry matches. +- `keycloakOAuth2Config.groupMappings`: comma-separated `keycloak-group:mapstore-group` pairs. +- `keycloakOAuth2Config.dropUnmapped`: `false` (default) creates and assigns **all** groups from the claim, even unmapped ones; `true` assigns only groups with an explicit entry in `groupMappings`. +- `keycloakOAuth2Config.defaultGroups`: (*optional*) comma-separated group names always assigned to every authenticated user. + +!!! note + The `keycloakOAuth2Config.jsonConfig` property from earlier versions is **no longer supported**. Use `keycloakOAuth2Config.discoveryUrl` instead. #### Configure MapStore front-end for Keycloak OpenID @@ -338,3 +589,55 @@ keycloakOAuth2Config.authenticatedDefaultRole=USER ] } ``` + +#### Role and group mapping in Keycloak + +##### Role mapping + +GeoStore supports only two roles: `ADMIN` and `USER`. The simplest approach is to set `authenticatedDefaultRole=USER` and promote admin users manually through the MapStore UI. + +If you want the token to drive admin assignment automatically, set `rolesClaim` to point at the claim that carries roles. MapStore supports dot-notation paths for nested claims, so you can reference Keycloak's default `realm_access.roles` structure directly: + +```properties +keycloakOAuth2Config.rolesClaim=realm_access.roles +keycloakOAuth2Config.roleMappings=admin:ADMIN,user:USER +keycloakOAuth2Config.authenticatedDefaultRole=USER +``` + +Alternatively, add a **User Realm Role** protocol mapper in Keycloak to emit a flat top-level claim and use that instead. In the Keycloak Admin Console, go to **Clients → mapstore-server → Client scopes → mapstore-server-dedicated → Add mapper → By configuration → User Realm Role**, then set: + +| Field | Value | +| --- | --- | +| Name | `roles` | +| Token Claim Name | `roles` | +| Add to ID token | On | +| Add to access token | On | +| Multivalued | On | + +This produces `"roles": ["admin", "user"]` at the token root: + +```properties +keycloakOAuth2Config.rolesClaim=roles +keycloakOAuth2Config.roleMappings=admin:ADMIN,user:USER +keycloakOAuth2Config.authenticatedDefaultRole=USER +``` + +##### Group mapping + +Add a **Group Membership** mapper in the same client scope: + +| Field | Value | +| --- | --- | +| Name | `groups` | +| Token Claim Name | `groups` | +| Full group path | Off | +| Add to ID token | On | + +This produces `"groups": ["my-group", "another-group"]` with display names (no leading `/`). Configure MapStore: + +```properties +keycloakOAuth2Config.groupsClaim=groups +keycloakOAuth2Config.groupMappings=my-keycloak-group:my-mapstore-group +# false = all groups from claim created/assigned; true = only mapped ones +keycloakOAuth2Config.dropUnmapped=false +``` diff --git a/docs/developer-guide/mapstore-migration-guide.md b/docs/developer-guide/mapstore-migration-guide.md index bef4218874..3f7fdc08bd 100644 --- a/docs/developer-guide/mapstore-migration-guide.md +++ b/docs/developer-guide/mapstore-migration-guide.md @@ -162,6 +162,121 @@ createPlugin('MyPlugin', { }); ``` +### GeoStore 2.6 — OpenID Connect configuration changes + +This version ships with **GeoStore 2.6**, which consolidates all OIDC providers (Keycloak, Google, and any other) into a single generic OIDC layer. If you are using OpenID Connect authentication, you must update your configuration. + +#### Update `pom.xml` (custom projects only) + +If you maintain a custom MapStore project, bump the GeoStore version in your `pom.xml`: + +```diff +-2.5.x ++2.6.x +``` + +Replace `2.5.x` / `2.6.x` with the exact release version used by this MapStore release (check the root `pom.xml` of MapStore for the current value of `geostore-webapp.version`). + +#### Keycloak: replace `jsonConfig` with `discoveryUrl` and add `oidc_providers` + +The Keycloak-specific configuration format (`keycloakOAuth2Config.jsonConfig`) is **no longer supported**. Replace it with the standard OIDC `discoveryUrl`. + +**Before:** + +```properties +keycloakOAuth2Config.enabled=true +keycloakOAuth2Config.jsonConfig={"realm":"myrealm","auth-server-url":"https://keycloak.example.com/","resource":"mapstore-server",...} +keycloakOAuth2Config.redirectUri=... +keycloakOAuth2Config.autoCreateUser=true +keycloakOAuth2Config.roleMappings=admin:ADMIN,user:USER +``` + +**After — `mapstore-ovr.properties`**: + +```properties +oidc_providers=keycloak + +keycloakOAuth2Config.enabled=true +keycloakOAuth2Config.clientId=mapstore-server +keycloakOAuth2Config.clientSecret= +keycloakOAuth2Config.sendClientSecret=true +keycloakOAuth2Config.discoveryUrl=https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration +keycloakOAuth2Config.redirectUri=... +keycloakOAuth2Config.autoCreateUser=true +keycloakOAuth2Config.roleMappings=admin:ADMIN,user:USER +``` + +All other `keycloakOAuth2Config.*` properties (`roleMappings`, `groupMappings`, `dropUnmapped`, `authenticatedDefaultRole`, `autoCreateUser`, `globalLogoutEnabled`, etc.) remain unchanged. + +#### Google: add `oidc_providers` + +```properties +# Add this line — Google is now registered via the generic OIDC layer +oidc_providers=google + +# All other googleOAuth2Config.* properties remain unchanged +googleOAuth2Config.enabled=true +googleOAuth2Config.clientId=... +# Add accessType=offline to request a refresh token (recommended) +googleOAuth2Config.accessType=offline +``` + +#### Keycloak direct user integration removed + +The **Direct user integration** feature (`keycloakRESTClient.*`, `-Dsecurity.integration=keycloak-direct`) has been removed. If you were using this feature, migrate to [LDAP integration](./integrations/users/ldap.md) instead. + +#### Multiple providers + +Multiple OIDC providers can now run simultaneously. Use a comma-separated list: + +```properties +oidc_providers=keycloak,google +``` + +!!! note + Use `oidc_providers` (underscore), not `oidc.providers` (dot). The dot form conflicts with Spring's `PropertyOverrideConfigurer`. + +#### `applicationContext.xml` update (MapStore projects only) + +Custom projects have their own copy of `web/src/main/resources/applicationContext.xml`. Add `ignoreInvalidKeys` to the order-10 `PropertyOverrideConfigurer` so that `oidc_providers` (a bare key with no dot) is accepted in `mapstore-ovr.properties`: + +```diff + + ++ + +``` + +Without this change, adding `oidc_providers=...` to `mapstore-ovr.properties` causes a startup failure (`Invalid key 'oidc_providers': expected 'beanName.property'`). + +#### Spring security XML update (MapStore projects only) + +If you maintain a **custom MapStore project** (i.e. you have your own `geostore-spring-security-db.xml`), you must update it to use the new dynamic OIDC provider mechanism. + +```diff + +- +- +- ++ + + +- +- +- ++ ++ + + +``` + +`oidcProviderRegistrar` reads `oidc_providers` from `mapstore-ovr.properties` at startup and registers one `{name}OAuth2Config` bean per provider. `PropertyOverrideConfigurer` then populates each bean from the matching `{name}OAuth2Config.*` properties. `compositeOpenIdFilter` handles the Authorization Code Flow for all active providers through a single filter chain entry. + ## Migration from 2026.01.01 to 2026.01.02 ### Monitored state available by default diff --git a/docs/user-guide/rule-manager.md b/docs/user-guide/rule-manager.md index 5ebfd4f85c..a819061ba1 100644 --- a/docs/user-guide/rule-manager.md +++ b/docs/user-guide/rule-manager.md @@ -24,7 +24,7 @@ To **Add a Rule**, the user can click the + diff --git a/mkdocs.yml b/mkdocs.yml index fba94cce46..25340351c2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -176,8 +176,6 @@ nav: - Users: - LDAP: 'developer-guide/integrations/users/ldap.md' - OpenID Connect: 'developer-guide/integrations/users/openId.md' - - Keycloak: 'developer-guide/integrations/users/keycloak.md' - - Keycloak-SSO: 'developer-guide/integrations/users/keycloak-sso-impl.md' - General: - GeoServer: 'developer-guide/integrations/geoserver.md' - Authentication: 'developer-guide/integrations/auth.md' diff --git a/pom.xml b/pom.xml index 2f62525f83..bb0c9934f5 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 1.10.2 - 2.5-SNAPSHOT + 2.6-SNAPSHOT 2.3.5 1.6-SNAPSHOT diff --git a/product/config/db/geostore-spring-security-db.xml b/product/config/db/geostore-spring-security-db.xml index 1b7a90bc60..9964684ac3 100644 --- a/product/config/db/geostore-spring-security-db.xml +++ b/product/config/db/geostore-spring-security-db.xml @@ -22,9 +22,7 @@ - - - + @@ -52,21 +50,28 @@ - - - + + + - - + + - + + - + + - + - + diff --git a/product/config/ldap/geostore-spring-security-ldap.xml b/product/config/ldap/geostore-spring-security-ldap.xml index 98487d1245..fc35f22c8d 100644 --- a/product/config/ldap/geostore-spring-security-ldap.xml +++ b/product/config/ldap/geostore-spring-security-ldap.xml @@ -25,8 +25,7 @@ - - + @@ -140,14 +139,21 @@ - - + + + - + - + + + + + - diff --git a/project/standard/templates/pom.xml b/project/standard/templates/pom.xml index d13db74f4b..15be6ed37d 100644 --- a/project/standard/templates/pom.xml +++ b/project/standard/templates/pom.xml @@ -30,7 +30,7 @@ 1.10.2 1.10-SNAPSHOT - 2.5-SNAPSHOT + 2.6-SNAPSHOT 1.6-SNAPSHOT 2.3.5 diff --git a/project/standard/templates/web/src/main/resources/applicationContext.xml b/project/standard/templates/web/src/main/resources/applicationContext.xml index 735a6436d1..7a72b8eabd 100644 --- a/project/standard/templates/web/src/main/resources/applicationContext.xml +++ b/project/standard/templates/web/src/main/resources/applicationContext.xml @@ -46,6 +46,7 @@ + diff --git a/web/src/config/db/geostore-spring-security-db.xml b/web/src/config/db/geostore-spring-security-db.xml index 7c0c068795..994fb4ed9a 100644 --- a/web/src/config/db/geostore-spring-security-db.xml +++ b/web/src/config/db/geostore-spring-security-db.xml @@ -22,8 +22,7 @@ - - + @@ -49,19 +48,24 @@ - - + + + - + - + - + - - + + + diff --git a/web/src/config/ldap/geostore-spring-security-ldap.xml b/web/src/config/ldap/geostore-spring-security-ldap.xml index 067ce5a79c..ff7e73f22a 100644 --- a/web/src/config/ldap/geostore-spring-security-ldap.xml +++ b/web/src/config/ldap/geostore-spring-security-ldap.xml @@ -25,8 +25,7 @@ - - + @@ -139,14 +138,21 @@ - - + + + - + - + + + + + -