There is a new authentication architecture available in the latest releases of the Alfresco products. More specifically we are talking about the ACS 6.0, APS 1.9, and ADF 2.4 product releases. It is still Early Access (EA) functionality not yet supported, so don’t use this in production. The new authentication architecture centralizes the authentication function and supports Single Sign On (SSO). Users authenticate via a new service called the Alfresco Identity Service.
This means that the applications that we are dealing with, such as ACS, APS, and ADF clients don’t have to deal with login forms and authentication. Once a user is logged into the Alfresco Identity Service they don’t have to login again to access ACS, APS, or any ADF application.
This also applies to logout, which means that once a user is logged out of Alfresco Identity Service they are also automatically logged out of all other applications.
Alfresco Identity Service is implemented on top of JBoss Keycloak, which is both an Identity Provider (IdP) and a token issuer for OAuth 2 tokens. Keycloak deals with authentication, safety password storage, SSO, two factor authentication etc. Keycloak supports protocols such as OpenID Connect and SAML. Keycloak can store the user data in a variety of places, such as LDAP, Active Directory, and RDBMS.
At the moment Alfresco Identity Service is the same thing as JBoss Keycloak.
The source code for this article can be found here. Clone this project and it will be easy to follow along with this article.
This article assumes that you have the following software installed:
At the moment the Alfresco Identity Service is just an instance of JBoss Keycloak. And these days you can spin up pretty much everything with Docker. So we will build a Docker Compose file that starts up ACS 6.0, APS 1.9, Keycloak 3.4.3, and a shared PostgreSQL database.
If you wanted to have a quick look at keycloak you can kick it off like this:
$ docker run -p “8080:8080” -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=secret jboss/keycloak
This uses a built in H2 database, which we actually don’t want to use as we lose all data as soon as the container is removed...
The Docker Compose file that we will be using looks as follows:
# Using version 2 as 3 does not support resource constraint options (cpu_*, mem_* limits) for non swarm mode in Compose
version: "2"
services:
content:
image: alfresco/alfresco-content-repository:6.0.0
mem_limit: 1500m
environment:
CATALINA_OPTS: "
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
"
JAVA_OPTS: "
-Ddb.driver=org.postgresql.Driver
-Ddb.username=alfresco
-Ddb.password=alfresco
-Ddb.url=jdbc:postgresql://postgres:5432/alfresco
-Dsolr.host=solr6
-Dsolr.port=8983
-Dsolr.secureComms=none
-Dsolr.base.url=/solr
-Dindex.subsystem.name=solr6
-Ddeployment.method=DOCKER_COMPOSE
-Xms1g -Xmx1g
"
volumes:
- ./data/acs:/usr/local/tomcat/alf_data/
- ./acs/alfresco-global.properties:/usr/local/tomcat/shared/classes/alfresco-global.properties
- ./acs/log4j.properties:/usr/local/tomcat/webapps/alfresco/WEB-INF/classes/log4j.properties
ports:
- 8082:8080 # Browser port
- 5005:5005 # Remote Debug
links:
- solr6
- postgres:postgres
depends_on:
- solr6
- postgres
share:
image: alfresco/alfresco-share:6.0
mem_limit: 1g
environment:
- REPO_HOST=content
- REPO_PORT=8080
- "CATALINA_OPTS= -Xms500m -Xmx500m"
ports:
- 8081:8080
links:
- content:content
depends_on:
- content
solr6:
image: alfresco/alfresco-search-services:1.1.1
mem_limit: 2500m
environment:
#Solr needs to know how to register itself with Alfresco
- SOLR_ALFRESCO_HOST=content
- SOLR_ALFRESCO_PORT=8080
#Alfresco needs to know how to call solr
- SOLR_SOLR_HOST=solr6
- SOLR_SOLR_PORT=8983
#Create the default alfresco and archive cores
- SOLR_CREATE_ALFRESCO_DEFAULTS=alfresco,archive
- "SOLR_JAVA_MEM=-Xms2g -Xmx2g"
volumes:
- ./data/solr/contentstore:/opt/alfresco-search-services/contentstore/
- ./data/solr/data:/opt/alfresco-search-services/data/
ports:
- 8083:8983 #Browser port
process:
image: alfresco/process-services:1.9.0.1
environment:
ACTIVITI_DATASOURCE_USERNAME: alfresco
ACTIVITI_DATASOURCE_PASSWORD: alfresco
ACTIVITI_DATASOURCE_DRIVER: org.postgresql.Driver
ACTIVITI_HIBERNATE_DIALECT: org.hibernate.dialect.PostgreSQLDialect
ACTIVITI_DATASOURCE_URL: 'jdbc:postgresql://postgres:5432/activiti?characterEncoding=UTF-8'
ACTIVITI_CSRF_DISABLED: 'true'
ACTIVITI_CORS_ENABLED: 'true'
ACTIVITI_ES_SERVER_TYPE: client
ACTIVITI_ES_DISCOVERY_HOSTS: elasticsearch:9300
ACTIVITI_ES_CLUSTER_NAME: elasticsearch
volumes:
- ./aps/enterprise-license:/root/.activiti/enterprise-license/:ro
- ./aps/transform.lic:/usr/share/tomcat/lib/transform.lic
- ./aps/activiti-app.properties:/usr/local/tomcat/lib/activiti-app.properties
- ./aps/activiti-ldap.properties:/usr/local/tomcat/lib/activiti-ldap.properties
- ./aps/activiti-identity-service.properties:/usr/local/tomcat/lib/activiti-identity-service.properties
#- ./aps/log4j.properties:/usr/local/tomcat/webapps/activiti-app/WEB-INF/classes/log4j.properties
- ./data/aps:/usr/local/data/
ports:
- 9080:8080
links:
- elasticsearch:elasticsearch
- postgres:postgres
depends_on:
- elasticsearch
- postgres
elasticsearch:
image: elasticsearch:1.7.3
postgres:
image: postgres:10.1
mem_limit: 1500m
environment:
- POSTGRES_PASSWORD=alfresco
- POSTGRES_USER=alfresco
- POSTGRES_DB=alfresco
command: postgres -c max_connections=300 -c log_min_messages=LOG
ports:
- 5432:5432
volumes:
- ./data/postgres:/var/lib/postgresql/data
- ./docker-postgresql-multiple-databases:/docker-entrypoint-initdb.d
identity-service:
image: jboss/keycloak:3.4.3.Final
environment:
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
POSTGRES_PORT_5432_TCP_ADDR: postgres
POSTGRES_PORT_5432_TCP_PORT: 5432
POSTGRES_DATABASE: keycloak
POSTGRES_USER: alfresco
POSTGRES_PASSWORD: alfresco
ports:
- 8080:8080
depends_on:
- postgres
# UNCOMMENT TO ENABLE LDAP SERVER
# ldap:
# image: greggigon/apacheds
# environment:
# - BOOTSTRAP_FILE=/bootstrap/demo.ldif
# restart: always
# ports:
# - 10389:10389
# volumes:
# - ./data/ldap/data:/data
# - ./ldap/bootstrap:/bootstrap
The interesting part is at the end of the Docker Compose file where we define a new service called the identity-service (i.e. keycloak). It is configured with the following environment variables:
Variable | Value | Description |
KEYCLOAK_USER | admin | This is the initial Administrator username that will be created first time keycloak is started. You can use this user to login to keycloak. It will automatically be part of the master realm. |
KEYCLOAK_PASSWORD | admin | Password for the administrator user. |
POSTGRES_PORT_5432_TCP_ADDR | postgres | The host where the PostgreSQL database server is running. In this case it points to the internal docker network hostname postgres (the same as the service name in a Docker Compose file) |
POSTGRES_PORT_5432_TCP_PORT | 5432 | The port that the PostgreSQL server is listening on. Note. this is the internal port, if you map an external port to something else, then you still need to use 5432. |
POSTGRES_DATABASE | keycloak | The name of the keycloak database in the PostgreSQL server. This database is created the first time PostgreSQL starts via an init script called create-multiple-postgresql-databases.sh (see source code) |
POSTGRES_USER | alfresco | The user that should be used to login to PostgreSQL and access the keycloak database. |
POSTGRES_PASSWORD | alfresco | PostgreSQL user password. |
ports | 8080:8080 | This is the external port mapping. The keycloak server will start up on port 8080 in the Docker Compose network and be exposed on port 8080 externally. At the moment we cannot configure a different external port as then we cannot configure Keycloak Auth Server URL properly from for example APS. More on this when we configure APS. Note that Share has to be exposed on another port than 8080, but should be OK in a developer environment. |
I have seen other environment variables being suggested when configuring keycloak to use an external PostgreSQL database. But these are the once that worked for me with keycloak version 3.4.3.
Now, there are a lot of volumes configured for the services above in the Docker Compose file. They are supported by the following directories and files in the docker-compose directory (see source code):
.
├── acs (Directory contains custom configuration for ACS)
│ ├── alfresco-global.properties
│ └── log4j.properties
├── aps (Directory contains custom configuration for APS)
│ ├── activiti-app.properties
│ ├── activiti-identity-service.properties
│ ├── activiti-ldap.properties
│ ├── enterprise-license
│ │ └── (Put your enterprise APS license here)
│ ├── log4j.properties
│ └── transform.lic
├── data (Directory contains all data produced when running DBP)
│ ├── acs
│ ├── aps
│ ├── ldap
│ ├── postgres
│ └── solr
├── docker-compose.yml
├── docker-postgresql-multiple-databases (creates Activiti and Keycloak DBs)
│ └── create-multiple-postgresql-databases.sh
└── ldap
└── bootstrap
└── demo.ldif
Before you move on and start the Alfresco Digital Business Platform (DBP) solution via the above Docker Compose file you will have to put an APS Enterprise license into the /docker-compose/aps/enterprise-license directory. You can download a trial license from here, which will get you access to a trial license.
Now it is very easy to start the whole DBP solution including the Identity Service, just step into the docker-compose directory that is part of the source code project that you cloned in the beginning and execute (first time you run this it will take some time as this will download all the Docker images from Docker Hub):
docker-compose mbergljung$ docker-compose up
Creating network "docker-compose_default" with the default driver
Creating docker-compose_solr6_1 ... done
Creating docker-compose_postgres_1 ... done
Creating docker-compose_elasticsearch_1 ... done
Creating docker-compose_identity-service_1 ... done
Creating docker-compose_content_1 ... done
Creating docker-compose_process_1 ... done
Creating docker-compose_share_1 ... done
....
content_1 | 17-Jul-2018 13:51:05.617 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
content_1 | 17-Jul-2018 13:51:05.658 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
content_1 | 17-Jul-2018 13:51:05.668 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 257946 ms
Wait until the ACS server has all started, then the system should most likely be ready. Then verify that it’s all working by accessing all the apps. Start with APS via http://localhost:9080/activiti-app and admin@app.activiti.com/admin credentials:
Then try ACS at http://localhost:8081/share with admin/admin:
Now access Alfresco Identity Service (i.e. Keycloak) at http://localhost:8080/auth/admin/master/console logging in using admin/admin:
You should see the following screen with the Master realm info after successful login:
We are now ready to start configuring keycloak for use with ACS, APS, and ADF.
Alfresco Identity Service is built on Keycloak and uses the OAuth 2.0 standard, which means that we have the following roles involved in the authentication flow:
The access token represent the credentials to access protected resources. So your application (i.e. ADF App) does not need to store the resource owner credentials, but instead just the access token. The whole idea with OAuth2 is to create an authentication and authorization system that is not reliant on a username/password combination being broadcast and to instead rely upon a token exchange mechanism built upon digital signature technology.
The following picture illustrates:
By looking at the flows in the above picture we can see that the client (i.e. the ADF App) needs to know about the location of the authorization server and what redirection URL to use. Each resource server (i.e. ACS and APS) needs to also know the location of the authorization server so it can verify the access token being used to access a protected resource. The authorization server needs to be hooked up to a database or a directory with user credentials data.
It is also common to create a separate security realm for the solution that you are implementing authentication for. There is only the master realm by default with the admin user, and we don’t want to use this realm for our DBP solution’s users, groups, roles etc. The master realm is a place for super admins to create and manage the realms in your system.
If you have followed along you should now be able to access the Keycloak Admin Console with your admin account by visiting the following URL: http://localhost:8080/auth/admin. By default, you’ll be inside the Master realm.
OK, but what is a realm, really? It’s just a domain in which you apply specific security policies. The Master realm is the parent of any realm you create. For my purpose, I want to create a new realm, which will be a new security domain specifically for the Alfresco Digital Business Platform (DBP) solution.
On the top left of the Admin Console, click the little arrow next to Master. It’ll appear a drop-down menu where Keycloak shows all your realms and let you select any of them:
Click Add realm and then fill in the name of the new realm, in this case we call it alfresco-dbp (note. avoid space in the realm name as it can cause problems):
Now click the Create button. For the moment you can keep all the default configurations for the realm. We can get a quick summary of the new realm by hitting the following URL: http://localhost:8080/auth/realms/alfresco-dbp :
{
}
Here we can see that it will use the OpenID Connect protocol.
The first step in securing a web application with Keycloak is creating a client. But what is a client anyway? As discussed before, a Client is an entity that can request authentication on behave of the end-user (e.g. ADF application). A client basically represents a web application that wants to use Keycloak to authenticate and authorize users.
To create a new client, go to Clients:
Then click the Create button to the right:
Set the Client ID to alfresco-client. We will use one Client for ADF apps, ACS, and APS. We want to use the OpenID Connect (OIDC) protocol, which is selected by default. Click Save to create this new client.
Once you create the client, a new page opens with further fields to configure it:
Make sure you set the following options in this way:
Click the Save button when finished with the Client configuration.
To validate our environment (and for APS Admin capabilities), it’s recommended to create a local user in Keycloak. The user has to exist in both ACS and APS, so it would make sense to create an Administrator user. If the user does not exist, then we will not be able to authenticate with it in APS, which requires the user to exist.
Click on the Users menu item to the left:
Then click the Add user button:
Fill in the following fields:
Click Save button to add the user to the Alfresco DBP realm.
Go to the Credentials tab and insert a new password (I am using admin as password):
For this blog post I don’t want to require the user to change the password on their first login, so I’ll toggle the Temporary option to OFF. After that, click Reset password.
In a production environment, it would be a good idea enabling this option when you define a password on behalf of some user.
We are now ready to start configuration of the DBP components (i.e. ACS, APS, ADF).
We will start with the APS application as it’s UI (i.e. /activiti-app) can participate in OpenID Connect authentication. It will act as the OAuth2 Client.
Open up the docker-compose/aps/activiti-identity-service.properties file in the source code project and uncomment the following properties so the Alfresco Identity Service (i.e. keycloak) will be enabled for APS:
keycloak.enabled=true
keycloak.realm=alfresco-dbp
keycloak.auth-server-url=http://mbp512-mbergljung-0917.local:8080/auth
keycloak.ssl-required=none
keycloak.resource=alfresco-client
keycloak.principal-attribute=email
keycloak.public-client=true
#keycloak.credentials.secret=
keycloak.always-refresh-token=true
keycloak.autodetect-bearer-only=true
keycloak.token-store=cookie
keycloak.enable-basic-auth=true
The only property that you should have to change is the keycloak.auth-server-url property, see below for explanation. The properties have the following meaning (most of them have not been changed from out-of-the-box configuration):
Property Name | Value | Description |
keycloak.enabled | true | True = turns on SSO via Keycloak. False = APS will not be involved in OAuth2 authentication. |
keycloak.realm | alfresco-dbp | Which keycloak security realm should be used (i.e. where are the users)? |
keycloak.auth-server-url | Where is the Keycloak server running? Used to construct two URLs: 1) for the browser redirect to the keycloak server 2) for the APS backend communication with keycloak when verifying access tokens. Which means that this URL has to work both inside and outside the APS container. It does that by using the Mac hostname (in lowercase) and the external port 8080 (the internal port is the same so works both externally and internally). | |
keycloak.ssl-required | none | If set to all, then it ensures that all communication to and from the Keycloak server is over HTTPS. |
keycloak.resource | alfresco-client | The OAuth2 client ID. Each application has a client-id that is used to identify the application. We use the same client ID for all Alfresco clients. |
keycloak.principal-attribute | The primary user id in APS is email address. So we use the email field in keycloak user account to authenticate. This is the OpenID Connection ID Token attribute to populate the UserPrincipal name with. If token attribute is null, then defaults to sub. Possible values are sub, preferred_username, email, name, nickname, given_name, family_name. | |
keycloak.public-client | true | If set to true, then the APS Activiti App client will not send credentials for the client to Keycloak. |
keycloak.credentials.secret | The secret key for this client if the access type is not set to public. | |
keycloak.always-refresh-token | true | If true, then APS will refresh token in every request. |
keycloak.autodetect-bearer-only | true | This should be set to true if APS serves both a web application (e.g. Activiti App) and web services (e.g. ReST). It allows you to redirect unauthenticated users of the web application to the Keycloak login page, but send an HTTP 401 status code to unauthenticated ReST clients instead as they would not understand a redirect to the login page. Keycloak auto-detects ReST clients based on typical headers like X-Requested-With, SOAPAction or Accept. The default value is false. |
keycloak.token-store | cookie | Possible values are session and cookie. Default is session, which means that APS stores account info in HTTP Session. Alternative cookie means storage of info in cookie. |
keycloak.enable-basic-auth | true | This tells the adapter to also support basic authentication. |
In this case APS is both an OAuth2 Client and an OAuth2 Resource Server.
Now to test it restart just the APS container as follows:
docker-compose mbergljung$ docker-compose stop process
Stopping docker-compose_process_1 ... done
docker-compose mbergljung$ docker-compose rm process
Going to remove docker-compose_process_1
Are you sure? [yN] y
Removing docker-compose_process_1 ... done
docker-compose mbergljung$ docker-compose up -d --no-deps process
Creating docker-compose_process_1 ... done
Note. you need to fully remove the container for these changes to take effect.
When we now go to http://localhost:9080/activiti-app we will be redirected to the Keycloak server for authentication with a URL looking something like:
The Keycloak login screen should now be visible as follows:
In this case we use the email admin@app.activiti.com as username and password admin. Just like we used when creating the user in the alfresco-dbp realm. Clicking the Log in button should successfully log us in and we should see the APS dashboard (and the APS login screen should never be visible):
We can logout from APS by clicking Sign out in the upper right corner menu. That should take us back to the Keycloak login screen.
Now, let’s see if we can configure ACS to use the Alfresco Identity Service (i.e. keycloak). Open up the docker-compose/acs/alfresco-global.properties file in the source code project and uncomment the following properties so the Alfresco Identity Service (i.e. keycloak) will be enabled for ACS:
authentication.chain=identity-service1:identity-service,alfrescoNtlm1:alfrescoNtlm
identity-service.authentication.enabled=true
identity-service.enable-basic-auth=true
identity-service.authentication.defaultAdministratorUserNames=admin
identity-service.authentication.validation.failure.silent=false
identity-service.auth-server-url=http://mbp512-mbergljung-0917.local:8080/auth
identity-service.realm=alfresco-dbp
identity-service.resource=alfresco-client
identity-service.public-client=true
identity-service.ssl-required=none
The only property that you should have to change is the identity-service.auth-server-url property, see above in the APS config section for explanation.
Now restart the ACS container as follows:
docker-compose mbergljung$ docker-compose stop content
Stopping docker-compose_content_1 ... done
docker-compose mbergljung$ docker-compose rm content
Going to remove docker-compose_content_1
Are you sure? [yN] y
Removing docker-compose_content_1 ... done
docker-compose mbergljung$ docker-compose up -d --no-deps content
Creating docker-compose_content_1 ... done
There is no ACS web application that we can use to verify that ACS keycloak configuration is correct. So we have to call an ACS ReST API instead to test it.
Request an access token first via curl (or with Postman):
$ curl -d 'client_id=alfresco-client' -d 'username=admin' -d 'password=admin' -d 'grant_type=password' 'http://mbp512-mbergljung-0917.local:8080/auth/realms/alfresco-dbp/protocol/openid-connect/token' | python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2682 100 2607 100 75 19029 547 --:--:-- --:--:-- --:--:-- 19576
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxdUE1c3FDd0pqV21iNk14SkxrcmhjR0k0cHQtVDRENk1ZRjZvTmx1SF9jIn0.eyJqdGkiOiJkMDc3MTQ5ZS03ZmE0LTQ0NDUtOGZlMS1kM2ZiNDcyNjUyNjgiLCJleHAiOjE1MzEyMzYzMzgsIm5iZiI6MCwiaWF0IjoxNTMxMjM2MDM4LCJpc3MiOiJodHRwOi8vbWJwNTEyLW1iZXJnbGp1bmctMDkxNy5sb2NhbDo4MDgwL2F1dGgvcmVhbG1zL2FsZnJlc2NvLWRicCIsImF1ZCI6ImFsZnJlc2NvLWNsaWVudCIsInN1YiI6IjQ3MGM3YTkxLWIwMTQtNGRhYi05YmFmLTI5MDYyMWMxMDA1ZiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFsZnJlc2NvLWNsaWVudCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjU1Y2E5MGVjLWVhNWYtNDhiNy1hZTk5LTFkNDkyOThjMWQyOCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJuYW1lIjoiQWxmcmVzY28gQWRtaW4iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJBbGZyZXNjbyIsImZhbWlseV9uYW1lIjoiQWRtaW4iLCJlbWFpbCI6ImFkbWluQGFwcC5hY3Rpdml0aS5jb20ifQ.TqXDSzXsbIDWpQUJ12xdezB-SOg3NIu5WK8nOGmR9gt3ijISN0xn4BogKC8k-r7ldMqqDsxNrpoHgFNaop6YYtYeDQLAONBhrL8RvupT2OM_vQRt311bw6WS30Q0975ImA6W7s3-MCiFKJr4mvPipsrxocrDs1274tRxIhTWpxTUtQYPlBhPN32PpVows04wT5lO5_uG5BEcRFdWT8RGamT3UtRbkpsQc8ppx0ZC_oUFHtf5HZCPqXmTb95MfdsMR9OgTC15tEy_Jkqy6neZkHOwqfFrFBacJqzcVqhdHTmXIBDZizvSUHu7e3DSDnYIObVQonlOgFvrySAb8QkXeQ",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxdUE1c3FDd0pqV21iNk14SkxrcmhjR0k0cHQtVDRENk1ZRjZvTmx1SF9jIn0.eyJqdGkiOiI5ZTZjZjQ0Yy0yNGQxLTQ4YzItODllOC0xMWYwNmY0NGUyMDciLCJleHAiOjE1MzEyMzc4MzgsIm5iZiI6MCwiaWF0IjoxNTMxMjM2MDM4LCJpc3MiOiJodHRwOi8vbWJwNTEyLW1iZXJnbGp1bmctMDkxNy5sb2NhbDo4MDgwL2F1dGgvcmVhbG1zL2FsZnJlc2NvLWRicCIsImF1ZCI6ImFsZnJlc2NvLWNsaWVudCIsInN1YiI6IjQ3MGM3YTkxLWIwMTQtNGRhYi05YmFmLTI5MDYyMWMxMDA1ZiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhbGZyZXNjby1jbGllbnQiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiI1NWNhOTBlYy1lYTVmLTQ4YjctYWU5OS0xZDQ5Mjk4YzFkMjgiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19fQ.XruKZ_87hISBvu9qRb7tV4h8qC89TKFUGB5KUjdPdBbQ_7_W60Zv__vN7X-MlUoXaOM2tddWjY1oKq7iz_S1eKyDZex_To9GSWr1nEFErQjuZ7F_7bln3cdCC0a9p9zBaZm8PwbOnOg5KvJbZHNx4DY1mfWCsQGjqgj_316jSpb3plnHsKTuOjw5YF4QOhk6-O0Y5Ob7mya3E4bxfTHs0x2V37n3nnNlQ9umxwoVKa-wX9q9A3VWu8YynirIaFp1rlMR4nsSQQDXVJX8kitEofVv_I3MRVWRcv49NuHk9Dfda5WvHgPTluol7S_xT5RKpkrtudPXApznDtKYA86y8Q",
"token_type": "bearer",
"not-before-policy": 1531230785,
"session_state": "55ca90ec-ea5f-48b7-ae99-1d49298c1d28"
}
Then test an ACS ReST API with the access token:
$ curl http://mbp512-mbergljung-0917.local:8082/alfresco/api/-default-/public/alfresco/versions/1/nodes/-ro... -H "Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxdUE1c3FDd0pqV21iNk14SkxrcmhjR0k0cHQtVDRENk1ZRjZvTmx1SF9jIn0.eyJqdGkiOiJkMDc3MTQ5ZS03ZmE0LTQ0NDUtOGZlMS1kM2ZiNDcyNjUyNjgiLCJleHAiOjE1MzEyMzYzMzgsIm5iZiI6MCwiaWF0IjoxNTMxMjM2MDM4LCJpc3MiOiJodHRwOi8vbWJwNTEyLW1iZXJnbGp1bmctMDkxNy5sb2NhbDo4MDgwL2F1dGgvcmVhbG1zL2FsZnJlc2NvLWRicCIsImF1ZCI6ImFsZnJlc2NvLWNsaWVudCIsInN1YiI6IjQ3MGM3YTkxLWIwMTQtNGRhYi05YmFmLTI5MDYyMWMxMDA1ZiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFsZnJlc2NvLWNsaWVudCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjU1Y2E5MGVjLWVhNWYtNDhiNy1hZTk5LTFkNDkyOThjMWQyOCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJuYW1lIjoiQWxmcmVzY28gQWRtaW4iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJBbGZyZXNjbyIsImZhbWlseV9uYW1lIjoiQWRtaW4iLCJlbWFpbCI6ImFkbWluQGFwcC5hY3Rpdml0aS5jb20ifQ.TqXDSzXsbIDWpQUJ12xdezB-SOg3NIu5WK8nOGmR9gt3ijISN0xn4BogKC8k-r7ldMqqDsxNrpoHgFNaop6YYtYeDQLAONBhrL8RvupT2OM_vQRt311bw6WS30Q0975ImA6W7s3-MCiFKJr4mvPipsrxocrDs1274tRxIhTWpxTUtQYPlBhPN32PpVows04wT5lO5_uG5BEcRFdWT8RGamT3UtRbkpsQc8ppx0ZC_oUFHtf5HZCPqXmTb95MfdsMR9OgTC15tEy_Jkqy6neZkHOwqfFrFBacJqzcVqhdHTmXIBDZizvSUHu7e3DSDnYIObVQonlOgFvrySAb8QkXeQ" | python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2758 100 2758 0 0 12312 0 --:--:-- --:--:-- --:--:-- 12312
{
"list": {
"pagination": {
"count": 7,
"hasMoreItems": false,
"totalItems": 7,
"skipCount": 0,
"maxItems": 100
},
"entries": [
{
"entry": {
"createdAt": "2018-07-03T13:19:03.359+0000",
"isFolder": true,
"isFile": false,
"createdByUser": {
"id": "System",
"displayName": "System"
},
"modifiedAt": "2018-07-03T13:19:33.994+0000",
"modifiedByUser": {
"id": "System",
"displayName": "System"
},
"name": "Data Dictionary",
"id": "b9589f54-14da-4c96-b5b2-fdd9830d1911",
"nodeType": "cm:folder",
"parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"
}
},
{
"entry": {
"createdAt": "2018-07-03T13:19:03.968+0000",
"isFolder": true,
"isFile": false,
"createdByUser": {
"id": "System",
"displayName": "System"
},
"modifiedAt": "2018-07-03T13:19:03.968+0000",
"modifiedByUser": {
"id": "System",
"displayName": "System"
},
"name": "Guest Home",
"id": "fa557b0e-c517-44dd-8470-5f9a654df40d",
"nodeType": "cm:folder",
"parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"
}
},
{
"entry": {
"createdAt": "2018-07-03T13:19:04.116+0000",
"isFolder": true,
"isFile": false,
"createdByUser": {
"id": "System",
"displayName": "System"
},
"modifiedAt": "2018-07-03T13:19:04.116+0000",
"modifiedByUser": {
"id": "System",
"displayName": "System"
},
"name": "Imap Attachments",
"id": "ba05fdb9-4807-48b9-b71b-a227e513dbd9",
"nodeType": "cm:folder",
"parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"
}
},
{
"entry": {
"createdAt": "2018-07-03T13:19:04.131+0000",
"isFolder": true,
"isFile": false,
"createdByUser": {
"id": "System",
"displayName": "System"
},
"modifiedAt": "2018-07-03T13:19:04.131+0000",
"modifiedByUser": {
"id": "System",
"displayName": "System"
},
"name": "IMAP Home",
"id": "bc6a70ac-22ff-4d35-b3bf-ddf63c3ecf43",
"nodeType": "cm:folder",
"parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"
}
},
{
"entry": {
"createdAt": "2018-07-03T13:19:04.048+0000",
"isFolder": true,
"isFile": false,
"createdByUser": {
"id": "System",
"displayName": "System"
},
"modifiedAt": "2018-07-03T13:19:04.048+0000",
"modifiedByUser": {
"id": "System",
"displayName": "System"
},
"name": "Shared",
"id": "3d1d3ad2-4385-4981-b87b-fd5e9a5a3331",
"nodeType": "cm:folder",
"parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"
}
},
{
"entry": {
"createdAt": "2018-07-03T13:19:09.878+0000",
"isFolder": true,
"isFile": false,
"createdByUser": {
"id": "System",
"displayName": "System"
},
"modifiedAt": "2018-07-03T13:19:29.873+0000",
"modifiedByUser": {
"id": "System",
"displayName": "System"
},
"name": "Sites",
"id": "962eeedd-142e-4a83-9bcf-32f1f7f0c7ad",
"nodeType": "st:sites",
"parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"
}
},
{
"entry": {
"createdAt": "2018-07-03T13:19:04.037+0000",
"isFolder": true,
"isFile": false,
"createdByUser": {
"id": "System",
"displayName": "System"
},
"modifiedAt": "2018-07-03T13:19:04.037+0000",
"modifiedByUser": {
"id": "System",
"displayName": "System"
},
"name": "User Homes",
"id": "0c9744fc-ffd6-4583-aa8d-c8ebebcbcc56",
"nodeType": "cm:folder",
"parentId": "c81674eb-7cf9-4a65-a970-9370f99d3e97"
}
}
]
}
}
Everything is working fine, time to move on to the ADF applications and configure them to integrate with keycloak.
To have an ADF application to work with I’m going to use the ADF Project generator and generate an ADP application that will have both process and content components. This way I can test that SSO login works seamlessly for both ACS and APS.
I already got node and npm installed.
Make sure you have newest generator as the keycloak integration only works with ADF version 2.4.0:
$ sudo npm uninstall -g generator-alfresco-adf-app
$ npm install -g generator-alfresco-adf-app
Now, generate an ADF application:
$ yo alfresco-adf-app
_-----_
| | ╭──────────────────────────────────────────╮
|--(o)--| │ Update available: 2.0.3 (current: 2.0.0) │
`---------´ │ Run npm install -g yo to update. │
( _´U`_ ) ╰──────────────────────────────────────────╯
/___A___\ /
| ~ |
__'.___.'__
´ ` |° ´ Y `
,****.
,.**. `***** <-_
******** ***** ####
$********::**** ####;
_.-._`***::*** ######
,*******, *::* .;##### @
**********,' -=#####',@@@
***' .,---, ,.-==@@@@@@@@
* /@@@@@',@ @\ '@@@@@@@
'@@@@/ @@@ @@@\ ':#'
!@@@@ @@@@ @@@@@@@@@^
@@@@ @@@@@ @@@@@@@'
`"$ '@@@@@. '##'
'@@@@;'
ADF Angular app generator for Alfresco
Version 2.4.0
? Your project name adf-app-client
? Application blueprint Process and Content Services
? Would you like to install dependencies now? No
adf-cli-acs-aps-template
create .angular-cli.json
create .editorconfig
create .npmignore
create .travis.yml
create docker.md
create Dockerfile
create e2e/app.e2e-spec.ts
create e2e/app.po.ts
create e2e/tsconfig.e2e.json
create karma.conf.js
create LICENSE
create nginx.conf
create package.json
create protractor-ci.conf.js
create protractor.conf.js
create proxy.conf.json
create README.md
create src/app.config.json
create src/app/adf.module.ts
create src/app/app-layout/app-layout.component.css
create src/app/app-layout/app-layout.component.html
create src/app/app-layout/app-layout.component.spec.ts
create src/app/app-layout/app-layout.component.ts
create src/app/app.component.html
create src/app/app.component.scss
create src/app/app.component.spec.ts
create src/app/app.component.ts
create src/app/app.module.ts
create src/app/app.routes.ts
create src/app/apps/apps.component.css
create src/app/apps/apps.component.html
create src/app/apps/apps.component.ts
create src/app/documentlist/documentlist.component.css
create src/app/documentlist/documentlist.component.html
create src/app/documentlist/documentlist.component.spec.ts
create src/app/documentlist/documentlist.component.ts
create src/app/file-view/blob-view.component.ts
create src/app/file-view/bob-view.component.html
create src/app/file-view/file-view.component.html
create src/app/file-view/file-view.component.scss
create src/app/file-view/file-view.component.ts
create src/app/home/home.component.css
create src/app/home/home.component.html
create src/app/home/home.component.spec.ts
create src/app/home/home.component.ts
create src/app/login/login.component.css
create src/app/login/login.component.html
create src/app/login/login.component.spec.ts
create src/app/login/login.component.ts
create src/app/services/preview.service.ts
create src/app/start-process/start-process.component.html
create src/app/start-process/start-process.component.scss
create src/app/start-process/start-process.component.spec.ts
create src/app/start-process/start-process.component.ts
create src/app/stencils.module.ts
create src/app/task-details/task-details.component.css
create src/app/task-details/task-details.component.html
create src/app/task-details/task-details.component.spec.ts
create src/app/task-details/task-details.component.ts
create src/app/tasks/tasks.component.css
create src/app/tasks/tasks.component.html
create src/app/tasks/tasks.component.spec.ts
create src/app/tasks/tasks.component.ts
create src/assets/.gitkeep
create src/custom-style.scss
create src/environments/environment.prod.ts
create src/environments/environment.ts
create src/favicon-96x96.png
create src/index.html
create src/main.ts
create src/polyfills.ts
create src/test.ts
create src/tsconfig.app.json
create src/tsconfig.spec.json
create src/typings.d.ts
create tsconfig.json
create tslint.json
The ACS and APS URLs need to be configured in the newly generated ADF app as the default ones don’t match. Open up the proxy.conf.json file and change it to the following:
{
"/alfresco": {
"target": "http://localhost:8082",
"secure": false,
"changeOrigin": true
},
"/activiti-app": {
"target": "http://localhost:9080",
"secure": false,
"changeOrigin": true
}
}
We also need to do some configuration to enable Identity Service authentication (i.e. Keycloak), open up the src/app.config.json file and configure OAuth2 as follows:
{
"$schema": "../node_modules/@alfresco/adf-core/app.config.schema.json",
"ecmHost": "http://{hostname}{ort}",
"bpmHost": "http://{hostname}{ort}",
"providers" : "ALL",
"authType" :"OAUTH",
"oauth2": {
"host": "http://mbp512-mbergljung-0917.local:8080/auth/realms/alfresco-dbp",
"clientId": "alfresco-client",
"scope": "openid",
"secret": "",
"implicitFlow": true,
"silentLogin": false,
"redirectUri": "/",
"redirectUriLogout": "/logout"
},
"application": {
"name": "Alfresco ADF Appplication"
},
"languages": [
...
The only property that you should have to change is the oauth2.host property, see above in the APS config section for explanation.
Now, install all packages and then start the ADF App as follows:
$ cd adf-app-client/
adf-app-client mbergljung$ npm install
...
adf-app-client mbergljung$ npm start
And then access the UI on URL http://localhost:4200. It should redirect you to Keycloak for authentication with a URL looking something like this:
And after successfully logging in (with admin/admin) you should be redirected back to the ADF app as per the redirect_uri in the above URL. You should now see the following ADF app UI:
Now click on both Process Apps and Document List in the left navigation menu, you should not be prompted to login. Test also that you can logout by clicking the bottom icon in the left navigation menu.
So far we created the user directly in Keycloak. You will most likely not create and store your users in the keycloak database. Most solutions will use an enterprise LDAP server, such as MS Active Directory or OpenLDAP. We will look at how to use an external LDAP server by installing Apache DS locally and then populate it with a couple of users that we will then use for authentication.
The simplest way to get started is using a Docker Hub image. Update the docker-compose/docker-compose.yml file and uncomment the section with the following service definition at the end:
...
ldap:
image: greggigon/apacheds
environment:
- BOOTSTRAP_FILE=/bootstrap/demo.ldif
restart: always
ports:
- 10389:10389
volumes:
- ./data/ldap/data:/data
- ./ldap/bootstrap:/bootstrap
Make sure you have cloned the source code project as it has an LDIF file located at docker-compose/ldap/bootstrap/demo.ldif that is used to bootstrap the users.
Restart everything:
docker-compose $ docker-compose down
docker-compose $ docker-compose up
You should see something like this in the logs:
ldap_1 | Bootstraping Apache DS with Data from /bootstrap/demo.ldif
ldap_1 | adding new entry "ou=People,dc=example,dc=com"
ldap_1 | adding new entry "ou=RealmRoles,dc=example,dc=com"
ldap_1 | adding new entry "ou=FinanceRoles,dc=example,dc=com"
ldap_1 | adding new entry "uid=jbrown,ou=People,dc=example,dc=com"
ldap_1 | adding new entry "uid=bwilson,ou=People,dc=example,dc=com"
ldap_1 | adding new entry "cn=ldap-user,ou=RealmRoles,dc=example,dc=com"
ldap_1 | adding new entry "cn=ldap-admin,ou=RealmRoles,dc=example,dc=com"
ldap_1 | adding new entry "cn=accountant,ou=FinanceRoles,dc=example,dc=com"
If you want to you can install Apache Directory Studio and have a look at the directory from there:
So we now got the directory server running and it is populated with a couple of users. Time to integrate it with Keycloak so we can authenticate with these new users. Keycloak can federate external user databases. Out of the box there's support for LDAP and Active Directory.
Go to the Admin Console in Keycloak via http://localhost:8080/auth/admin/master/console/.
In the realm alfresco-dbp that we created earlier on click on the Users Federation menu option to get you to the User Federation page:
When you get to this page, there is an Add Provider... select box. You should see ldap within this list. Selecting ldap will bring you to the ldap configuration page. We will add here the ApacheDS LDAP settings:
Here are the fields that you need to configure:
Out of the box, Keycloak is configured to import only username, email, first and last name, but you are free to configure mappers and add more attributes or delete default ones. It supports password validation via LDAP/AD protocols and different user metadata synchronization modes.
Here is the full list of attributes for our schema:
Configure APS so it knows about the users in the Directory
You will not be able to use the Apache DS users until they also exist (are known to) APS. So we need to configure LDAP sync for users in APS. You can easily test this by trying to login at http://localhost:9080/activiti-app, you will see an error message as follows when trying to login with a directory user such as jbrown/password:
Configure LDAP Sync as follows in docker-compose/aps/activiti-ldap.properties, uncomment the properties in the supplied file from the source code project:
# LDAP Connection
ldap.authentication.java.naming.provider.url=ldap://ldap:10389
ldap.synchronization.java.naming.security.principal=uid=admin,ou=system
ldap.synchronization.java.naming.security.credentials=secret
# LDAP Sync
ldap.synchronization.full.enabled=true
ldap.synchronization.full.cronExpression=0 0 0 * * ?
ldap.synchronization.userSearchBase=ou=People,dc=example,dc=com
ldap.synchronization.personQuery=(objectclass\=inetOrgPerson)
# Need to set proper group search base, otherwise we get exceptions
ldap.synchronization.groupSearchBase=ou=RealmRoles,dc=example,dc=com
ldap.synchronization.groupQuery=(objectclass\=groupOfNames)
Restart the process container as follows:
docker-compose mbergljung$ docker-compose stop process
Stopping docker-compose_process_1 ... done
docker-compose mbergljung$ docker-compose rm process
Going to remove docker-compose_process_1
Are you sure? [yN] y
Removing docker-compose_process_1 ... done
docker-compose mbergljung$ docker-compose up -d --no-deps process
Creating docker-compose_process_1 ... done
The logs should show the LDAP sync progress:
process_1 | 08:43:40 [pool-4-thread-1] INFO com.activiti.api.idm.AbstractExternalIdmSourceSyncService - No initial LDAP sync info found. Executing full synchronization.
…
Now verify that it works to login with an LDAP user. Go to http://localhost:9080/activiti-app and use jbrown/password.
In ACS you don’t have to configure LDAP user sync before you can test with a directory user. ACS will create missing users on the fly. However, if you want the first name, last name, email etc to be available in ACS, then you still need to configure LDAP sync as normally done in ACS. We will not do it here though as we just want to test keycloak authentication.
It should now be possible to login with jbrown/password at http://localhost:4200 and access both process and content components. Note. you might have to logout of the previous session first, and then login as jbrown.
Blog posts and updates from the Alfresco Platform Services Team.
By using this site, you are agreeing to allow us to collect and use cookies as outlined in Alfresco’s Cookie Statement and Terms of Use (and you have a legitimate interest in Alfresco and our products, authorizing us to contact you in such methods). If you are not ok with these terms, please do not use this website.