This article will guide you through understanding OAuth2 and OpenID usage with Keycloak using a JAX-RS filter named ContainerRequestFilter which is available in JAX-RS servers such as Thorntail or WildFly.

OpenID is a process which deals with authentication (i.e. proving who you are). On the other habd, OAuth is about authorisation (i.e. to grant access to resources without having to deal with the original authentication).

OAuth is typically used in external partner sites to allow access to protected data without them having to re-authenticate a user.

In order to run this example, you will need a Keycloak server up and running. To learn more about it, check this tutorial: Introduction to Keycloak

Using Keycloak Admin CLI to create a Realm

Our example requires a Keycloak Realm to be set up and a Client definition which is allows up to be authorized using a Token issued by Keycloak. You can create the Realm and Client by executing the following CLI, which uses Keycloak Admin CLI (located in the bin folder).

#Authenticate with the Admin Server	
$ ./kcadm.sh config credentials --server http://localhost:8180/auth --realm master --user admin --password admin
#Create Realm Demo-Realm
$ ./kcadm.sh create realms -s realm=demo-realm -s enabled=true -o
#Create User customer-admin
$ ./kcadm.sh create users -r demo-realm -s username=customer-admin -s enabled=true
#Set customer-admin password
$ ./kcadm.sh set-password -r demo-realm --username customer-admin --new-password admin
#Create Client
$ ./kcadm.sh create clients -r demo-realm -s clientId=customer-manager-client -s bearerOnly="false"   -s "redirectUris=[\"http://localhost:8080/*\"]" -s enabled=true -s directAccessGrantsEnabled=true -s clientAuthenticatorType=client-secret -s secret=mysecret
#Create Role customer-manager
$ ./kcadm.sh create roles -r demo-realm -s name=customer-manager
#Assign Role to customer-admin 
$ ./kcadm.sh add-roles --uusername customer-admin --rolename customer-manager -r demo-realm

On the other hand, if you prefer, you can directly import the Realm definition which is available at: https://github.com/fmarchioni/mastertheboss/tree/master/keycloak/oauth2

You can check from Keycloak's Admin Console that the Realm and Client have been correctly defined:

keycloak oauth2 tutorial wildfly keycloak oauth2 tutorial wildfly keycloak oauth2 tutorial wildfly

As you can see, we have defined as redirect URI a generic localhost:8080 which will return, after checking the token, to the root page of a Web Server. In real life examples, you will want to redirect to a precise Web context.

Building the OAUTH Example

Our project, derived from Keycloak's quickstart, runs on Thorntail and uses the following fractions to compile and run the example:

<dependencyManagement>
	<dependencies>
	  <dependency>
	    <groupId>io.thorntail</groupId>
	    <artifactId>bom-all</artifactId>
	    <version>${version.thorntail}</version>
	    <scope>import</scope>
	    <type>pom</type>
	  </dependency>
	</dependencies>
</dependencyManagement>
<dependencies>
	<dependency>
	  <groupId>io.thorntail</groupId>
	  <artifactId>jaxrs</artifactId>
	</dependency>
	<dependency>
	  <groupId>io.thorntail</groupId>
	  <artifactId>keycloak</artifactId>
	</dependency>
</dependencies>

The main class, KeycloakSecurityContextFilter leverages a feature of JAX-RS which allows us to define a ContainerRequestFilter. This filter allows us to register a custom JAX-RS SecurityContext and access KeyCloak tokens:

package com.mastertheboss.keycloak;

import java.io.IOException;
import java.security.Principal;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;

import org.wildfly.swarm.keycloak.deployment.KeycloakSecurityContextAssociation;

