How to Add Authorization and Protect Your Application With OpenAM and OpenIG Stack - OpenIdentityPlatform/OpenAM GitHub Wiki
Introduction
In the following article, we will set up centralized authentication with OpenAM and set up gateway access to a test application with OpenIG. OpenIG will use an authentication session issued by OpenAM for authorization.
We will use an application developed using Spring Boot and Spring Security as the protected application.
The source code of the application is located on GitHub.
For the demonstration purposes, we will run services in Docker containers with the docker compose
tool.
The authorization diagram is in the picture below:
Run the Test Application
Create a docker-compose.yaml
file and add spring-service
to the services section.
Map the 8081 port to test for a functional check
docker-compose.yaml
services:
spring-service:
container_name: spring-service
image: openidentityplatform/spring-security-openam-example
restart: always
ports:
- "8081:8081"
environment:
JAVA_OPTS: -Dspring.profiles.active=jwt
networks:
openam_network:
aliases:
- spring-service
networks:
openam_network:
driver: bridge
Once the application is running, let's check access to the API for which we need to add authentication.
curl http://localhost:8081/api/protected-jwt | json_pp
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 105 0 105 0 0 12684 0 --:--:-- --:--:-- --:--:-- 13125
{
"error" : "Unauthorized",
"path" : "/protected-jwt",
"status" : 401,
"timestamp" : "2024-04-16T06:01:25.331+00:00"
}
For successful authentication, a valid JWT must be passed to the API in the Authorization HTTP header. This is what the OpenAM and OpenIG stack will be responsible for. Shut down the test service with the docker compose down
command for now.
OpenAM Setup
Let's configure the OpenAM authentication service. It will be responsible for authentication and converting the authentication token to JWT (see below).
Add the OpenAM and OpenIG hostnames to the hosts
file, for example 127.0.0.1 openam.example.org openig.example.org
.
On Windows systems, the hosts file is located at C:\Windows\System32\drivers\etc\hosts
, on Linux and Mac it is located at /etc/hosts
.
Add the OpenAM service to the docker-compose.yaml
file:
...
openam:
image: openidentityplatform/openam:latest
container_name: openam
ports:
- "8080:8080"
networks:
openam_network:
aliases:
- openam.example.org
...
Start OpenAM with the docker compose up openam
command. Once OpenAM is running, configure using the following command:
docker exec -w '/usr/openam/ssoconfiguratortools' openam bash -c \
'echo "ACCEPT_LICENSES=true
SERVER_URL=http://openam.example.org:8080
DEPLOYMENT_URI=/$OPENAM_PATH
BASE_DIR=$OPENAM_DATA_DIR
locale=en_US
PLATFORM_LOCALE=en_US
AM_ENC_KEY=
ADMIN_PWD=passw0rd
AMLDAPUSERPASSWD=p@passw0rd
COOKIE_DOMAIN=example.org
ACCEPT_LICENSES=true
DATA_STORE=embedded
DIRECTORY_SSL=SIMPLE
DIRECTORY_SERVER=openam.example.org
DIRECTORY_PORT=50389
DIRECTORY_ADMIN_PORT=4444
DIRECTORY_JMX_PORT=1689
ROOT_SUFFIX=dc=openam,dc=example,dc=org
DS_DIRMGRDN=cn=Directory Manager
DS_DIRMGRPASSWD=passw0rd" > conf.file && java -jar openam-configurator-tool*.jar --file conf.file'
And wait for the command execution to complete.
STS Setup
STS (Security Token Service) converts an OpenAM token to a JWT. After authentication, OpenAM returns an authentication token, which is a randomly generated sequence of characters. The STS service is responsible for converting the authentication token into a JWT, which contains information about the authenticated user. OpenIG will use STS for authorization.
To configure STS, go to the administrator console URL
http://openam.example.org:8080/openam/XUI/#login/
In the login field enter the amadmin
value, in the password field enter the value from the ADMIN_PWD
parameter of the setup command, in this case, passw0rd
.
In the left menu click STS and create a new STS Instance with the following settings:
Setting | Value |
---|---|
Supported Token Transforms | OPENAM->OPENIDCONNECT;don't invalidate interim OpenAM session |
Deployment Url Element | jwt |
The id of the OpenID Connect Token Provider | https://openam.example.org/openam |
Client secret | changeme |
Confirm client secret | changeme |
The audience for issued tokens | https://openam.example.org/openam |
Then press the Create button
Setting up a test user
In the OpenAM admin console, navigate to the root realm and select Subjects
from the left menu. Set the password for the demo
user. To do this, select it in the list of users, and click the Edit
link under Password. Enter and save the new password. Next, log out of the administrator console.
OpenIG Setup
OpenIG is responsible for authorization policies for back-end services. It converts the authentication token to a JWT in the OpenAM service and after successful authorization, passes the JWT to the back-end services in the HTTP Authorization header.
Create an openig-config
folder and add two files to it: admin.json
{
"prefix" : "openig",
"mode": "PRODUCTION"
}
and config.json
:
{
"heap": [
],
"handler": {
"type": "Chain",
"config": {
"filters": [
],
"handler": {
"type": "Router",
"name": "_router",
"capture": "all"
}
}
}
}
Next, add a route that will proxy user requests to the test application, enriching the request with an Authorization header with a JWT token obtained from OpenAM.
Create a folder openig-config/routes/
and add the 10-protected.json
file to the folder.
10-protected.json
{
"name": "${matches(request.uri.path, '^/api/protected-jwt') || matches(request.uri.path, '^/protected-jwt')}",
"condition": "${matches(request.uri.path, '^/api/protected-jwt') || matches(request.uri.path, '^/protected-jwt')}",
"monitor": true,
"timer": true,
"handler": {
"type": "Chain",
"config": {
"filters": [
{
"type": "ConditionalFilter",
"config": {
"condition": "${empty contexts.sts.issuedToken and not empty request.cookies['iPlanetDirectoryPro'][0].value}",
"delegate": {
"type": "TokenTransformationFilter",
"config": {
"openamUri": "${system['openam']}",
"realm": "/",
"instance": "jwt",
"from": "OPENAM",
"to": "OPENIDCONNECT",
"idToken": "${request.cookies['iPlanetDirectoryPro'][0].value}"
}
}
}
},
{
"type": "ConditionalFilter",
"config": {
"condition": "${not empty contexts.sts.issuedToken}",
"delegate": {
"type": "HeaderFilter",
"config": {
"messageType": "REQUEST",
"remove": [
"Authorization",
"JWT"
],
"add": {
"Authorization": [
"Bearer ${contexts.sts.issuedToken}"
]
}
}
}
}
},
{
"type": "ConditionEnforcementFilter",
"config": {
"condition": "${not empty contexts.sts.issuedToken}",
"failureHandler": {
"type": "StaticResponseHandler",
"config": {
"status": 302,
"reason": "Found",
"headers": {
"Content-Type": [
"application/json"
],
"Location": [
"${system['openam']}/UI/Login?org=/&goto=${urlEncode(contexts.router.originalUri)}"
]
},
"entity": "{ \"Redirect\": \"${system['openam']}/UI/Login?org=/&goto=${urlEncode(contexts.router.originalUri)}\"}"
}
}
}
}
],
"handler": "EndpointHandler"
}
},
"heap": [
{
"name": "EndpointHandler",
"type": "DispatchHandler",
"config": {
"bindings": [
{
"handler": "ClientHandler",
"capture": "all",
"baseURI": "${matchingGroups(system['spring-service'],\"((http|https):\/\/(.[^\/]*))\")[1]}"
}
]
}
}
]
}
This route authorizes two endpoints: API - /api/protected-jwt
and UI - /protected-jwt
.
Add the OpenIG service to the docker-compose.yaml
file and remove the port mapping from the spring-service
. Now this service is available only through OpenIG.
services:
openam:
image: openidentityplatform/openam:latest
container_name: openam
ports:
- "8080:8080"
networks:
openam_network:
aliases:
- openam.example.org
openig:
image: openidentityplatform/openig:latest
container_name: openig
volumes:
- ./openig-config:/usr/local/openig-config:ro
environment:
CATALINA_OPTS: -Dopenig.base=/usr/local/openig-config -Dspring-service=http://spring-service:8081 -Dopenam=http://openam.example.org:8080/openam
ports:
- "8081:8080"
networks:
openam_network:
aliases:
- openig.example.org
spring-service:
container_name: spring-service
image: openidentityplatform/spring-security-openam-example
restart: always
# ports:
# - "8081:8081"
environment:
JAVA_OPTS: -Dspring.profiles.active=jwt
networks:
openam_network:
aliases:
- spring-service
networks:
openam_network:
driver: bridge
Start OpenIG and the demo application services with the docker compose up openig spring-service
command.
Test the Solution
Test the API Authorization
Get the OpenAM authentication token for the demo
user:
curl -X POST -H "X-OpenAM-Username: demo" -H "X-OpenAM-Password: passw0rd" \
-H "Content-Type: application/json" -H "Accept-API-Version: resource=2.1" \
http://openam.example.org:8080/openam/json/realms/root/authenticate | json_pp
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 159 100 159 0 0 4197 0 --:--:-- --:--:-- --:--:-- 4297
{
"realm" : "/",
"successUrl" : "/openam/console",
"tokenId" : "AQIC5wM2LY4Sfcze3DbBXVSXggTyZNpGfwOoFPLnHwmqLG0.*AAJTSQACMDEAAlNLABM2MTY1Mjg2MzI5Mzc4ODM0MzQ5AAJTMQAA*"
}
Call with the received token /api/protected-jwt
endpoint:
curl --cookie "iPlanetDirectoryPro=AQIC5wM2LY4Sfcze3DbBXVSXggTyZNpGfwOoFPLnHwmqLG0.*AAJTSQACMDEAAlNLABM2MTY1Mjg2MzI5Mzc4ODM0MzQ5AAJTMQAA*" \
"http://openig.example.org:8081/api/protected-jwt" | json_pp
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 30 0 30 0 0 1071 0 --:--:-- --:--:-- --:--:-- 1111
{
"method" : "JWT",
"user" : "demo"
}
OpenIG, via OpenAM, converted the authentication token passed in the iPlanetDirectoryPro
cookie into a JWT and passed this JWT to the spring-service
test application. The test application retrieved the user information from the JWT and returned a successful response.
Test the UI Authorization
Log out of the OpenAM administrator console or open the browser in Incognito mode
Open the http://openig.example.org:8081/protected-jwt URL in your browser. OpenIG will not find the authentication token cookie and will redirect the browser to OpenAM authentication. Enter the login demo
, and password and click the Log In
button.
After successful authentication, the gateway will redirect the request to the Spring Boot application. It will receive a cookie with an authentication token from the http request, convert the token in OpenAM in STS service to JWT, and pass the received JWT to the demo application. The demo application will verify the JWT and return a successful response.
The source code for the demo application is available at https://github.com/OpenIdentityPlatform/spring-security-openam-example.
The source code for the docker compose configuration and OpenIG routes for the article can be found at https://github.com/OpenIdentityPlatform/openam-openig-springboot-example.