diff --git a/README.md b/README.md index 34d4c99..059cf69 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ The Share sub-module provides a Keycloak-based filter and customisations that su - Share logout action triggering a Keycloak logout (logging user out of other applications handled by Keycloak if those support Keycloak back-channel logout requests) - [RFC 8693 OAuth 2.0 Token Exchange](https://tools.ietf.org/html/rfc8693) (a [preview functionality in Keycloak](https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange) to properly delegate the Share-tier authentication to the Repository, if signed on via Keycloak SSO +All authentication functionality of this addon is based on OpenID Connect. Though Keycloak does support SAML clients, no support was implemented to have Alfresco act as a SAML client against Keycloak as an alternative to OpenID Connect client behaviour. + # Configuration The configuration of both the Keycloak server and this module offer a large number of properties to adjust, and various modes of operation. Therefore, the following sub-documents have been created to provide details and guides: @@ -44,7 +46,7 @@ The configuration of both the Keycloak server and this module offer a large numb - [Getting Started (Simple Configuration)](./docs/Simple-Configuration.md) - Repository Configuration Reference - [Keycloak Subsystem](./docs/Reference-Repository-Subsystem.md) - - [Keycloak Adapter](./docs/Reference-Repository-Adapter.md) + - [Keycloak Adapter](./docs/Reference-Adapter.md) - [Extension API](./docs/Reference-Repository-Extension.md) - [Share Configuration Reference](./docs/Reference-Repository.md) diff --git a/docs/Reference-Adapter.md b/docs/Reference-Adapter.md new file mode 100644 index 0000000..752bd99 --- /dev/null +++ b/docs/Reference-Adapter.md @@ -0,0 +1,97 @@ +# Keycloak Adapter Configuration Reference + +Both the Repository and Share sub-modules of this addon use the Keycloak-provided adapter library to easily integrate with the Keycloak authentication server. In order to retain much of the adapter libraries flexibility when it comes to different deployment scenarios / configurations that Keycloak is able to handle, most of the configuration properties of this library have been exposed for configuration in both the Repository-tier Keycloak authentication subsystem and the Share-tier `share-config-custom.xml`. + +Configuration of adapter properties in the Repository-tier subsystem is straight-forward. Properties can be either configured in `alfresco-global.properties` or within the subsystems extension path, e.g. in a `alfresco/extension/subsystems/Authentication/keycloak/keycloak1/*.properties` (assuming the `authentication.chain` contains a value for `keycloak1:keycloak`). All adapter-specific properties use the common key prefix of `keycloak.adapter.`, with the remaining key denoting the specific property to set. In case a particular property of the Keycloak adapter library denotes a map, entries can be specified by simply appending the entry key to property key, e.g. if `keycloak.adapter.propertyX` denotes a map, `keycloak.adapter.propertyX.key1=value1` would set `value1` in the map for the key `key1`. + +Configuration of adapter properties in the Share-tier `share-config-custom.xml` is also straight-forward. Instead of using a common property key prefix, the configuration properties are all child elements of the same XML structure. Multiple configuration sections can be defined and their configuration will be properly merged with a reliable last-wins behaviour. Map entries are handled by specifying entry keys as sub-elements of the map-based property. As part of the merging behaviour, any element which is redefined in a later configuration section with an empty value is effectively removed from the configuration - this is in contrast to Alfresco Share's default merging behaviour which generally does not support easy removal of configurations. + +`share-config-custom.xml` example for adapter configuration: + +```xml + + + + http://localhost:8180/auth + alfresco + alfresco-share + + secret + ... + + + +``` + +## Supported Adapter Properties + +Note: This listing does not include the common property key prefix `keycloak.adapter.` that needs to be prepended in Repository-tier configuration. + +| Property | Default Value | Description | +| --- | ---: | --- | +| `auth-server-url` | `http://localhost:8180/auth` | Publically resolvable base URL to the Keycloak server to be used in redirect URLs and remote calls | +| `directAuthHost` | | Alternative base URL for the Keycloak server (excluding path) to be used for calls from Alfresco to Keycloak - useful e.g. in scenarios where the regular `auth-server-url` can not be resolved or round-trips via a public gateway / proxy should be avoided | +| `realm` | `alfresco` | Technical name of the Keycloak realm | +| `realm-public-key` | | Fixed public key of the realm (PEM string) - if not set, the public key(s) will be dynamically loaded and automatically refreshed after a configurable amount of times between JSON Web Key Store requests | +| `resource` | `alfresco` / `alfresco-share` | Technical name of the client set up in the realm | +| `ssl-required` | `none` | SSL requirement mode, controlling both redirect URL generation and redirect validation - defaults to `none` for simple default deployment, but should typically be set to `external` (localhost / loopback requests may use HTTP) or `all` | +| `confidential-port` | `-1` | SSL port to use when generating redirect URLs back to an SSL protected Alfresco resource - when a value of `-1` is configured, the addon will try to determine the port from the request, e.g. if a request contains a `X-Forwarded-Port` port with either port `80`/`443`, it is assumed Alfresco is operated behind a proxy and port `443` is used, otherwise the module will try to determine the SSL port from the raw request and fall back to the Tomcat default SSL port `8443`. This property **must to be changed** if Alfresco is operated in any constellation other than previously described. | +| `bearer-only` | `false` | Flag determining whether authentication handling is terminated after checking for bearer token | +| `autodetect-bearer-only` | `false` | Flag determining whether the Keycloak adapter library should attempt to determine if authentication handling for a request should be terminated after checking for bearer token (i.e. XMLHttpRequest, SOAPAction, or partial Faces requests, or any other request that does not accept `text/html`, `text/*` or `*/*` response content types) | +| `enable-basic-auth` | `false` | Flag determining whether the Keycloak adapter library should handle basic authentications - if enabled, this supersedes any Alfresco provided basic authentication handling, limiting users to Keycloak-authenticated users only | +| `public-client` | `false` | Flag whether the client uses the authentication flow of an OAuth public client | +| `credentials` | | Map of credential parameters use to configure the way the client authenticates against Keycloak for direct requests | +| `credentials.provider` | `secret` | Type of credential provider to use for client authentication - out-of-the-box, this addon supports `secret`, `jwt` and `secret-jwt` providers, while additional providers can be provided via Java's `ServiceLoader` facility, using the `ClientCredentialsProvider` interface in the shaded Repository / Share dependencies sub-module | +| `credentials.secret` | | Value of the shared secret to use when either the `secret` or `secret-jwt` credential provider is used | +| `credentials.algorithm` | `HS256` | Signing algorithm to use when the `secret-jwt` credential provider is used | +| `credentials.client-keystore-file` | | File or class path location of the client's keystore, when the `jwt` credential provider is used | +| `credentials.client-keystore-type` | | Type of the client's keystore, when the `jwt` credential provider is used | +| `credentials.client-keystore-password` | | Password of the client's keystore, when the `jwt` credential provider is used | +| `credentials.client-key-password` | | Password of the client's key inside the keystore, when the `jwt` credential provider is used | +| `credentials.client-key-alias` | | Alias of the client's key inside the keystore, when the `jwt` credential provider is used | +| `redirect-rewrite-rules` | | Map of key-value replacement tokens for dynamically rewriting the path of the generated redirect URI - note: only one / the first map entry will actually be processed by the Keycloak adapter library | +| `allow-any-hostname` | `false` | Flag whether to disable the host name verification on the Apache HTTP client used to call Keycloak | +| `disable-trust-manager` | `false` | Flag whether to disable the trust manager on the Apache HTTP client used to call Keycloak | +| `truststore` | | File or class path location of the client's custom truststore for validating Keycloak's SSL server certificate | +| `truststore-password` | | Password of the client's custom truststore for validating Keycloak's SSL server certificate | +| `client-keystore` | | File or class path location of the client's keystore containing its SSL client certificate to be presented to the Keycloak server | +| `client-keystore-password` | | Password for the client's keystore containing its SSL client certificate to be presented to the Keycloak server | +| `client-key-password` | | Password for the client's key within the keystore containing its SSL client certificate to be presented to the Keycloak server | +| `connection-pool-size` | `20` | Number of connections in the Apache HTTP clients connection pool for calls to the Keycloak server | +| `always-refresh-token` | `true` | Flag determining whether a user's access token should always be refreshed when its remaining time-to-live is less than the allowed minimum value, or it has already expired - if `false`, user logins in Alfresco will effectively expire and require a new (transparent) authentication via Keycloak, as no component in the addon performs an explicit refresh | +| `adapter-state-cookie-path` | | The path to use for the client cookie holding adapter state during execution of the authentication redirects - if not set, this will use the context path from the raw request, which is typically the correct value to use | +| `principal-attribute` | | The name of the attribute to extract from the access token as an override to the token's subject for the name of the authenticated principal - since this addon does not use the principal name for anything (only the `preferred_username`), this configuration likely has no practical effect if changed | +| `token-minimum-time-to-live` | | The minimum allowed time-to-live for an access token in seconds - if an access token is returned by Keycloak in exchange for an authorisation code or as part of a token refresh with a lower time-to-live, the validation of that token will fail | +| `min-time-between-jwks-requests` | `10` | The minimum time in seconds that must be elapsed between two JSON Web Key Store requests to Keycloak to load public key(s) of the realm | +| `public-key-cache-ttl` | `86400` | Time-to-live in seconds for public key cache entries | +| `ignore-oauth-query-parameter` | `false` | Flag determining whether OAuth `access_token` in an URL query is to be ignored | +| `verify-token-audience` | `true` / `false` | Flag enabling validation of the audience specified in an access token, enabled by default on the Repository-tier - must be disabled if Share or any other application which authenticates users via Keycloak is not delegating user authentication using RFC 8693 OAuth 2.0 Token Exchange | + +## Non-Standard Adapter Properties + +The following properties are not supported by the Keycloak adapter library, but have been added by the addon for customisation of the adapter's behaviour. + +| Property | Default Value | Description | +| --- | ---: | --- | +| `connectionTimeout` | `-1` | Connect timeout for the Apache HTTP client used in calls to Keycloak | +| `socketTimeout` | `-1` | General socket timeout for the Apache HTTP client used in calls to Keycloak | + +## Unsupported Adapter Properties + +This listing details configuration properties from the Keycloak adapter library which are not supported by this addon and may result in `UnsupportedOperationException` or other errors potentially being triggered at runtime when set to a non-default value. + +| Property | Default Value | Description | +| --- | ---: | --- | +| `use-resource-role-mappings` | `false` | Flag effectively limited to determining whether resource config on Keycloak can override the realm's caller verification setting. If caller verification is required, clients must provide a certificate. - This is not supported by the Keycloak adapter library outside of a JBoss deployment. | +| `enable-cors` | `false` | Flag enabling special handling for CORS requests (e.g. containing an Origin header). - No tests or special considerations have been done for CORS requests, as CORS should not be relevant in a proper Alfresco setup (e.g. whenever CORS would be needed for an ADF app or similar SPA, a simple proxy should do the trick without all the complexities of CORS). Additionally, CORS handling in Alfresco is already [provided out-of-the-box](https://docs.alfresco.com/6.2/concepts/enabling-cors.html). | +| `cors-max-age` | `-1` | Value for the HTTP `Access-Control-Max-Age` response header | +| `cors-allowed-headers` | | Value for the HTTP `Access-Control-Allow-Headers` response header | +| `cors-allowed-methods` | | Value for the HTTP `Access-Control-Allow-Methods` response header | +| `cors-exposed-headers` | | Value for the HTTP `Access-Control-Expose-Headers`response header | +| `expose-token` | `false` | Flag determining whether CORS requests can retrieve a bearer token via special request URI | +| `register-node-at-startup` | `false` | Flag determining whether the Keycloak adapter will register the node (server) with the Keycloak server - not relevant on Alfresco installations as this relates to [clustering on JBoss server technology](https://www.keycloak.org/docs/latest/securing_apps/#_applicationclustering), this addon already handles relevant caches for potential clustering in Alfresco Enterprise, and the necessary component of the Keycloak adapter library is not used in the integration of this addon | +| `register-node-period` | `-1` | Time in seconds between node registration requests | +| `token-store` | `session` | Mode for how the Keycloak adapter stores user account information - related to clustering like previous two settings and not relevant for the integration as provided by the addon | +| `turn-off-change-session-id-on-login` | | Completely unused flag in the Keycloak adapter library | +| `policy-enforcer` | | Complex configuration object determining fine-grained access policies to the Repository / Share application. - This is currently not supported for configuration by the addon due to use of complex object structures | +| `enable-pkce` | `false` | RFC 7636 - Flag enabling the use of the Proof Key for Code Exchange for OAuth public clients. - This has not yet implemented by the Keycloak adapter library. | \ No newline at end of file diff --git a/docs/Simple-Configuration.md b/docs/Simple-Configuration.md index 4b519bd..f0498de 100644 --- a/docs/Simple-Configuration.md +++ b/docs/Simple-Configuration.md @@ -99,11 +99,11 @@ The following core configuration properties can be set (more extensive list in t | `...groupFilter.containedInGroup.property.groupPaths` | | Comma-separated list of group paths (e.g. `/Group A/Group B,/Group A/Group C`) to use in filtering which groups are synchronised to Alfresco (by default - configured separately - any match qualifies, and transitive containment is considered) | | `...groupFilter.containedInGroup.property.groupIds` | | Comma-separated list of group IDs to use in filtering which groups are synchronised to Alfresco (by default - configured separately - any match qualifies, and transitive containment is considered) | | `keycloak.adapter.auth-server-url` | `http://localhost:8180/auth` | Publically resolvable base URL to the Keycloak server to be used in redirect URLs and remote calls | -| `keycloak.authentication.directAuthHost` | | Alternative base URL for the Keycloak server (excluding path) to be used for calls from Alfresco to Keycloak - useful e.g. in scenarios where the regular `auth-server-url` can not be resolved by the Alfresco Repository host or round-trips via a public gateway / proxy should be avoided | -| `keycloak.adapter.realm` | `alfresco` | Technical name of the Keycloak realm | -| `keycloak.adapter.resource` | `alfresco` | Technical name of the client set up for the Alfresco Repository in the realm | -| `keycloak.adapter.keycloak.adapter.credentials.secret` | | Shared secret for validation of authorisation codes / access tokens | -| `keycloak.adapter.verify-token-audience` | `true` | Flag enabling validation of the audience specified in an access token - must be disabled if Share or any other application which authenticates users via Keycloak is not delegating user authentication using RFC 8693 OAuth 2.0 Token Exchange | +| `...directAuthHost` | | Alternative base URL for the Keycloak server (excluding path) to be used for calls from Alfresco to Keycloak - useful e.g. in scenarios where the regular `auth-server-url` can not be resolved by the Alfresco Repository host or round-trips via a public gateway / proxy should be avoided | +| `...realm` | `alfresco` | Technical name of the Keycloak realm | +| `...resource` | `alfresco` | Technical name of the client set up for the Alfresco Repository in the realm | +| `...keycloak.adapter.credentials.secret` | | Shared secret for validation of authorisation codes / access tokens | +| `...verify-token-audience` | `true` | Flag enabling validation of the audience specified in an access token - must be disabled if Share or any other application which authenticates users via Keycloak is not delegating user authentication using RFC 8693 OAuth 2.0 Token Exchange | ## Alfresco Share diff --git a/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication-context.xml b/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication-context.xml index 70085ba..ed1f3ac 100644 --- a/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication-context.xml +++ b/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication-context.xml @@ -30,9 +30,9 @@ - - - + + + @@ -135,7 +135,6 @@ - diff --git a/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication.properties b/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication.properties index 96f0965..15601ce 100644 --- a/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication.properties +++ b/repository/src/main/globalConfig/subsystems/Authentication/keycloak/keycloak-authentication.properties @@ -13,19 +13,20 @@ keycloak.authentication.mapPersonPropertiesOnLogin=true keycloak.authentication.authenticateFTP=true keycloak.authentication.silentRemoteUserValidationFailure=true -keycloak.authentication.connectionTimeout=-1 -keycloak.authentication.socketTimeout=-1 -keycloak.authentication.sslRedirectPort=8443 keycloak.authentication.bodyBufferLimit=10485760 # override for a direct route to the auth server host # useful primarily for Docker-ized deployments where container running Alfresco cannot resolve the auth server via the public DNS name -keycloak.authentication.directAuthHost= +keycloak.adapter.directAuthHost= +# other custom adapter properties not part of default Keycloak adapter library +keycloak.adapter.connectionTimeout=-1 +keycloak.adapter.socketTimeout=-1 keycloak.adapter.auth-server-url=http://localhost:8180/auth keycloak.adapter.realm=alfresco keycloak.adapter.resource=alfresco keycloak.adapter.ssl-required=none +keycloak.adapter.confidential-port=-1 keycloak.adapter.public-client=false keycloak.adapter.credentials.provider=secret keycloak.adapter.credentials.secret= diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationFilter.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationFilter.java index 5adf4bb..b64da14 100644 --- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationFilter.java +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/authentication/KeycloakAuthenticationFilter.java @@ -126,10 +126,6 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter protected int bodyBufferLimit = DEFAULT_BODY_BUFFER_LIMIT; - // use 8443 as default SSL redirect based on Tomcat default server.xml configuration - // can't rely on SysAdminParams#getAlfrescoPort either because that may be proxied / non-SSL - protected int sslRedirectPort = 8443; - protected KeycloakDeployment keycloakDeployment; protected SessionIdMapper sessionIdMapper; @@ -250,15 +246,6 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter this.bodyBufferLimit = bodyBufferLimit; } - /** - * @param sslRedirectPort - * the sslRedirectPort to set - */ - public void setSslRedirectPort(final int sslRedirectPort) - { - this.sslRedirectPort = sslRedirectPort; - } - /** * @param keycloakDeployment * the keycloakDeployment to set @@ -540,8 +527,11 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter final OIDCFilterSessionStore tokenStore = new OIDCFilterSessionStore(req, facade, this.bodyBufferLimit > 0 ? this.bodyBufferLimit : DEFAULT_BODY_BUFFER_LIMIT, this.keycloakDeployment, this.sessionIdMapper); + + final int sslPort = this.determineLikelySslPort(req); + final FilterRequestAuthenticator authenticator = new FilterRequestAuthenticator(this.keycloakDeployment, tokenStore, facade, req, - this.sslRedirectPort); + sslPort); final AuthOutcome authOutcome = authenticator.authenticate(); if (authOutcome == AuthOutcome.AUTHENTICATED) @@ -1252,6 +1242,38 @@ public class KeycloakAuthenticationFilter extends BaseAuthenticationFilter } } + /** + * Determines the likely SSL port to be used in redirects from the incoming request. This operation should only be used to determine a + * technical default value in lieu of an explicitly configured value. + * + * @param req + * the incoming request + * @return the assumed SSL port to be used in redirects + */ + protected int determineLikelySslPort(final HttpServletRequest req) + { + int rqPort = req.getServerPort(); + final String forwardedPort = req.getHeader("X-Forwarded-Port"); + if (forwardedPort != null && forwardedPort.matches("^\\d+$")) + { + rqPort = Integer.parseInt(forwardedPort); + } + final int sslPort; + if (rqPort == 80 || rqPort == 443) + { + sslPort = 443; + } + else if (req.isSecure() && "https".equals(req.getScheme())) + { + sslPort = rqPort; + } + else + { + sslPort = 8443; + } + return sslPort; + } + /** * {@inheritDoc} */ diff --git a/repository/src/test/docker/alfresco/extension/alfresco-global.addition.properties b/repository/src/test/docker/alfresco/extension/alfresco-global.addition.properties index 55e8405..ea636db 100644 --- a/repository/src/test/docker/alfresco/extension/alfresco-global.addition.properties +++ b/repository/src/test/docker/alfresco/extension/alfresco-global.addition.properties @@ -25,7 +25,7 @@ keycloak.adapter.credentials.provider=secret keycloak.adapter.credentials.secret=6f70a28f-98cd-41ca-8f2f-368a8797d708 # localhost in auth-server-url won't work for direct access in a Docker deployment -keycloak.authentication.directAuthHost=http://keycloak:8080 +keycloak.adapter.directAuthHost=http://keycloak:8080 keycloak.synchronization.userFilter.containedInGroup.property.groupPaths=/Test A keycloak.synchronization.groupFilter.containedInGroup.property.groupPaths=/Test A diff --git a/share/src/main/config/default-config.xml b/share/src/main/config/default-config.xml index 20a06eb..0bfed4d 100644 --- a/share/src/main/config/default-config.xml +++ b/share/src/main/config/default-config.xml @@ -31,8 +31,6 @@ true true false - - 8443 10485760 1000 true @@ -43,8 +41,9 @@ http://localhost:8180/auth alfresco - alfresco + alfresco-share none + -1 false diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElement.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElement.java index 7a0f85e..d47cd7e 100644 --- a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElement.java +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElement.java @@ -39,8 +39,6 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement protected final ConfigValueHolder bodyBufferLimit = new ConfigValueHolder<>(); - protected final ConfigValueHolder sslRedirectPort = new ConfigValueHolder<>(); - protected final ConfigValueHolder sessionMapperLimit = new ConfigValueHolder<>(); protected final ConfigValueHolder ignoreDefaultFilter = new ConfigValueHolder<>(); @@ -125,23 +123,6 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement return this.bodyBufferLimit.getValue(); } - /** - * @param sslRedirectPort - * the sslRedirectPort to set - */ - public void setSslRedirectPort(final Integer sslRedirectPort) - { - this.sslRedirectPort.setValue(sslRedirectPort); - } - - /** - * @return the sslRedirectPort - */ - public Integer getSslRedirectPort() - { - return this.sslRedirectPort.getValue(); - } - /** * @param sessionMapperLimit * the sessionMapperLimit to set @@ -265,16 +246,6 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement otherConfigElement.getBodyBufferLimit() != null ? otherConfigElement.getBodyBufferLimit() : this.getBodyBufferLimit()); } - if (otherConfigElement.sslRedirectPort.isUnset()) - { - combined.sslRedirectPort.unset(); - } - else - { - combined.setSslRedirectPort( - otherConfigElement.getSslRedirectPort() != null ? otherConfigElement.getSslRedirectPort() : this.getSslRedirectPort()); - } - if (otherConfigElement.sessionMapperLimit.isUnset()) { combined.sessionMapperLimit.unset(); @@ -341,9 +312,6 @@ public class KeycloakAuthenticationConfigElement extends BaseCustomConfigElement builder.append("bodyBufferLimit="); builder.append(this.bodyBufferLimit); builder.append(", "); - builder.append("sslRedirectPort="); - builder.append(this.sslRedirectPort); - builder.append(", "); builder.append("sessionMapperLimit="); builder.append(this.sessionMapperLimit); builder.append(", "); diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElementReader.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElementReader.java index aceda88..fdd968e 100644 --- a/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElementReader.java +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/config/KeycloakAuthenticationConfigElementReader.java @@ -65,13 +65,6 @@ public class KeycloakAuthenticationConfigElementReader implements ConfigElementR configElement.setBodyBufferLimit(value.isEmpty() ? null : Integer.valueOf(value)); } - final Element sslRedirectPort = element.element("ssl-redirect-port"); - if (sslRedirectPort != null) - { - final String value = sslRedirectPort.getTextTrim(); - configElement.setSslRedirectPort(value.isEmpty() ? null : Integer.valueOf(value)); - } - final Element sessionMapperLimit = element.element("session-mapper-limit"); if (sessionMapperLimit != null) { diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java index 3d8d1cb..77b3e02 100644 --- a/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java @@ -556,7 +556,6 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I .getConfig(KeycloakConfigConstants.KEYCLOAK_CONFIG_SECTION_NAME).getConfigElement(KeycloakAuthenticationConfigElement.NAME); final Integer bodyBufferLimit = keycloakAuthConfig.getBodyBufferLimit(); - final Integer sslRedirectPort = keycloakAuthConfig.getSslRedirectPort(); final OIDCServletHttpFacade facade = new OIDCServletHttpFacade(req, res); @@ -604,7 +603,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I } else { - this.processFilterAuthentication(context, req, res, chain, bodyBufferLimit, sslRedirectPort, facade); + this.processFilterAuthentication(context, req, res, chain, bodyBufferLimit, facade); } } @@ -689,8 +688,6 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I * @param bodyBufferLimit * the configured size limit to apply to any HTTP POST/PUT body buffering that may need to be applied to process the * authentication via an intermediary redirect - * @param sslRedirectPort - * the configured port to use for any forced redirection to HTTPS/SSL communication * @param facade * the Keycloak HTTP facade * @throws IOException @@ -699,8 +696,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I * if any error occurs during Keycloak authentication or processing of the filter chain */ protected void processFilterAuthentication(final ServletContext context, final HttpServletRequest req, final HttpServletResponse res, - final FilterChain chain, final Integer bodyBufferLimit, final Integer sslRedirectPort, final OIDCServletHttpFacade facade) - throws IOException, ServletException + final FilterChain chain, final Integer bodyBufferLimit, final OIDCServletHttpFacade facade) throws IOException, ServletException { final OIDCFilterSessionStore tokenStore = new OIDCFilterSessionStore(req, facade, bodyBufferLimit != null ? bodyBufferLimit.intValue() : DEFAULT_BODY_BUFFER_LIMIT, this.keycloakDeployment, @@ -708,7 +704,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I // use 8443 as default SSL redirect based on Tomcat default server.xml configuration final FilterRequestAuthenticator authenticator = new FilterRequestAuthenticator(this.keycloakDeployment, tokenStore, facade, req, - sslRedirectPort != null ? sslRedirectPort.intValue() : 8443); + this.keycloakDeployment.getConfidentialPort()); final AuthOutcome authOutcome = authenticator.authenticate(); if (authOutcome == AuthOutcome.AUTHENTICATED) @@ -814,7 +810,6 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I .getConfig(KeycloakConfigConstants.KEYCLOAK_CONFIG_SECTION_NAME).getConfigElement(KeycloakAuthenticationConfigElement.NAME); final Integer bodyBufferLimit = keycloakAuthConfig.getBodyBufferLimit(); - final Integer sslRedirectPort = keycloakAuthConfig.getSslRedirectPort(); // fake a request that will yield a redirect final HttpServletRequest wrappedReq = new HttpServletRequestWrapper(req) @@ -838,9 +833,9 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I final OIDCFilterSessionStore tokenStore = new OIDCFilterSessionStore(req, captureFacade, bodyBufferLimit != null ? bodyBufferLimit.intValue() : DEFAULT_BODY_BUFFER_LIMIT, this.keycloakDeployment, null); - // use 8443 as default SSL redirect based on Tomcat default server.xml configuration - final OAuthRequestAuthenticator authenticator = new OAuthRequestAuthenticator(null, captureFacade, this.keycloakDeployment, - sslRedirectPort != null ? sslRedirectPort.intValue() : 8443, tokenStore); + final int sslPort = this.determineLikelySslPort(req); + final OAuthRequestAuthenticator authenticator = new OAuthRequestAuthenticator(null, captureFacade, this.keycloakDeployment, sslPort, + tokenStore); final AuthOutcome authOutcome = authenticator.authenticate(); if (authOutcome != AuthOutcome.NOT_ATTEMPTED) @@ -1781,6 +1776,38 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I } } + /** + * Determines the likely SSL port to be used in redirects from the incoming request. This operation should only be used to determine a + * technical default value in lieu of an explicitly configured value. + * + * @param req + * the incoming request + * @return the assumed SSL port to be used in redirects + */ + protected int determineLikelySslPort(final HttpServletRequest req) + { + int rqPort = req.getServerPort(); + final String forwardedPort = req.getHeader("X-Forwarded-Port"); + if (forwardedPort != null && forwardedPort.matches("^\\d+$")) + { + rqPort = Integer.parseInt(forwardedPort); + } + final int sslPort; + if (rqPort == 80 || rqPort == 443) + { + sslPort = 443; + } + else if (req.isSecure() && "https".equals(req.getScheme())) + { + sslPort = rqPort; + } + else + { + sslPort = 8443; + } + return sslPort; + } + /** * Sets up a forced route for the Keycloak-library backing HTTP client if configured. This may be necessary to deal with situations * where Share cannot use the public address of the authentication server (used in authentication redirects) to talk with the server diff --git a/share/src/test/java/de/acosix/alfresco/keycloak/share/config/KeycloakAdapterConfigTest.java b/share/src/test/java/de/acosix/alfresco/keycloak/share/config/KeycloakAdapterConfigTest.java index b934283..266ece2 100644 --- a/share/src/test/java/de/acosix/alfresco/keycloak/share/config/KeycloakAdapterConfigTest.java +++ b/share/src/test/java/de/acosix/alfresco/keycloak/share/config/KeycloakAdapterConfigTest.java @@ -52,7 +52,6 @@ public class KeycloakAdapterConfigTest Assert.assertTrue(keycloakAuthConfig.getEnhanceLoginForm()); Assert.assertTrue(keycloakAuthConfig.getEnableSsoFilter()); Assert.assertFalse(keycloakAuthConfig.getForceKeycloakSso()); - Assert.assertEquals(Integer.valueOf(8443), keycloakAuthConfig.getSslRedirectPort()); Assert.assertEquals(Integer.valueOf(10485760), keycloakAuthConfig.getBodyBufferLimit()); Assert.assertEquals(Integer.valueOf(1000), keycloakAuthConfig.getSessionMapperLimit()); @@ -99,7 +98,6 @@ public class KeycloakAdapterConfigTest Assert.assertFalse(keycloakAuthConfig.getEnhanceLoginForm()); Assert.assertFalse(keycloakAuthConfig.getEnableSsoFilter()); Assert.assertFalse(keycloakAuthConfig.getForceKeycloakSso()); - Assert.assertEquals(Integer.valueOf(8443), keycloakAuthConfig.getSslRedirectPort()); Assert.assertEquals(Integer.valueOf(10485760), keycloakAuthConfig.getBodyBufferLimit()); Assert.assertEquals(Integer.valueOf(2000), keycloakAuthConfig.getSessionMapperLimit()); diff --git a/share/src/test/resources/default-config.xml b/share/src/test/resources/default-config.xml index eabf189..8d12e5c 100644 --- a/share/src/test/resources/default-config.xml +++ b/share/src/test/resources/default-config.xml @@ -40,6 +40,7 @@ alfresco + http://keycloak:8080 http://localhost:8180/auth alfresco