AUTH-86/AUTH-85: Move SSO token implementation back to Alfresco Community

Moved current token authentication code from Enterprise repository to Community repository project.
This commit is contained in:
Gavin Cornwell
2018-03-15 11:21:07 +00:00
parent c9a20cc17f
commit 6b7b449938
13 changed files with 1437 additions and 6 deletions

67
pom.xml
View File

@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>alfresco-repository</artifactId>
<name>Alfresco Repository</name>
<version>6.32-SNAPSHOT</version>
<version>6.32-TOKEN-AUTH-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
@@ -56,7 +56,8 @@
<dependency.mysql.version>5.1.40</dependency.mysql.version>
<dependency.mariadb.version>2.0.1</dependency.mariadb.version>
<dependency.antlr.version>3.5.2</dependency.antlr.version>
<dependency.keycloak.version>3.4.3.Final</dependency.keycloak.version>
<dependency.jboss.logging.version>3.3.1.Final</dependency.jboss.logging.version>
</properties>
<dependencyManagement>
@@ -693,6 +694,68 @@
</exclusions>
</dependency>
<!-- Keycloak dependencies -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${dependency.jboss.logging.version}</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.mockito</groupId>

View File

@@ -0,0 +1,61 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.token;
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCAuthenticationError.Reason;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.HttpFacade;
/**
* Extends the Keycloak BearerTokenRequestAuthenticator class to capture the error description
* when token valiation fails.
*
* @author Gavin Cornwell
*/
public class AlfrescoBearerTokenRequestAuthenticator extends BearerTokenRequestAuthenticator
{
private String validationFailureDescription;
public AlfrescoBearerTokenRequestAuthenticator(KeycloakDeployment deployment)
{
super(deployment);
}
public String getValidationFailureDescription()
{
return this.validationFailureDescription;
}
@Override
protected AuthChallenge challengeResponse(HttpFacade facade, Reason reason, String error, String description)
{
this.validationFailureDescription = description;
return super.challengeResponse(facade, reason, error, description);
}
}

View File