@Provider
public class KeycloakSecurityContextFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        
        final SecurityContext securityContext = requestContext.getSecurityContext();

        final Principal kcPrincipal = () -> {
            return KeycloakSecurityContextAssociation.get().getToken().getPreferredUsername();
        };
        
        requestContext.setSecurityContext(new SecurityContext() {

            @Override
            public Principal getUserPrincipal() {
                return kcPrincipal;
            }

            @Override
            public boolean isUserInRole(String role) {
                return securityContext.isUserInRole(role);
            }

            @Override
            public boolean isSecure() {
                return securityContext.isSecure();
            }

            @Override
            public String getAuthenticationScheme() {
                return securityContext.getAuthenticationScheme();
            }            
        });
    }

}

As you can see, the class which does the trick is org.wildfly.swarm.keycloak.deployment.KeycloakSecurityContextAssociation that is available in Thorntail's keycloak fraction, and sets the principal name to the preferred user name.

We also need a basic Application class to activate JAX-RS and define the REST Context:

package com.mastertheboss.keycloak;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/app")
public class SecuredApplication extends Application {
}

And finally a REST Endpoint, which will return the UserPrincipal's name:

package com.mastertheboss.keycloak;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;

@Path("/secured")
public class SecuredResource {

    @GET
    public String hello(@Context SecurityContext sc) {
        return "Welcome " + sc.getUserPrincipal().getName() + "!";
    }

}

Adding the configuration

Finally, let's add some configuration items. Within the project-defaults.yml file, we specify the Security Constraints and the Auth-method which is KEYCLOAK:

swarm:
  deployment:
    example-keycloak.war:
      web:
        login-config:
          auth-method: KEYCLOAK
        security-constraints:
          - url-pattern: /app/secured
            methods: [GET]
            roles: [customer-manager]

Then, we need to provide to Thorntail the keycloak.json file with the Realm settings (that is available from the Client ->Installation Tab):

{
  "realm": "demo-realm",
  "auth-server-url": "http://localhost:8180/auth",
  "ssl-required": "external",
  "resource": "customer-manager-client",
  "credentials": {
    "secret": "mysecret"
  },
  "confidential-port": 0
}

Here is the final project structure:

├── src
│   └── main
│       ├── java
│       │   └── com
│       │       └── mastertheboss
│       │           └── keycloak
│       │               ├── KeycloakSecurityContextFilter.java
│       │               ├── SecuredApplication.java
│       │               └── SecuredResource.java
│       └── resources
│           ├── index.html
│           ├── keycloak.json
│           └── project-defaults.yml

Now you can start Thorntail passing as argument the keycloak.json, which will is available in the application classpath:

mvn clean install thorntail:run  -Dthorntail.keycloak.json.path=classpath:keycloak.json

Now request the token, passing as argument the Realm OpenID address, the user credentials along with the secret (mysecret):

export access_token=$(\
    curl -X POST http://localhost:8180/auth/realms/demo-realm/protocol/openid-connect/token \
    --user customer-manager-client:mysecret \
    -H 'content-type: application/x-www-form-urlencoded' \
    -d 'username=customer-admin&password=admin&grant_type=password' | jq --raw-output '.access_token' \
 )

You will see that the access_token now contains the Token we just got from Keycloak:

echo $access_token
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ5cE9FQlZQZEhEUjJuM3dWd3N6QTZLWVRBT2ptMWd0MWt4dU1XdEhfZ2VBIn0.eyJqdGkiOiI0M2Y5ZjA3Zi1iZGY1LTRkOWYtODc3MC04OTgwZDA3YmNlOGIiLCJleHAiOjE1NzIzMzkxNzIsIm5iZiI6MCwiaWF0IjoxNTcyMzM4ODcyLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgxODAvYXV0aC9yZWFsbXMvZGVtby1yZWFsbSIsImF1ZCI6ImFjY291bnQi

Now access the REST Endpoint using the Token in the Header of the Request

curl -H "Authorization: Bearer $access_token" http://localhost:8080/app/secured

As return, you should see the output: "Welcome customer-admin!"

Conclusion

In this turorial we have covered how to create a JAX-RS Application protected with OAUTH2 and Thorntail. In the next one, we will show how to use Quarkus to achieve the same result!

Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/keycloak/oauth2

0
0
0
s2sdefault