WebService Authorization - Demo
In the last post [https://www.dhirubhai.net/pulse/webservice-authorization-common-pain-wael-eldoamiry] we discussed some of the WebServices authorization challenges, We also explained how a generic approach based on open specifications can save lots of efforts and provide a clean, decoupled implementation of authorization for web workload in general and more specifically the WebServices implementation.
In this demo we will show how authorization services can be configured in Red Hat Single Sign-on - RHSSO, and then integrated in spring boot application with zero code changes, using a declarative security method relying on external configuration files to enable/disable authorization and associated rules and control it both at deployment and runtime.
Don't forget, by end of this demo we will see how to secure Rest APIs using role based access control in addition to script (rule) based access control in the form of api-key, this is done declaratively and with zero code changes!.
Steps
- Create a realm in RHSSO
- Create openid-connect client
- Enable Authorization services for this client
- Define following resource
- Admin resource mapped to /api/admin uri
- User resource mapped to /api/user uri
- Claim resource mapped to /api/claim uri
- Define following policies
- Admin Role based policy
- User Role based policy
- Script claim based policy
- Define following permissions
- Admin Role policy to access Admin resource
- User Role policy to access User resource
- Script claim policy to access Claim resource
- Add following Roles in the current realm
- Admin
- User
- Finally create two users and assign then to following roles
- wael/ admin role
- alice/ user role
Now the moment of truth, How to integrate (consume the above in ) a spring boot application?
We will use a simple Spring boot application having a RestController to create a rest API with three endpoints as follows:
@RestController
public class ApplicationController { @RequestMapping(value = "/api/user", method = RequestMethod.GET) public String userRoleProtectedResource() { return createResponse(); } @RequestMapping(value = "/api/claim", method = RequestMethod.GET) public String claimProtectedResource() { return createResponse(); } @RequestMapping(value = "/api/admin", method = RequestMethod.GET) public String adminRoleProtectedResource() { return createResponse(); } private String createResponse() { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); StackTraceElement e = stacktrace[2]; String methodName = e.getMethodName(); return "Access Granted to --> " + methodName; } }
With the above controller all the rest API is already created and exposed with public access (Neither authentication nor authorization), when we run this spring boot application, All users can access the three endpoints:
- https://localhost:8080/api/user
- https://localhost:8080/api/admin
- https://localhost:8080/api/claim
Lets enable the authorization services, open the application.properties file and add the following lines:
keycloak.realm=auth-demo keycloak.auth-server-url=https://localhost:8180/auth keycloak.ssl-required=external keycloak.resource=api-service keycloak.bearer-only=true keycloak.credentials.secret=bfeb73d5-51ba-4b4e-b45f-a60309e0f3f9 keycloak.securityConstraints[0].authRoles[0]=admin keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/api/admin keycloak.securityConstraints[0].authRoles[1]=user keycloak.securityConstraints[0].securityCollections[1].patterns[0]=/api/user keycloak.securityConstraints[0].securityCollections[2].patterns[0]=/api/claim keycloak.policy-enforcer-config.lazy-load-paths=true keycloak.policy-enforcer-config.paths[0].path=/api/claim keycloak.policy-enforcer-config.paths[0].claimInformationPointConfig.claims[some-claim]={request.parameter['api-key']}
With the above properties file we identified the following
RHSSO realm name: auth-demo
RHSSO server URL: https://localhost:8180/auth
RHSSO openid-client (client id): api-service
RHSSO client secret (secret of api-service client): bfeb73d5-51ba-4b4e-b45f-a60309e0f3f9
Roles will be used in the application: admin, user
URLs will be protected: /api/admin, /api/user, /api/claim
Mapping the claim policy to query parameter (api-key): {request.parameter['api-key']}
Now, start the spring boot application again, Hit the three endpoints, you will get 403 response code (HTTP ERROR 403, You don't have authorization)
How do we get the authorization token?
We will need to generate the openid-connect token using users accounts (username/password)
First
Generate JWT token using the following CURL command:
curl -s -X POST https://localhost:8180/auth/realms/auth-demo/protocol/openid-connect/token \ -H 'Authorization: Basic YXBpLXNlcnZpY2U6YmZlYjczZDUtNTFiYS00YjRlLWI0NWYtYTYwMzA5ZTBmM2Y5' \ -H 'content-type: application/x-www-form-urlencoded' \ -d 'username=wael&password=wael&grant_type=password'
Second
Extract the .access_token from the JSON response
Third
Hit the endpoints using the following CURL
curl -s -X GET https://localhost:8080/api/user -H "Authorization: Bearer TOKEN_VALUE"
Consider the following
- User wael who is mapped to admin role will be able access /api/admin, while wael user will be denied access to /api/user because the user is not mapped to user role
- User alice who is mapped to user role will be able access /api/user, while alice user will be denied access to /api/admin because the user is not mapped to admin role
- No matter what is the user role, any user can access the endpoint /api/claim as long as a valid api-key is provided as configured in the Script claim based policy
Focusing more on the script claim policy that is used to introduce a simple api-key mechanism to protect your /api/claim endpoint, below is the policy implementation:
var context = $evaluation.context; var attributes = context.attributes; if (attributes.containsValue('some-claim', '123456')) { $evaluation.grant(); }
In spring boot application.properties file, we mapped the claim to request parameter of name “api-key” as follows
keycloak.policy-enforcer-config.paths[0].claimInformationPointConfig.claims[some-claim]={request.parameter['api-key']}
So once users provide the “api-key” parameter with the right value “123456”, they will be granted access to the api as follows:
https://localhost:8080/api/claim?api-key=123456
Note: for all above policies - including the script claim - users are still required to provide the token that was generated with the first call to the openid-connect token service, while the Role based access control (RBAC) requires nothing bur “Authorization: Bearer TOKEN_VALUE” header, the script claim requires extra query string parameter “api-key” as explained.
In the demo all above steps are explained in details in addition to a helper bash script that is used to automate all the testing use cases, you can find it on https://github.com/wael2000/webservice-authorization/blob/master/test.sh
Below is a screenshot of the outcome of running the script
Finally, Find the RHSSO realm configuration file along with the spring boot application and testing script "test.sh" on below Github repo:
https://github.com/wael2000/webservice-authorization
Senior Solutions Architect, Alexa International at Amazon
5 年Anwar M. Saqqar
Principal Solutions Architect at Red Hat
5 年The first part, introduction to WebServices authorization https://www.dhirubhai.net/pulse/webservice-authorization-common-pain-wael-eldoamiry/