@@ -0,0 +1,81 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.token;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.springframework.beans.factory.InitializingBean;
public class AlfrescoKeycloakAdapterConfig extends AdapterConfig implements InitializingBean
{
private static Log logger = LogFactory.getLog(AlfrescoKeycloakAdapterConfig.class);
private static final String CREDENTIALS_SECRET = "keycloak.credentials.secret";
private static final String CREDENTIALS_PROVIDER = "keycloak.credentials.provider";
private Properties globalProperties;
public void setGlobalProperties(Properties globalProperties)
{
this.globalProperties = globalProperties;
}
@Override
public void afterPropertiesSet() throws Exception
{
// programatically build the more complex objects i.e. credentials
Map<String, Object> credentials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
String secret = this.globalProperties.getProperty(CREDENTIALS_SECRET);
if (secret != null && !secret.isEmpty())
{
credentials.put("secret", secret);
}
String provider = this.globalProperties.getProperty(CREDENTIALS_PROVIDER);
if (provider != null && !provider.isEmpty())
{
credentials.put("provider", provider);
}
// TODO: add support for redirect-rewrite-rules and policy-enforcer if and when we need to support it
if (!credentials.isEmpty())
{
this.setCredentials(credentials);
if (logger.isDebugEnabled())
{
logger.debug("Created credentials map from config: " + credentials);
}
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.token;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.springframework.beans.factory.FactoryBean;
/**
* Creates an instance of a KeycloakDeployment object.
*
* @author Gavin Cornwell
*/
public class AlfrescoKeycloakDeploymentFactoryBean implements FactoryBean<KeycloakDeployment>
{
private static Log logger = LogFactory.getLog(AlfrescoKeycloakDeploymentFactoryBean.class);
private AdapterConfig keycloakAdapterConfig;
public void setAdapterConfig(AdapterConfig adapterConfig)
{
this.keycloakAdapterConfig = adapterConfig;
}
@Override
public KeycloakDeployment getObject() throws Exception
{
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(this.keycloakAdapterConfig);
if (logger.isInfoEnabled())
{
logger.info("Keycloak JWKS URL: " + deployment.getJwksUrl());
logger.info("Keycloak Realm: " + deployment.getRealm());
logger.info("Keycloak Client ID: " + deployment.getResourceName());
}
return deployment;
}
@Override
public Class<KeycloakDeployment> getObjectType()
{
return KeycloakDeployment.class;
}
@Override
public boolean isSingleton()
{
return true;
}
}

View File

@@ -0,0 +1,107 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.token;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.adapters.servlet.ServletHttpFacade;
/**
* Keycloak HttpFacade wrapper so we can re-use Keycloak authenticator classes.
*
* @author Gavin Cornwell
*/
public class AlfrescoKeycloakHttpFacade extends ServletHttpFacade
{
public AlfrescoKeycloakHttpFacade(HttpServletRequest request)
{
super(request, null);
}
@Override
public Response getResponse()
{
// return our dummy NoOp implementation so we don't effect the ACS response
return new NoOpResponseFacade();
}
/**
* NoOp implementation of Keycloak Response interface.
*/
private class NoOpResponseFacade implements Response
{
@Override
public void setStatus(int status)
{
}
@Override
public void addHeader(String name, String value)
{
}
@Override
public void setHeader(String name, String value)
{
}
@Override
public void resetCookie(String name, String path)
{
}
@Override
public void setCookie(String name, String value, String path, String domain, int maxAge,
boolean secure, boolean httpOnly)
{
}
@Override
public OutputStream getOutputStream()
{
return new ByteArrayOutputStream();
}
@Override
public void sendError(int code)
{
}
@Override
public void sendError(int code, String message)
{
}
@Override
public void end()
{
}
}
}

View File

@@ -0,0 +1,247 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.token;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.service.cmr.security.PersonService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.keycloak.adapters.BasicAuthRequestAuthenticator;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.representations.AccessToken;
/**
* A {@link RemoteUserMapper} implementation that detects and validates JWTs.
*
* @author Gavin Cornwell
*/
public class TokenRemoteUserMapper implements RemoteUserMapper, ActivateableBean
{
private static Log logger = LogFactory.getLog(TokenRemoteUserMapper.class);
/** Is the mapper enabled */
private boolean isEnabled;
/** Are token validation failures handled silently? */
private boolean isValidationFailureSilent;
/** The person service. */
private PersonService personService;
/** The Keycloak deployment object */
private KeycloakDeployment keycloakDeployment;
/**
* Sets the active flag
*
* @param isEnabled true to enable the subsystem
*/
public void setActive(boolean isEnabled)
{
this.isEnabled = isEnabled;
}
/**
* Determines whether token validation failures are silent
*
* @param silent true to silently fail, false to throw an exception
*/
public void setValidationFailureSilent(boolean silent)
{
this.isValidationFailureSilent = silent;
}
/**
* Sets the person service.
*
* @param personService
* the person service
*/
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
public void setKeycloakDeployment(KeycloakDeployment deployment)
{
this.keycloakDeployment = deployment;
}
/*
* (non-Javadoc)
* @see org.alfresco.web.app.servlet.RemoteUserMapper#getRemoteUser(javax.servlet.http.HttpServletRequest)
*/
public String getRemoteUser(HttpServletRequest request)
{
if (logger.isDebugEnabled())
logger.debug("Retrieving username from http request...");
if (!this.isEnabled)
{
if (logger.isDebugEnabled())
logger.debug("TokenRemoteUserMapper is disabled, returning null.");
return null;
}
String headerUserId = extractUserFromHeader(request);
if (headerUserId != null)
{
// Normalize the user ID taking into account case sensitivity settings
String normalizedUserId = normalizeUserId(headerUserId);
if (logger.isDebugEnabled())
logger.debug("Returning username: " + normalizedUserId);
return normalizedUserId;
}
return null;
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive()
*/
public boolean isActive()
{
return this.isEnabled;
}
/**
* Extracts the user name from the JWT in the given request.
*
* @param request The request containing the JWT
* @return The user name or null if it can not be determined
*/
private String extractUserFromHeader(HttpServletRequest request)
{
String userName = null;
AlfrescoKeycloakHttpFacade facade = new AlfrescoKeycloakHttpFacade(request);
// try authenticating with bearer token first
if (logger.isDebugEnabled())
{
logger.debug("Trying bearer token...");
}
AlfrescoBearerTokenRequestAuthenticator tokenAuthenticator =
new AlfrescoBearerTokenRequestAuthenticator(this.keycloakDeployment);
AuthOutcome tokenOutcome = tokenAuthenticator.authenticate(facade);
if (logger.isDebugEnabled())
{
logger.debug("Bearer token outcome: " + tokenOutcome);
}
if (tokenOutcome == AuthOutcome.FAILED && !isValidationFailureSilent)
{
throw new AuthenticationException("Token validation failed: " +
tokenAuthenticator.getValidationFailureDescription());
}
if (tokenOutcome == AuthOutcome.AUTHENTICATED)
{
userName = extractUserFromToken(tokenAuthenticator.getToken());
}
else
{
// if bearer token failed, try basic auth, if enabled
if (this.keycloakDeployment.isEnableBasicAuth())
{
if (logger.isDebugEnabled())
{
logger.debug("Trying basic auth...");
}
BasicAuthRequestAuthenticator basicAuthenticator =
new BasicAuthRequestAuthenticator(this.keycloakDeployment);
AuthOutcome basicOutcome = basicAuthenticator.authenticate(facade);
if (logger.isDebugEnabled())
{
logger.debug("Basic auth outcome: " + basicOutcome);
}
// if auth was successful, extract username and return
if (basicOutcome == AuthOutcome.AUTHENTICATED)
{
userName = extractUserFromToken(basicAuthenticator.getToken());
}
}
}
return userName;
}
private String extractUserFromToken(AccessToken jwt)
{
// retrieve the preferred_username claim
String userName = jwt.getPreferredUsername();
if (logger.isDebugEnabled())
logger.debug("Extracted username: " + userName);
return userName;
}
/**
* Normalizes a user id, taking into account existing user accounts and case sensitivity settings.
*
* @param userId
* the user id
* @return the string
*/
private String normalizeUserId(final String userId)
{
if (userId == null)
{
return null;
}
String normalized = AuthenticationUtil.runAs(new RunAsWork<String>()
{
public String doWork() throws Exception
{
return personService.getUserIdentifier(userId);
}
}, AuthenticationUtil.getSystemUserName());
if (logger.isDebugEnabled())
logger.debug("Normalized user name for '" + userId + "': " + normalized);
return normalized == null ? userId : normalized;
}
}

View File

@@ -0,0 +1,222 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<bean id="authenticationComponent" class="org.alfresco.repo.security.authentication.SimpleAcceptOrRejectAllAuthenticationComponentImpl"
parent="authenticationComponentBase">
<property name="authenticationDao">
<ref bean="authenticationDao" />
</property>
<property name="nodeService">
<ref bean="nodeService" />
</property>
<property name="personService">
<ref bean="personService" />
</property>
<property name="transactionService">
<ref bean="transactionService" />
</property>
<property name="defaultAdministratorUserNameList">
<value>${token.authentication.defaultAdministratorUserNames}</value>
</property>
</bean>
<!-- Wrapped version to be used within subsystem -->
<bean id="AuthenticationComponent" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="proxyInterfaces">
<list>
<value>org.alfresco.repo.security.authentication.AuthenticationComponent</value>
</list>
</property>
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref bean="authenticationComponent" />
</property>
<property name="transactionAttributes">
<props>
<prop key="*">${server.transaction.mode.default}</prop>
</props>
</property>
</bean>
<!-- Authentication service for chaining -->
<bean id="localAuthenticationService" class="org.alfresco.repo.security.authentication.AuthenticationServiceImpl">
<property name="ticketComponent">
<ref bean="ticketComponent" />
</property>
<property name="authenticationComponent">
<ref bean="authenticationComponent" />
</property>
<property name="sysAdminParams">
<ref bean="sysAdminParams" />
</property>
<property name="protectedUsersCache">
<ref bean="protectedUsersCache" />
</property>
<property name="protectionEnabled">
<!-- for external authentication ignore ${authentication.protection.enabled} -->
<!-- the external users are already authenticated by something else -->
<value>false</value>
</property>
<property name="protectionLimit">
<value>${authentication.protection.limit}</value>
</property>
<property name="protectionPeriodSeconds">
<value>${authentication.protection.periodSeconds}</value>
</property>
<property name="personService">
<ref bean="PersonService" />
</property>
</bean>
<bean name="keycloakAdpapterConfig" class="org.alfresco.repo.security.authentication.token.AlfrescoKeycloakAdapterConfig">
<property name="globalProperties">
<ref bean="global-properties" />
</property>
<property name="realm">
<value>${keycloak.realm}</value>
</property>
<property name="realmKey">
<value>${keycloak.realm-public-key:#{null}}</value>
</property>
<property name="authServerUrl">
<value>${keycloak.auth-server-url}</value>
</property>
<property name="sslRequired">
<value>${keycloak.ssl-required:external}</value>
</property>
<property name="confidentialPort">
<value>${keycloak.confidential-port:0}</value>
</property>
<property name="resource">
<value>${keycloak.resource}</value>
</property>
<property name="useResourceRoleMappings">
<value>${keycloak.use-resource-role-mappings:false}</value>
</property>
<property name="cors">
<value>${keycloak.enable-cors:false}</value>
</property>
<property name="corsMaxAge">
<value>${keycloak.cors-max-age:-1}</value>
</property>
<property name="corsAllowedHeaders">
<value>${keycloak.cors-allowed-headers:#{null}}</value>
</property>
<property name="corsAllowedMethods">
<value>${keycloak.cors-allowed-methods:#{null}}</value>
</property>
<property name="corsExposedHeaders">
<value>${keycloak.cors-exposed-headers:#{null}}</value>
</property>
<property name="exposeToken">
<value>${keycloak.expose-token:false}</value>
</property>
<property name="bearerOnly">
<value>${keycloak.bearer-only:false}</value>
</property>
<property name="autodetectBearerOnly">
<value>${keycloak.autodetect-bearer-only:false}</value>
</property>
<property name="enableBasicAuth">
<value>${keycloak.enable-basic-auth:false}</value>
</property>
<property name="publicClient">
<value>${keycloak.public-client:false}</value>
</property>
<property name="allowAnyHostname">
<value>${keycloak.allow-any-hostname:false}</value>
</property>
<property name="disableTrustManager">
<value>${keycloak.disable-trust-manager:false}</value>
</property>
<property name="truststore">
<value>${keycloak.truststore:#{null}}</value>
</property>
<property name="truststorePassword">
<value>${keycloak.truststore-password:#{null}}</value>
</property>
<property name="clientKeystore">
<value>${keycloak.client-keystore:#{null}}</value>
</property>
<property name="clientKeystorePassword">
<value>${keycloak.client-keystore-password:#{null}}</value>
</property>
<property name="clientKeyPassword">
<value>${keycloak.client-key-password:#{null}}</value>
</property>
<property name="connectionPoolSize">
<value>${keycloak.connection-pool-size:20}</value>
</property>
<property name="alwaysRefreshToken">
<value>${keycloak.always-refresh-token:false}</value>
</property>
<property name="registerNodeAtStartup">
<value>${keycloak.register-node-at-startup:false}</value>
</property>
<property name="registerNodePeriod">
<value>${keycloak.register-node-period:-1}</value>
</property>
<property name="tokenStore">
<value>${keycloak.token-store:#{null}}</value>
</property>
<property name="principalAttribute">
<value>${keycloak.principal-attribute:#{null}}</value>
</property>
<property name="turnOffChangeSessionIdOnLogin">
<value>${keycloak.turn-off-change-session-id-on-login:false}</value>
</property>
<property name="tokenMinimumTimeToLive">
<value>${keycloak.token-minimum-time-to-live:0}</value>
</property>
<property name="minTimeBetweenJwksRequests">
<value>${keycloak.min-time-between-jwks-requests:10}</value>
</property>
<property name="publicKeyCacheTtl">
<value>${keycloak.public-key-cache-ttl:86400}</value>
</property>
<property name="pkce">
<value>${keycloak.enable-pkce:false}</value>
</property>
<property name="ignoreOAuthQueryParameter">
<value>${keycloak.ignore-oauth-query-parameter:false}</value>
</property>
</bean>
<bean name="keycloakDeployment" class="org.alfresco.repo.security.authentication.token.AlfrescoKeycloakDeploymentFactoryBean">
<property name="adapterConfig">
<ref bean="keycloakAdpapterConfig" />
</property>
</bean>
<!-- Enable control over mapping between request and user ID -->
<bean id="remoteUserMapper" class="org.alfresco.repo.security.authentication.token.TokenRemoteUserMapper">
<property name="active">
<value>${token.authentication.enabled}</value>
</property>
<property name="validationFailureSilent">
<value>${token.authentication.validation.failure.silent}</value>
</property>
<property name="personService">
<ref bean="PersonService" />
</property>
<property name="keycloakDeployment">
<ref bean="keycloakDeployment" />
</property>
</bean>
<bean id="authenticationDao" class="org.alfresco.repo.security.authentication.RepositoryAuthenticationDao">
<property name="nodeService" ref="nodeService" />
<property name="authorityService" ref="authorityService" />
<property name="tenantService" ref="tenantService" />
<property name="namespaceService" ref="namespaceService" />
<property name="compositePasswordEncoder" ref="compositePasswordEncoder" />
<property name="policyComponent" ref="policyComponent" />
<property name="authenticationCache" ref="authenticationCache" />
<property name="singletonCache" ref="immutableSingletonCache"/>
<property name="transactionService">
<ref bean="transactionService" />
</property>
</bean>
</beans>

View File

@@ -0,0 +1,10 @@
token.authentication.enabled=true
token.authentication.validation.failure.silent=true
token.authentication.defaultAdministratorUserNames=admin
# Keycloak configuration
keycloak.auth-server-url=http://localhost:8180/auth
keycloak.realm=springboot
keycloak.ssl-required=none
keycloak.resource=activiti
keycloak.public-client=true

View File

@@ -25,10 +25,6 @@
*/
package org.alfresco.repo.security;
import junit.framework.JUnit4TestAdapter;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.alfresco.repo.domain.permissions.FixedAclUpdaterTest;
import org.alfresco.repo.ownable.impl.OwnableServiceTest;
import org.alfresco.repo.security.authentication.AlfrescoSSLSocketFactoryTest;
@@ -42,6 +38,7 @@ import org.alfresco.repo.security.authentication.UpgradePasswordHashTest;
import org.alfresco.repo.security.authentication.external.DefaultRemoteUserMapperTest;
import org.alfresco.repo.security.authentication.external.LocalAuthenticationServiceTest;
import org.alfresco.repo.security.authentication.subsystems.SubsystemChainingFtpAuthenticatorTest;
import org.alfresco.repo.security.authentication.token.TokenRemoteUserMapperTest;
import org.alfresco.repo.security.authority.AuthorityBridgeTableAsynchronouslyRefreshedCacheTest;
import org.alfresco.repo.security.authority.AuthorityServiceTest;
import org.alfresco.repo.security.authority.DuplicateAuthorityTest;
@@ -57,6 +54,10 @@ import org.alfresco.repo.security.permissions.impl.model.PermissionModelTest;
import org.alfresco.repo.security.person.HomeFolderProviderSynchronizerTest;
import org.alfresco.repo.security.person.PersonTest;
import junit.framework.JUnit4TestAdapter;
import junit.framework.Test;
import junit.framework.TestSuite;
/**
* @author Andy Hind
*
@@ -100,6 +101,7 @@ public class SecurityTestSuite extends TestSuite
suite.addTestSuite(FixedAclUpdaterTest.class);
suite.addTestSuite(DefaultRemoteUserMapperTest.class);
suite.addTestSuite(TokenRemoteUserMapperTest.class);
suite.addTestSuite(SubsystemChainingFtpAuthenticatorTest.class);
suite.addTest(new JUnit4TestAdapter(LocalAuthenticationServiceTest.class));

View File

@@ -0,0 +1,512 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.token;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.util.Map;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.repo.management.subsystems.AbstractChainedSubsystemTest;
import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory;
import org.alfresco.repo.management.subsystems.DefaultChildApplicationContextManager;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.util.ApplicationContextHelper;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.AccessToken;
import org.springframework.context.ApplicationContext;
/**
* Tests the token based authentication subsystem.
*
* @author Gavin Cornwell
*/
public class TokenRemoteUserMapperTest extends AbstractChainedSubsystemTest
{
private static final String REMOTE_USER_MAPPER_BEAN_NAME = "remoteUserMapper";
private static final String KEYCLOAK_DEPLOYMENT_BEAN_NAME = "keycloakDeployment";
private static final String TEST_USER_USERNAME = "testuser";
private static final String TEST_USER_EMAIL = "testuser@mail.com";
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer ";
private static final String BASIC_PREFIX = "Basic ";
private static final String CONFIG_SILENT_ERRORS = "token.authentication.validation.failure.silent";
private static final String PASSWORD_GRANT_RESPONSE = "{" +
"\"access_token\": \"%s\"," +
"\"expires_in\": 300," +
"\"refresh_expires_in\": 1800," +
"\"refresh_token\": \"%s\"," +
"\"token_type\": \"bearer\"," +
"\"not-before-policy\": 0," +
"\"session_state\": \"71c2c5ba-9c98-49fc-882f-dedcf80ee1b5\"}";
ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
DefaultChildApplicationContextManager childApplicationContextManager;
ChildApplicationContextFactory childApplicationContextFactory;
private KeyPair keyPair;
private AlfrescoKeycloakAdapterConfig keycloakAdapterConfig;
/* (non-Javadoc)
* @see junit.framework.TestCase#setUp()
*/
@Override
protected void setUp() throws Exception
{
// switch authentication to use token auth
childApplicationContextManager = (DefaultChildApplicationContextManager) ctx.getBean("Authentication");
childApplicationContextManager.stop();
childApplicationContextManager.setProperty("chain", "token1:token");
childApplicationContextFactory = getChildApplicationContextFactory(childApplicationContextManager, "token1");
// generate keys for test
this.keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
// hardcode the realm public key in the Keycloak deployment bean to stop it fetching keys
applyHardcodedPublicKey(this.keyPair.getPublic());
// extract config
this.keycloakAdapterConfig = (AlfrescoKeycloakAdapterConfig)childApplicationContextFactory.
getApplicationContext().getBean("keycloakAdpapterConfig");
}
/* (non-Javadoc)
* @see junit.framework.TestCase#tearDown()
*/
@Override
protected void tearDown() throws Exception
{
childApplicationContextManager.destroy();
childApplicationContextManager = null;
childApplicationContextFactory = null;
}
public void testKeycloakConfig() throws Exception
{
// check string overrides
assertEquals("keycloak.auth-server-url", "http://192.168.0.1:8180/auth",
this.keycloakAdapterConfig.getAuthServerUrl());
assertEquals("keycloak.realm", "test",
this.keycloakAdapterConfig.getRealm());
assertEquals("keycloak.realm-public-key",
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvWLQxipXNe6cLnVPGy7l" +
"BgyR51bDiK7Jso8Rmh2TB+bmO4fNaMY1ETsxECSM0f6NTV0QHks9+gBe+pB6JNeM" +
"uPmaE/M/MsE9KUif9L2ChFq3zor6s2foFv2DTiTkij+1aQF9fuIjDNH4FC6L252W" +
"ydZzh+f73Xuy5evdPj+wrPYqWyP7sKd+4Q9EIILWAuTDvKEjwyZmIyfM/nUn6ltD" +
"P6W8xMP0PoEJNAAp79anz2jk2HP2PvC2qdjVsphdTk3JG5qQMB0WJUh4Kjgabd4j" +
"QJ77U8gTRswKgNHRRPWhruiIcmmkP+zI0ozNW6rxH3PF4L7M9rXmfcmUcBcKf+Yx" +
"jwIDAQAB",
this.keycloakAdapterConfig.getRealmKey());
assertEquals("keycloak.ssl-required", "external",
this.keycloakAdapterConfig.getSslRequired());
assertEquals("keycloak.resource", "test",
this.keycloakAdapterConfig.getResource());
assertEquals("keycloak.cors-allowed-headers", "Authorization",
this.keycloakAdapterConfig.getCorsAllowedHeaders());
assertEquals("keycloak.cors-allowed-methods", "POST, PUT, DELETE, GET",
this.keycloakAdapterConfig.getCorsAllowedMethods());
assertEquals("keycloak.cors-exposed-headers", "WWW-Authenticate, My-custom-exposed-Header",
this.keycloakAdapterConfig.getCorsExposedHeaders());
assertEquals("keycloak.truststore",
"classpath:/alfresco/subsystems/tokenAuthentication/keystore.jks",
this.keycloakAdapterConfig.getTruststore());
assertEquals("keycloak.truststore-password", "password",
this.keycloakAdapterConfig.getTruststorePassword());
assertEquals("keycloak.client-keystore",
"classpath:/alfresco/subsystems/tokenAuthentication/keystore.jks",
this.keycloakAdapterConfig.getClientKeystore());
assertEquals("keycloak.client-keystore-password", "password",
this.keycloakAdapterConfig.getClientKeystorePassword());
assertEquals("keycloak.client-key-password", "password",
this.keycloakAdapterConfig.getClientKeyPassword());
assertEquals("keycloak.token-store", "SESSION",
this.keycloakAdapterConfig.getTokenStore());
assertEquals("keycloak.principal-attribute", "preferred_username",
this.keycloakAdapterConfig.getPrincipalAttribute());
// check number overrides
assertEquals("keycloak.confidential-port", 100,
this.keycloakAdapterConfig.getConfidentialPort());
assertEquals("keycloak.cors-max-age", 1000,
this.keycloakAdapterConfig.getCorsMaxAge());
assertEquals("keycloak.connection-pool-size", 5,
this.keycloakAdapterConfig.getConnectionPoolSize());
assertEquals("keycloak.register-node-period", 50,
this.keycloakAdapterConfig.getRegisterNodePeriod());
assertEquals("keycloak.token-minimum-time-to-live", 10,
this.keycloakAdapterConfig.getTokenMinimumTimeToLive());
assertEquals("keycloak.min-time-between-jwks-requests", 60,
this.keycloakAdapterConfig.getMinTimeBetweenJwksRequests());
assertEquals("keycloak.public-key-cache-ttl", 3600,
this.keycloakAdapterConfig.getPublicKeyCacheTtl());
// check boolean overrides
assertFalse("keycloak.public-client",
this.keycloakAdapterConfig.isPublicClient());
assertTrue("keycloak.use-resource-role-mappings",
this.keycloakAdapterConfig.isUseResourceRoleMappings());
assertTrue("keycloak.enable-cors",
this.keycloakAdapterConfig.isCors());
assertTrue("keycloak.expose-token",
this.keycloakAdapterConfig.isExposeToken());
assertTrue("keycloak.bearer-only",
this.keycloakAdapterConfig.isBearerOnly());
assertTrue("keycloak.autodetect-bearer-only",
this.keycloakAdapterConfig.isAutodetectBearerOnly());
assertTrue("keycloak.enable-basic-auth",
this.keycloakAdapterConfig.isEnableBasicAuth());
assertTrue("keycloak.allow-any-hostname",
this.keycloakAdapterConfig.isAllowAnyHostname());
assertTrue("keycloak.disable-trust-manager",
this.keycloakAdapterConfig.isDisableTrustManager());
assertTrue("keycloak.always-refresh-token",
this.keycloakAdapterConfig.isAlwaysRefreshToken());
assertTrue("keycloak.register-node-at-startup",
this.keycloakAdapterConfig.isRegisterNodeAtStartup());
assertTrue("keycloak.enable-pkce",
this.keycloakAdapterConfig.isPkce());
assertTrue("keycloak.ignore-oauth-query-parameter",
this.keycloakAdapterConfig.isIgnoreOAuthQueryParameter());
assertTrue("keycloak.turn-off-change-session-id-on-login",
this.keycloakAdapterConfig.getTurnOffChangeSessionIdOnLogin());
// check credentials overrides
Map<String, Object> credentials = this.keycloakAdapterConfig.getCredentials();
assertNotNull("Expected a credentials map", credentials);
assertFalse("Expected to retrieve a populated credentials map", credentials.isEmpty());
assertEquals("keycloak.credentials.secret", "11111", credentials.get("secret"));
assertEquals("keycloak.credentials.provider", "secret", credentials.get("provider"));
}
public void testValidToken() throws Exception
{
// create token
String jwt = generateToken(false);
// create mock request object
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
// validate correct user was found
assertEquals(TEST_USER_USERNAME, ((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
}
public void testWrongPublicKey() throws Exception
{
// generate and apply an incorrect public key
childApplicationContextFactory.stop();
applyHardcodedPublicKey(KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic());
// create token
String jwt = generateToken(false);
// create mock request object
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
// ensure null is returned if the public key is wrong
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
}
public void testWrongPublicKeyWithError() throws Exception
{
// generate and apply an incorrect public key
childApplicationContextFactory.stop();
childApplicationContextFactory.setProperty(CONFIG_SILENT_ERRORS, "false");
applyHardcodedPublicKey(KeyPairGenerator.getInstance("RSA").generateKeyPair().getPublic());
// create token
String jwt = generateToken(false);
// create mock request object
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
// ensure an exception is thrown with correct description
try
{
((RemoteUserMapper)childApplicationContextFactory.getApplicationContext().getBean(
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest);
fail("Expected an AuthenticationException to be thrown");
}
catch (AuthenticationException ae)
{
assertTrue("Exception message contains 'Invalid token signature'",
ae.getMessage().indexOf("Invalid token signature") != -1);
}
}
public void testInvalidJwt() throws Exception
{
// create mock request object
HttpServletRequest mockRequest = createMockTokenRequest("thisisnotaJWT");
// ensure null is returned if the JWT is invalid
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
}
public void testMissingToken() throws Exception
{
// create mock request object
HttpServletRequest mockRequest = createMockTokenRequest("");
// ensure null is returned if the token is missing
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
}
public void testExpiredToken() throws Exception
{
// create token
String jwt = generateToken(true);
// create mock request object
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
// ensure null is returned if the token has expired
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
}
public void testExpiredTokenWithError() throws Exception
{
// turn on validation failure reporting
childApplicationContextFactory.stop();
childApplicationContextFactory.setProperty(CONFIG_SILENT_ERRORS, "false");
applyHardcodedPublicKey(this.keyPair.getPublic());
// create token
String jwt = generateToken(true);
// create mock request object
HttpServletRequest mockRequest = createMockTokenRequest(jwt);
// ensure an exception is thrown with correct description
try
{
((RemoteUserMapper)childApplicationContextFactory.getApplicationContext().getBean(
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest);
fail("Expected an AuthenticationException to be thrown");
}
catch (AuthenticationException ae)
{
assertTrue("Exception message contains 'Token is not active'",
ae.getMessage().indexOf("Token is not active") != -1);
}
}
public void testMissingHeader() throws Exception
{
// create mock request object with no Authorization header
HttpServletRequest mockRequest = createMockTokenRequest(null);
// ensure null is returned if the header was missing
assertNull(((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
}
public void testBasicAuthFallback() throws Exception
{
// create mock objects
HttpServletRequest mockRequest = createMockBasicRequest();
HttpClient mockHttpClient = createMockHttpClient();
// override the http client on the keycloak deployment
KeycloakDeployment deployment = (KeycloakDeployment)childApplicationContextFactory.getApplicationContext().
getBean(KEYCLOAK_DEPLOYMENT_BEAN_NAME);
deployment.setClient(mockHttpClient);
// validate correct user was found
assertEquals(TEST_USER_USERNAME, ((RemoteUserMapper) childApplicationContextFactory.getApplicationContext().getBean(
REMOTE_USER_MAPPER_BEAN_NAME)).getRemoteUser(mockRequest));
}
/**
* Utility method for creating a mocked Servlet request with a token.
*
* @param token The token to add to the Authorization header
* @return The mocked request object
*/
private HttpServletRequest createMockTokenRequest(String token)
{
// Mock a request with the token in the Authorization header (if supplied)
HttpServletRequest mockRequest = mock(HttpServletRequest.class);
Vector<String> authHeaderValues = new Vector<>(1);
if (token != null)
{
authHeaderValues.add(BEARER_PREFIX + token);
}
when(mockRequest.getHeaders(AUTHORIZATION_HEADER)).thenReturn(authHeaderValues.elements());
return mockRequest;
}
/**
* Utility method for creating a mocked Servlet request with basic auth.
*
* @return The mocked request object
*/
@SuppressWarnings("unchecked")
private HttpServletRequest createMockBasicRequest()
{
// Mock a request with the token in the Authorization header (if supplied)
HttpServletRequest mockRequest = mock(HttpServletRequest.class);
Vector<String> authHeaderValues = new Vector<>(1);
String userPwd = TEST_USER_USERNAME + ":" + TEST_USER_USERNAME;
authHeaderValues.add(BASIC_PREFIX + Base64.encodeBytes(userPwd.getBytes()));
// NOTE: as getHeaders gets called twice provide two separate Enumeration objects so that
// an empty result is not returned for the second invocation.
when(mockRequest.getHeaders(AUTHORIZATION_HEADER)).thenReturn(authHeaderValues.elements(),
authHeaderValues.elements());
return mockRequest;
}
private HttpClient createMockHttpClient() throws Exception
{
// mock HttpClient object and set on keycloak deployment to avoid basic auth
// attempting to get a token using HTTP POST
HttpClient mockHttpClient = mock(HttpClient.class);
HttpResponse mockHttpResponse = mock(HttpResponse.class);
StatusLine mockStatusLine = mock(StatusLine.class);
HttpEntity mockHttpEntity = mock(HttpEntity.class);
// for the purpose of this test use the same token for access and refresh
String token = generateToken(false);
String jsonResponse = String.format(PASSWORD_GRANT_RESPONSE, token, token);
ByteArrayInputStream jsonResponseStream = new ByteArrayInputStream(jsonResponse.getBytes());
when(mockHttpClient.execute(any())).thenReturn(mockHttpResponse);
when(mockHttpResponse.getStatusLine()).thenReturn(mockStatusLine);
when(mockHttpResponse.getEntity()).thenReturn(mockHttpEntity);
when(mockStatusLine.getStatusCode()).thenReturn(200);
when(mockHttpEntity.getContent()).thenReturn(jsonResponseStream);
return mockHttpClient;
}
/**
* Utility method to create tokens for testing.
*
* @param expired Determines whether to create an expired JWT
* @return The string representation of the JWT
*/
private String generateToken(boolean expired) throws Exception
{
String issuerUrl = this.keycloakAdapterConfig.getAuthServerUrl() + "/realms/" + this.keycloakAdapterConfig.getRealm();
AccessToken token = new AccessToken();
token.type("Bearer");
token.id("1234");
token.subject("abc123");
token.issuer(issuerUrl);
token.setPreferredUsername(TEST_USER_USERNAME);
token.setEmail(TEST_USER_EMAIL);
token.setGivenName("Joe");
token.setFamilyName("Bloggs");
if (expired)
{
token.expiration(Time.currentTime() - 60);
}
String jwt = new JWSBuilder()
.jsonContent(token)
.rsa256(keyPair.getPrivate());
return jwt;
}
/**
* Finds the keycloak deployment bean and applies a hardcoded public key locator using the
* provided public key.
*/
private void applyHardcodedPublicKey(PublicKey publicKey)
{
KeycloakDeployment deployment = (KeycloakDeployment)childApplicationContextFactory.getApplicationContext().
getBean(KEYCLOAK_DEPLOYMENT_BEAN_NAME);
HardcodedPublicKeyLocator publicKeyLocator = new HardcodedPublicKeyLocator(publicKey);
deployment.setPublicKeyLocator(publicKeyLocator);
}
}

View File

@@ -0,0 +1,45 @@
# Test token authentication overrides
keycloak.auth-server-url=http://192.168.0.1:8180/auth
keycloak.realm=test
keycloak.realm-public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvWLQxipXNe6cLnVPGy7l\
BgyR51bDiK7Jso8Rmh2TB+bmO4fNaMY1ETsxECSM0f6NTV0QHks9+gBe+pB6JNeM\
uPmaE/M/MsE9KUif9L2ChFq3zor6s2foFv2DTiTkij+1aQF9fuIjDNH4FC6L252W\
ydZzh+f73Xuy5evdPj+wrPYqWyP7sKd+4Q9EIILWAuTDvKEjwyZmIyfM/nUn6ltD\
P6W8xMP0PoEJNAAp79anz2jk2HP2PvC2qdjVsphdTk3JG5qQMB0WJUh4Kjgabd4j\
QJ77U8gTRswKgNHRRPWhruiIcmmkP+zI0ozNW6rxH3PF4L7M9rXmfcmUcBcKf+Yx\
jwIDAQAB
keycloak.ssl-required=external
keycloak.resource=test
keycloak.public-client=false
keycloak.confidential-port=100
keycloak.use-resource-role-mappings=true
keycloak.enable-cors=true
keycloak.cors-max-age=1000
keycloak.cors-allowed-headers=Authorization
keycloak.cors-allowed-methods=POST, PUT, DELETE, GET
keycloak.cors-exposed-headers=WWW-Authenticate, My-custom-exposed-Header
keycloak.expose-token=true
keycloak.bearer-only=true
keycloak.autodetect-bearer-only=true
keycloak.enable-basic-auth=true
keycloak.allow-any-hostname=true
keycloak.disable-trust-manager=true
keycloak.truststore=classpath:/alfresco/subsystems/tokenAuthentication/keystore.jks
keycloak.truststore-password=password
keycloak.client-keystore=classpath:/alfresco/subsystems/tokenAuthentication/keystore.jks
keycloak.client-keystore-password=password
keycloak.client-key-password=password
keycloak.connection-pool-size=5
keycloak.always-refresh-token=true
keycloak.register-node-at-startup=true
keycloak.register-node-period=50
keycloak.token-store=SESSION
keycloak.principal-attribute=preferred_username
keycloak.turn-off-change-session-id-on-login=true
keycloak.token-minimum-time-to-live=10
keycloak.min-time-between-jwks-requests=60
keycloak.public-key-cache-ttl=3600
keycloak.enable-pkce=true
keycloak.ignore-oauth-query-parameter=true
keycloak.credentials.secret=11111
keycloak.credentials.provider=secret

View File

@@ -254,3 +254,7 @@ log4j.logger.org.alfresco.repo.usage.RepoUsageMonitor=info
log4j.logger.org.alfresco.repo.site.SiteServiceImpl=DEBUG
log4j.logger.org.alfresco.repo.action.ActionServiceImpl=DEBUG
log4j.logger.org.alfresco.repo.security.person.PersonServiceImpl=DEBUG
# token authentication
log4j.logger.org.alfresco.repo.security.authentication.token=debug
log4j.logger.org.keycloak=debug