-
Notifications
You must be signed in to change notification settings - Fork 161
How to Add Authorization and Protect Your Application With OpenAM and OpenIG Stack
- What is this article about?
- Preparation
- Configure OpenIG
- OpenAM configuration
- Integrate OpenAM Authentication with OpenIG
We will add OpenAM authentication for an application, and set up proxying to the application using OpenIG so an unauthenticated user could not access the application.
We will use Docker and docker-compose to simplify the deployment.
Authorization diagram is on the picture below:
As a service that needs to be protected, we will use maximthomas/sample-service
from DockerHub. The source code for sample service is on the GitHub https://github.com/maximthomas/openig-protect-ws/tree/master/sample-service:
This service returns request headers as well as authentication JWT data in JSON format.
Let's create docker-compose.yaml
file and add sample-service
to the file:
docker-compose.yaml
version: '3'
services:
sample-service:
image: maximthomas/sample-service
restart: always
ports:
- "8080:8080"
networks:
openam_network:
aliases:
- sample-service
networks:
openam_network:
driver: bridge
Test service
$ curl http://localhost:8080/secured | json_pp
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 88 0 88 0 0 8800 0 --:--:-- --:--:-- --:--:-- 8800
{
"headers" : {
"user-agent" : "curl/7.58.0",
"host" : "localhost:8080",
"accept" : "*/*"
},
"jwt" : {}
}
If we will open http://localhost:8080/secured
URL in a browser, the resulst will be the same JSON.
So, now we need to secure http://localhost:8080/secured
, so an unauthenticated user could not access the endpoint.
Add FQDN openam.example.org and openig.example.org to hosts
file:
127.0.0.1 openam.example.org openig.example.org
Now we will proxy all requests to sample-service/secured
endpoint via OpenIG
To do this, create an openig-config
folder and add 2 files there.
admin.json
{
"prefix" : "openig",
"mode": "PRODUCTION"
}
and
config.json
{
"heap": [
],
"handler": {
"type": "Chain",
"config": {
"filters": [
],
"handler": {
"type": "Router",
"name": "_router",
"capture": "all"
}
}
}
}
In the openig-config
folder create the routes
folder and add the route for the closed service 10-secured.json
.
10-secured.json
{
"name":"${matches(request.uri.path, '^/secured')}",
"condition":"${matches(request.uri.path, '^/secured')}",
"monitor":true,
"timer":true,
"handler":{
"type":"Chain",
"config":{
"filters":[
],
"handler":"EndpointHandler"
}
},
"heap":[
{
"name":"EndpointHandler",
"type":"DispatchHandler",
"config":{
"bindings":[
{
"handler":"ClientHandler",
"capture":"all",
"baseURI":"${matchingGroups(system['secured'],\"((http|https):\/\/(.[^\/]*))\")[1]}"
}
]
}
}
]
}
Then add OpenIG to docker-compose.yaml
file:
version: '3'
services:
sample-service:
image: maximthomas/sample-service
restart: always
networks:
openam_network:
aliases:
- sample-service
openig:
image: openidentityplatform/openig
volumes:
- ./openig-config:/usr/local/openig-config/config:ro
ports:
- "8080:8080"
environment:
CATALINA_OPTS: -Dopenig.base=/usr/local/openig-config -Dsecured=http://sample-service:8080 -Dopenam=http://openam.example.org:8080/openam
networks:
openam_network:
aliases:
- openig.example.org
networks:
openam_network:
driver: bridge
Now OpenIG proxies all requests to secured
endpoint and one cant access sample-service
directly
Then we will add authentication via OpenAM
And add OpenAM to docker-compose.yaml
file:
version: '3.7'
services:
sample-service:
image: maximthomas/sample-service
restart: always
networks:
openam_network:
aliases:
- sample-service
openig:
image: openidentityplatform/openig
volumes:
- ./openig-config:/usr/local/openig-config/config:ro
ports:
- "8081:8080"
environment:
CATALINA_OPTS: -Dopenig.base=/usr/local/openig-config -Dsecured=http://sample-service:8080 -Dopenam=http://openam.example.org:8080/openam
networks:
openam_network:
aliases:
- openig.example.org
openam:
image: openidentityplatform/openam
volumes:
- ./data/openam:/usr/openam/config
ports:
- "8080:8080"
user: root
networks:
openam_network:
aliases:
- openam.example.org
networks:
openam_network:
driver: bridge
Start docker-compose and open OpenAM link in browser: http://openam.example.org:8080/openam/
Choose Create Default Configuration, enter passwords for amAdmin
and Default Policy Agent
and press Crate Configuration
After successful configuration authenticate to OpenAM console
In OpenAM console go to Configure -> Global Services -> Platform
Add .example.org
to Cookie Domain setting
In OpenAM console go to Realms -> Top Level Realm
In the left menu click STS and create 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 Create and Back to Access Control
Open openig-config/routes/10-secured.json
file and add three filters:
- The first conditional filter takes OpenAM authentication token form request if it exists and converts authentication token to a JWT
- The second filter creates Authorization header with the JWT
- The third filter redirects the client to OpenAM authentication if there's not valid OpenAM token in the request
10-secured.json
{
"name": "${matches(request.uri.path, '^/secured')}",
"condition": "${matches(request.uri.path, '^/secured')}",
"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['secured'],\"((http|https):\/\/(.[^\/]*))\")[1]}"
}
]
}
}
]
}
Open http://openig.example.org:8081/secured in a browser
OpenIG will redirect to authentication in OpenAM:
Let's authenticate with amadmin user:
After successful authentication, OpenAM will redirect to http://openig.example.org:8081/secured
In the response secured service returns Authorization header with JWT token:
{"headers":{"authorization":"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbWFkbWluIiwiaXAiOiIxNzIuMTguMC4xIiwiaXNzIjoiaHR0cHM6Ly9vcGVuYW0uZXhhbXBsZS5vcmcvb3BlbmFtIiwibm9uY2UiOiJhOTdmNzk3My0zODcwLTRjYjMtYjVmZi0xYTk2MzA4MGJhNmIiLCJhdWQiOiJodHRwczovL29wZW5hbS5leGFtcGxlLm9yZy9vcGVuYW0iLCJhdXRoOnNlcnZpY2UiOiJsZGFwU2VydmljZSIsImF1dGg6bW9kdWxlIjoiRGF0YVN0b3JlIiwiYXV0aDp0aW1lIjoxNTg4MjMzNTYwLCJhdXRoOmxldmVsIjoiMCIsImF1dGg6dGltZTptYXgiOjE1ODg2NjU1MDAsInJlYWxtIjoiLyIsImF1dGg6dGltZTptYXg6aWRsZSI6MTU4ODIzNTM2MCwiZXhwIjoxNTg4MjM0MTYwLCJpYXQiOjE1ODgyMzM1NjAsImF1dGg6Y3R4aWQiOiIyMjQ4MThlOGVkZjQwOWFmMDEiLCJqdGkiOiJlNDYyNmM5My05ZWJhLTRiNWEtYWRlNi1lMTZjZmM0MjVjOTEifQ.PsdpW9rckgwnhU0w5Xgp2PMUD3XlGtCEiLSot-UAMJ8","referer":"http://openam.example.org:8080/openam/XUI/?org=/&goto=http%3A%2F%2Fopenig.example.org%3A8081%2Fsecured","accept-language":"en-US;q=1,en;q=0.9,ru;q=0.8","cookie":"amlbcookie=01; iPlanetDirectoryPro=AQIC5wM2LY4Sfcwl2X8fnzaMmd3RPnJ4EnXuRIlCYswUKkI.*AAJTSQACMDEAAlNLABMzMDYyNjc5ODcxODk5ODE4OTEzAAJTMQAA*","host":"openig.example.org:8081","upgrade-insecure-requests":"1","connection":"Keep-Alive","accept-encoding":"gzip, deflate","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36"},"jwt":{"header":{"typ":"JWT","alg":"HS256"},"body":{"sub":"amadmin","ip":"172.18.0.1","iss":"https://openam.example.org/openam","nonce":"a97f7973-3870-4cb3-b5ff-1a963080ba6b","aud":"https://openam.example.org/openam","auth:service":"ldapService","auth:module":"DataStore","auth:time":1588233560,"auth:level":"0","auth:time:max":1588665500,"realm":"/","auth:time:max:idle":1588235360,"exp":1588234160,"iat":1588233560,"auth:ctxid":"224818e8edf409af01","jti":"e4626c93-9eba-4b5a-ade6-e16cfc425c91"}}}
Let's do the same with curl
tool:
Try access http://openig.example.org:8081/secured URL without an authentication token
$ curl -v http://openig.example.org:8081/secured
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to openig.example.org (127.0.0.1) port 8081 (#0)
> GET /secured HTTP/1.1
> Host: openig.example.org:8081
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: Apache-Coyote/1.1
< Location: http://openam.example.org:8080/openam/UI/Login?org=/&goto=http%3A%2F%2Fopenig.example.org%3A8081%2Fsecured
< Content-Type: application/json
< Content-Length: 123
< Date: Thu, 30 Apr 2020 08:02:34 GMT
<
* Connection #0 to host openig.example.org left intact
OpenIG returns 302 Status with OpenAM location header
Let's authenticate OpenAM with curl
$ curl -v -X POST -H "X-OpenAM-Username: amadmin" -H "X-OpenAM-Password: ampassword" -H "Content-Type: application/json" -H "Accept-API-Version: resource=2.1" http://openam.example.org:8080/openam/json/realms/root/authenticate
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to openam.example.org (127.0.0.1) port 8080 (#0)
> POST /openam/json/realms/root/authenticate HTTP/1.1
> Host: openam.example.org:8080
> User-Agent: curl/7.58.0
> Accept: */*
> X-OpenAM-Username: amadmin
> X-OpenAM-Password: ampassword
> Content-Type: application/json
> Accept-API-Version: resource=2.1
>
< HTTP/1.1 200
< X-Frame-Options: SAMEORIGIN
< Set-Cookie: amlbcookie=01; Domain=example.org; Path=/
< Set-Cookie: amlbcookie=01; Domain=openam.example.org; Path=/
< Cache-Control: no-cache, no-store, must-revalidate
< Content-API-Version: resource=2.1
< Expires: 0
< Pragma: no-cache
< Content-Type: application/json;charset=UTF-8
< Content-Length: 163
< Date: Thu, 30 Apr 2020 08:08:36 GMT
<
* Connection #0 to host openam.example.org left intact
{"tokenId":"AQIC5wM2LY4SfczD-B2nuUiXZX0u77ac03pjxJvxGortWZM.*AAJTSQACMDEAAlNLABQtODI1MzA5MjkwMTk1OTk5Mzk4NQACUzEAAA..*","successUrl":"/openam/console","realm":"/"}
Got authentication token from the response
Now try to access http://openig.example.org:8081/secured URL with issued authentication token
$ curl -v \
--cookie "iPlanetDirectoryPro=AQIC5wM2LY4SfczD-B2nuUiXZX0u77ac03pjxJvxGortWZM.*AAJTSQACMDEAAlNLABQtODI1MzA5MjkwMTk1OTk5Mzk4NQACUzEAAA..*" \
"http://openig.example.org:8081/secured" | json_pp
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to openig.example.org (127.0.0.1) port 8081 (#0)
> GET /secured HTTP/1.1
> Host: openig.example.org:8081
> User-Agent: curl/7.58.0
> Accept: */*
> Cookie: iPlanetDirectoryPro=AQIC5wM2LY4SfczD-B2nuUiXZX0u77ac03pjxJvxGortWZM.*AAJTSQACMDEAAlNLABQtODI1MzA5MjkwMTk1OTk5Mzk4NQACUzEAAA..*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Date: Thu, 30 Apr 2020 08:12:18 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
<
{ [1455 bytes data]
100 1448 0 1448 0 0 24542 0 --:--:-- --:--:-- --:--:-- 24542
* Connection #0 to host openig.example.org left intact
{
"jwt" : {
"header" : {
"alg" : "HS256",
"typ" : "JWT"
},
"body" : {
"auth:level" : "0",
"exp" : 1588234938,
"auth:module" : "DataStore",
"auth:time:max:idle" : 1588236138,
"auth:time" : 1588234116,
"jti" : "b86e6dc1-eeca-443f-b73d-44235f41f818",
"auth:time:max" : 1588652958,
"auth:service" : "ldapService",
"iat" : 1588234338,
"aud" : "https://openam.example.org/openam",
"iss" : "https://openam.example.org/openam",
"nonce" : "54da63fd-f8f1-49fc-96cf-b5d211f0588b",
"realm" : "/",
"auth:ctxid" : "606dc841c299b0f01",
"sub" : "amadmin",
"ip" : "172.18.0.1"
}
},
"headers" : {
"user-agent" : "curl/7.58.0",
"connection" : "Keep-Alive",
"accept" : "*/*",
"authorization" : "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbWFkbWluIiwiaXAiOiIxNzIuMTguMC4xIiwiaXNzIjoiaHR0cHM6Ly9vcGVuYW0uZXhhbXBsZS5vcmcvb3BlbmFtIiwibm9uY2UiOiI1NGRhNjNmZC1mOGYxLTQ5ZmMtOTZjZi1iNWQyMTFmMDU4OGIiLCJhdWQiOiJodHRwczovL29wZW5hbS5leGFtcGxlLm9yZy9vcGVuYW0iLCJhdXRoOnNlcnZpY2UiOiJsZGFwU2VydmljZSIsImF1dGg6bW9kdWxlIjoiRGF0YVN0b3JlIiwiYXV0aDp0aW1lIjoxNTg4MjM0MTE2LCJhdXRoOmxldmVsIjoiMCIsImF1dGg6dGltZTptYXgiOjE1ODg2NTI5NTgsInJlYWxtIjoiLyIsImF1dGg6dGltZTptYXg6aWRsZSI6MTU4ODIzNjEzOCwiZXhwIjoxNTg4MjM0OTM4LCJpYXQiOjE1ODgyMzQzMzgsImF1dGg6Y3R4aWQiOiI2MDZkYzg0MWMyOTliMGYwMSIsImp0aSI6ImI4NmU2ZGMxLWVlY2EtNDQzZi1iNzNkLTQ0MjM1ZjQxZjgxOCJ9.LdtU3SwN0jbaTc44VFxwGvxJcZlAj3Qr_iNTsGzREOY",
"host" : "openig.example.org:8081",
"cookie" : "iPlanetDirectoryPro=AQIC5wM2LY4SfczD-B2nuUiXZX0u77ac03pjxJvxGortWZM.*AAJTSQACMDEAAlNLABQtODI1MzA5MjkwMTk1OTk5Mzk4NQACUzEAAA..*"
}
}
Everything works!