<<TableOfContents(2)>>
= keycloak =
Open Source Identity and Access Management.
 * https://www.keycloak.org/

== OIDC ==
 * https://www.scottbrady91.com/OpenID-Connect/OpenID-Connect-Overview
OpenID Connect (OIDC) provides a simple identity layer on top of the OAuth 2.0 protocol, enabling Single Sign-On (SSO) and API access in one round trip. It brings the missing user authentication story and identity layer to OAuth.

== Steps setup realm ==
{{{#!highlight bash
cd /tmp
wget https://github.com/keycloak/keycloak/releases/download/14.0.0/keycloak-14.0.0.zip
unzip -t keycloak-14.0.0.zip
unzip keycloak-14.0.0.zip
cd ~/tmp/keycloak-14.0.0/bin
sh standalone.sh 
http://localhost:8080/auth
}}}

=== Create admin user ===
 * http://localhost:8080/auth
 * Administration Console
 * User: admin 
 * Password: admin 
 * Password confirmation: admin 
 * Click on Create

=== Create realm ===
 * http://localhost:8080/auth/admin/master/console/#/realms/master
 * login with admin:admin
 * http://localhost:8080/auth/admin/master/console/#/create/realm
 * Name: MyRealm
 * Enabled: On
 * Click on Create

=== Add user myuser ===
 * http://localhost:8080/auth/admin/master/console/#/realms/MyRealm
 * Go to Users
 * Click on Add user
 * Username: myuser
 * User enabled: ON
 * Save 

=== Add user mysubtaskuser ===
 * http://localhost:8080/auth/admin/master/console/#/realms/MyRealm
 * Go to Users
 * Click on Add user
 * Username: mysubtaskuser
 * User enabled: ON
 * Save 

=== Set user password myuser ===
 * http://localhost:8080/auth/admin/master/console/#/realms/MyRealm/users
 * Select user myuser
 * Select credentials tab
 * Password: mypwd 
 * Password confirmation: mypwd
 * Temporary: off 
 * Click on "Set Password" 

=== Set user password mysubtaskuser ===
 * http://localhost:8080/auth/admin/master/console/#/realms/MyRealm/users
 * Select user mysubtaskuser
 * Select credentials tab
 * Password: mypwd2 
 * Password confirmation: mypwd2
 * Temporary: off 
 * Click on "Set Password" 

=== Create role USER ===
 * http://localhost:8080/auth/admin/master/console/#/create/role/MyRealm
 * Add role USER to MyRealm
 * Role name: USER
 * Click on Save 

=== Create role USERSUBTASK ===
 * http://localhost:8080/auth/admin/master/console/#/create/role/MyRealm
 * Add role USERSUBTASK to MyRealm
 * Role name: USERSUBTASK
 * Click on Save 


=== Associate role to user myuser ===
 * http://localhost:8080/auth/admin/master/console/#/realms/MyRealm/users
 * select user myuser 
 * select tab Role mappings
 * select USER role and click on add selected

=== Associate role to user mysubtaskuser ===
 * http://localhost:8080/auth/admin/master/console/#/realms/MyRealm/users
 * select user mysubtaskuser 
 * select tab Role mappings
 * select USERSUBTASK role and click on add selected


=== Create keycloak client ===
 * http://localhost:8080/auth/admin/master/console/#/realms/MyRealm/clients
 * click on create 
 * client id: curl_confidential
 * client protocol: openid-connect
 * root url: http://localhost:8080
 * Click on save 
 * Clients Curl_confidential  settings: 
 * access-type: confidential
 * Should appear tab Credentials 
 * Client authenticator: Client ID and secret
 * Click on "Regenerate Secret"
 * # 3a862f1b-6687-4f7a-8e04-be494fca99e0
 * Clients Curl_confidential Mappers Add builtin "realm roles", "groups"
 * add selected 
 * For each map add "Add to userinfo"
 * Clients Curl_confidential Scope, 
 * select full scope allowed: ON

=== client data ===
 * realm: MyRealm
 * user pwd: myuser mypwd
 * client id: curl_confidential
 * protocol: openid-connect
 * Curl_confidential  settings: 
 * access-type confidential
 * valid redirect url http://localhost:8080
 * tab credentials: regenerate secret 3a862f1b-6687-4f7a-8e04-be494fca99e0
 
=== Signout ===
 * http://localhost:8080/auth/realms/MyRealm/account/

=== cUrl calls to test keycloak ===
{{{#!highlight bash
ACCESS_TOKEN=$(curl -d 'client_id=curl_confidential' -d 'client_secret=3a862f1b-6687-4f7a-8e04-be494fca99e0' -d 'username=myuser' -d 'password=mypwd' -d 'grant_type=password' 'http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/token' | json_reformat | jq -r '.access_token')
echo $ACCESS_TOKEN

curl -X POST -d "access_token=$ACCESS_TOKEN" http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/userinfo | json_reformat

curl -X GET -d "access_token=$ACCESS_TOKEN" http://localhost:8080/auth/realms/MyRealm/.well-known/openid-configuration | json_reformat 

}}}

{{{#!highlight bash
CLIENT_ID="curl_confidential"
CLIENT_SECRET="3a862f1b-6687-4f7a-8e04-be494fca99e0"
TOKEN=$(curl -d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET" -d 'username=myuser' -d 'password=mypwd' -d 'grant_type=password' 'http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/token' | json_reformat)

ACCESS_TOKEN=$(echo $TOKEN | jq -r '.access_token')
REFRESH_TOKEN=$(echo $TOKEN | jq -r '.refresh_token')

curl -X POST -d "access_token=$ACCESS_TOKEN" http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/userinfo

curl -vvv -d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET" -d "refresh_token=$REFRESH_TOKEN" -H "Bearer: $ACCESS_TOKEN" 'http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/logout'

curl -X POST -d "access_token=$ACCESS_TOKEN" http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/userinfo
#
}}}

== Setup keycloak as service in Raspberry pi ==
 * /etc/init.d/keycloak
{{{#!highlight bash 
#! /bin/sh
### BEGIN INIT INFO
# Provides:          keycloak
# Default-Start:     2 3 4 5
# Default-Stop:
# Short-Description: keycloak
# Description:       keycloak
### END INIT INFO
#
# Some things that run always
touch /var/lock/keycloak
# Carry out specific functions when asked to by the system
case "$1" in
  start)
    echo "Starting script keycloak "
    su pi -c "nohup /home/pi/keycloak-14.0.0/bin/standalone.sh &"
    ;;
  stop)
    echo "Stopping script keycloak"
    kill $(ps uax | grep keycloak | grep java | awk '//{print $2}')
    ;;
  status)
    echo "keycloak PID: $(ps uax | grep keycloak | grep java | awk '//{print $2}')"
    ;;
  *)
    echo "Usage: /etc/init.d/keycloak {start|stop|status}"
    exit 1
    ;;
esac

exit 0
}}}

== Keycloak 21.1.1 + SpringBoot 3.1 + Spring Security + AspectJ (AOP) ==
{{{#!highlight sh
cd ~
wget https://github.com/keycloak/keycloak/releases/download/21.1.1/keycloak-21.1.1.zip
unzip keycloak-21.1.1.zip
cd keycloak-21.1.1/bin
bash kc.sh start 
bash kc.sh show-config
keytool -genkeypair -alias debian -keyalg RSA -keysize 2048 -validity 365 -keystore server.keystore -dname "cn=Server Administrator,o=Keycloak,c=PT" -keypass secret -storepass secret
cp server.keystore ../conf
./kc.sh start-dev --hostname=debian --https-key-store-password=secret
#Sign in to your account 
#Master, Create realm, MyRealm , Create 
#Users, Create new user, myuser, create 
#select user, credentials, set password,  mypwd mypwd, temporary off , save, save password
#Realm roles, create role, USER, save 
#Users, myuser, role mapping, assign role USER 
#signout
#http://debian:8080/admin/master/console/#/MyRealm
#My realm, clients, create client 
#  client type: openid connect 
#  client id:  curl_confidential
#  next 
#  client authentication: on 
#  standard flow, direct access grants 
#  next 
#  valid redirect url http://localhost:8080
#  save 
#  tab credentials of curl_confidential 
#  client secret regenerate -> Cymorm3jWN2b5z49dNASwPWwgY5zAsdV
  
curl -d 'client_id=curl_confidential' -d 'client_secret=Cymorm3jWN2b5z49dNASwPWwgY5zAsdV' -d 'usr' -d 'password=mypwd' -d 'grant_type=password' 'http://localhost:8080/realms/MyRealm/protocol/openid-connect/token'
sudo apt install jq 

TOKEN=$(curl -d 'client_id=curl_confidential' -d 'client_secret=Cymorm3jWN2b5z49dNASwPWwgY5zAsdV' -d 'username=myuser' -d 'password=mypwd' -d 'grant_type=password' 'http://localhost:8080/realms/MyRealm/protocol/openid-connect/token')
echo $TOKEN 

mkdir -p ~/Documents/test-springboot-keycloak/proj
cd ~/Documents/test-springboot-keycloak/proj
touch pom.xml
touch src/main/java/com/example/demo/UserRole.java
touch src/main/java/com/example/demo/RolesAspect.java
touch src/main/java/com/example/demo/SecurityConfiguration.java
touch src/main/java/com/example/demo/AdminRole.java
touch src/main/java/com/example/demo/DemoApplication.java
touch src/main/resources/application.properties
}}}

=== pom.xml ===
{{{#!highlight xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>3.1.0</version>
                <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>demo</name>
        <description>Demo project for Spring Boot</description>
        <properties>
                <java.version>17</java.version>
        </properties>
        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-security</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-aop</artifactId>
                </dependency>                
        </dependencies>
        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>
</project>
}}}

=== src/main/java/com/example/demo/UserRole.java ===
{{{#!highlight java
package com.example.demo;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

/**
 * Annotation to identify code associated with USER role
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UserRole {
}
}}}

=== src/main/java/com/example/demo/RolesAspect.java ===
{{{#!highlight java
package com.example.demo;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;

@Aspect
@Component
public class RolesAspect {

    private boolean hasRole(String role, ProceedingJoinPoint joinPoint) {
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length >= 1) {
            Object authArg = joinPoint.getArgs()[0];
            if (Authentication.class.isAssignableFrom(authArg.getClass())) {
                Jwt jwt = (Jwt) ((Authentication) authArg).getCredentials();
                Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access");
                List<String> roles = (ArrayList<String>) realmAccess.get("roles");
                if (roles.contains(role)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Intercept stuff annotated with UserRole annotation
     * 
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(UserRole)")
    public Object interceptUserRoleAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {

        if (hasRole("USER", joinPoint)) {
            return joinPoint.proceed();
        } else {
            System.out.println("USER role not found");
            return null;
        }
    }

    /**
     * Intercept stuff annotated with AdminRole annotation
     * 
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(AdminRole)")
    public Object interceptAdminRoleAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {

        if (hasRole("ADMIN", joinPoint)) {
            return joinPoint.proceed();
        } else {
            System.out.println("ADMIN role not found");
            return null;
        }
    }
}
}}}

=== src/main/java/com/example/demo/SecurityConfiguration.java ===
{{{#!highlight java
package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfiguration {

        @Bean
        protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                return http.oauth2ResourceServer(
                                (oauth2ResourceServer) -> {
                                        oauth2ResourceServer.jwt((jwt) -> {
                                                jwt.decoder(null);
                                        });
                                }).build();
        }

        @Bean
        public WebSecurityCustomizer webSecurityCustomizer() {
                return (web) -> web.ignoring().requestMatchers("/otherhello**").requestMatchers("/static**");
        }
}
}}}

=== src/main/java/com/example/demo/AdminRole.java ===
{{{#!highlight java
package com.example.demo;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

/**
 * Annotation to identify code associated with ADMIN role
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminRole {
}
}}}

=== src/main/java/com/example/demo/DemoApplication.java ===
{{{#!highlight java
package com.example.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;

@RestController
@SpringBootApplication
public class DemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

  @GetMapping("/otherhello")
  public String otherHello() {
    return "other hello";
  }

  @GetMapping("/hello")
  @UserRole
  public String hello(Authentication authentication) {
    String authorities = "";
    for (int authorityIndex = 0; authorityIndex < authentication.getAuthorities().size(); authorityIndex++) {
      GrantedAuthority ga = (GrantedAuthority) authentication.getAuthorities().toArray()[authorityIndex];
      authorities += ga.getAuthority() + " ";
    }

    Jwt jwt = (Jwt) authentication.getCredentials();

    Object[] keys = jwt.getClaims().keySet().toArray();
    String allkeys = "";

    String preferredUsername = jwt.getClaim("preferred_username").toString();
    Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access");
    List<String> roles = (ArrayList<String>) realmAccess.get("roles");
    System.out.println("Contains USER " + roles.contains("USER"));

    for (int j = 0; j < keys.length; j++) {
      allkeys = allkeys + " " + (String) keys[j] + ":" + jwt.getClaims().get(keys[j]).toString() + " ";
    }

    return "I am authenticated with user " + authentication.getName() + " Authorities: " + authorities + " Details: "
        + authentication.getDetails().toString() + " allKeys: " + allkeys + " ... " + preferredUsername + " ... "
        + roles;
  }

  @GetMapping("/helloAdmin")
  @AdminRole
  public String helloAdmin(Authentication authentication) {
    return "Hello ADMIN";
  }

  @GetMapping("/helloUser/{text}")
  @UserRole
  public String helloUser(Authentication authentication, @PathVariable String text) {
    return "Hello USER " + text;
  }
}
}}}

=== src/main/resources/application.properties ===
{{{#!highlight sh
server.port=8081
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://debian:8080/realms/MyRealm
#logging.level.root=DEBUG
logging.level.root=INFO
logging.file=/tmp/testout.log
}}}

== Keycloak 26.3.0 + SpringBoot 3.5.3 + Spring Security + AspectJ (AOP) ==
{{{#!highlight sh
cd ~
sudo apt install jq 
rm -rf keycloak-26.3.0
rm keycloak-26.3.0.tar.gz
wget https://github.com/keycloak/keycloak/releases/download/26.3.0/keycloak-26.3.0.tar.gz
tar xvzf keycloak-26.3.0.tar.gz
cd keycloak-26.3.0/conf/
sed -i 's/#hostname/hostname/g' keycloak.conf 
sed -i 's/myhostname/debian/g' keycloak.conf 
cd ../bin
# create keycloak server keystore for hostname debian
keytool -genkeypair -alias debian -keyalg RSA -keysize 2048 -validity 365 -keystore server.keystore -dname "cn=debian,o=Keycloak,c=PT" -keypass secret -storepass secret
cp server.keystore ../conf

./kc.sh start --verbose --https-key-store-password=secret
}}}


=== Create a temporary administrative user ===
 * Go to https://debian:8443/
 * admin, 12345678, 12345678, create user
 * Open administration console

=== Create realm ===
 * https://debian:8443/admin/master/console/#/master/realms
 * Click Create realm
 * Realm name: TestRealm
 * enabled: true
 * click on create 

=== Create user ===
 * https://debian:8443/admin/master/console/#/TestRealm/users
 * Create new user, 
 * username: myuser
 * click create 
 * select user myuser, 
 * email verified: true
 * tab credentials
 * click set password 
 * password mypwd 
 * password confirmation: mypwd
 * temporary off 
 * save
 * save password

=== Create roles ===
 * https://debian:8443/admin/master/console/#/TestRealm/roles
 * click on create role
 * Role name: USER
 * save 
 * click on create role
 * Role name: ADMIN
 * save 

=== Map role to user ===
 * https://debian:8443/admin/master/console/#/TestRealm/users
 * Select myuser
 * tab role mapping
 * assign realm role USER 
 * click on signout

=== Create client ===
 * https://debian:8443/admin/master/console/#/TestRealm/clients
 * clients list
 * click create client 
 * Client type: openid connect 
 * client id: oidc_test_client
 * next 
 * client authentication: on 
 * authentication flow: standard flow, direct access grants 
 * next 
 * valid redirect url http://localhost:8080
 * save 
 * tab credentials of oidc_test_client 
 * view client secret: F5yuRxN1kbMxI6oVISFlART276Ddi8vi

=== Disable required actions for realm ===
 * https://debian:8443/admin/master/console/#/TestRealm/authentication/required-actions
 * set all off 

=== Get token after authentication ===
{{{#!highlight sh
CLIENT_ID=oidc_test_client 
CLIENT_SECRET=F5yuRxN1kbMxI6oVISFlART276Ddi8vi
REALM=TestRealm

curl --noproxy '*' -k -d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET" -d 'username=myuser' -d 'password=mypwd' -d 'grant_type=password' "https://debian:8443/realms/$REALM/protocol/openid-connect/token"

TOKEN=$(curl --noproxy '*' -k -d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET" -d 'username=myuser' -d 'password=mypwd' -d 'grant_type=password' "https://debian:8443/realms/$REALM/protocol/openid-connect/token")
echo $TOKEN 
}}}

=== Spring boot test project ===
{{{#!highlight sh
rm -rf ~/Documents/test-springboot-keycloak
mkdir -p ~/Documents/test-springboot-keycloak/src/main/java/com/example/demo/
mkdir -p ~/Documents/test-springboot-keycloak/src/main/resources
cd ~/Documents/test-springboot-keycloak
touch pom.xml
touch src/main/java/com/example/demo/UserRole.java
touch src/main/java/com/example/demo/RolesAspect.java
touch src/main/java/com/example/demo/SecurityConfiguration.java
touch src/main/java/com/example/demo/AdminRole.java
touch src/main/java/com/example/demo/DemoApplication.java
touch src/main/resources/application.properties
}}}

==== pom.xml ====
{{{#!highlight xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.3</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>demo</artifactId>
  <version>0.0.2-SNAPSHOT</version>
  <name>demo</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>21</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency> 
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>
}}}

==== src/main/resources/application.properties ====
{{{#!highlight sh
server.port=8081
server.ssl.enabled=false
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://debian:8443/realms/TestRealm
#logging.level.root=DEBUG
logging.level.root=INFO
logging.file=/tmp/testout.log
}}}

==== src/main/java/com/example/demo/AdminRole.java ====
{{{#!highlight java
package com.example.demo;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

/**
 * Annotation to identify code associated with ADMIN role
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminRole {
}
}}}

==== src/main/java/com/example/demo/DemoApplication.java ====
{{{#!highlight java
package com.example.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;

@RestController
@SpringBootApplication
public class DemoApplication {

  public static void main(String[] args) {
    System.setProperty("javax.net.ssl.trustStore","truststore.p12");
    System.setProperty("javax.net.ssl.trustStorePassword","secret");
    System.setProperty("javax.net.ssl.trustStoreType","PKCS12");
    SpringApplication.run(DemoApplication.class, args);
  }

  @GetMapping("/otherhello")
  public String otherHello() {
    return "other hello";
  }

  @GetMapping("/hello")
  @UserRole
  public String hello(Authentication authentication) {
    String authorities = "";
    for (int authorityIndex = 0; authorityIndex < authentication.getAuthorities().size(); authorityIndex++) {
      GrantedAuthority ga = (GrantedAuthority) authentication.getAuthorities().toArray()[authorityIndex];
      authorities += ga.getAuthority() + " ";
    }

    Jwt jwt = (Jwt) authentication.getCredentials();

    Object[] keys = jwt.getClaims().keySet().toArray();
    String allkeys = "";

    String preferredUsername = jwt.getClaim("preferred_username").toString();
    Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access");
    List<String> roles = (ArrayList<String>) realmAccess.get("roles");
    System.out.println("Contains USER " + roles.contains("USER"));

    for (int j = 0; j < keys.length; j++) {
      allkeys = allkeys + " " + (String) keys[j] + ":" + jwt.getClaims().get(keys[j]).toString() + " ";
    }

    return "I am authenticated with user " + authentication.getName() + " Authorities: " + authorities + " Details: "
        + authentication.getDetails().toString() + " allKeys: " + allkeys + " ... " + preferredUsername + " ... "
        + roles;
  }

  @GetMapping("/helloAdmin")
  @AdminRole
  public String helloAdmin(Authentication authentication) {
    return "Hello ADMIN";
  }

  @GetMapping("/helloUser/{text}")
  @UserRole
  public String helloUser(Authentication authentication, @PathVariable String text) {
    return "Hello USER " + text;
  }

}
}}}


==== src/main/java/com/example/demo/RolesAspect.java ====
{{{#!highlight java
package com.example.demo;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;

@Aspect
@Component
public class RolesAspect {

    private boolean hasRole(String role, ProceedingJoinPoint joinPoint) {
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length >= 1) {
            Object authArg = joinPoint.getArgs()[0];
            if (authArg != null && Authentication.class.isAssignableFrom(authArg.getClass())) {
                Jwt jwt = (Jwt) ((Authentication) authArg).getCredentials();
                Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access");
                List<String> roles = (ArrayList<String>) realmAccess.get("roles");
                if (roles.contains(role)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Intercept stuff annotated with UserRole annotation
     * 
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(UserRole)")
    public Object interceptUserRoleAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {

        if (hasRole("USER", joinPoint)) {
            return joinPoint.proceed();
        } else {
            System.out.println("USER role not found");
            return null;
        }
    }

    /**
     * Intercept stuff annotated with AdminRole annotation
     * 
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(AdminRole)")
    public Object interceptAdminRoleAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {

        if (hasRole("ADMIN", joinPoint)) {
            return joinPoint.proceed();
        } else {
            System.out.println("ADMIN role not found");
            return null;
        }
    }
}
}}}

==== src/main/java/com/example/demo/SecurityConfiguration.java ====
{{{#!highlight java
package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfiguration {

        @Bean
        protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                return http.oauth2ResourceServer(
                                (oauth2ResourceServer) -> {
                                        oauth2ResourceServer.jwt((jwt) -> {
                                                jwt.decoder(null);
                                        });
                                }).build();
        }

        @Bean
        public WebSecurityCustomizer webSecurityCustomizer() {
                return (web) -> web.ignoring().requestMatchers("/otherhello**").requestMatchers("/static**");
        }
}
}}}

==== src/main/java/com/example/demo/UserRole.java ====
{{{#!highlight java
package com.example.demo;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

/**
 * Annotation to identify code associated with USER role
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UserRole {
}
}}}

==== get keycloak server cert debian.pem ====
{{{#!highlight sh
# debian.pem
rm src/main/resources/truststore.p12

openssl s_client -showcerts -connect debian:8443 </dev/null > debian.pem
nano debian.pem # remove lines before BEGIN CERTIFICATE and after END CERTIFICATE
# pwd secret
keytool -import -alias debian -keystore src/main/resources/truststore.p12 -file debian.pem -storepass secret

}}}

==== Build and run ====
{{{#!highlight sh
mvn clean install 

cd target
cp ../src/main/resources/truststore.p12 .
java -jar demo-0.0.2-SNAPSHOT.jar
}}}

==== Call secure endpoint ====
{{{#!highlight sh
CLIENT_ID=oidc_test_client 
CLIENT_SECRET=F5yuRxN1kbMxI6oVISFlART276Ddi8vi
REALM=TestRealm
TOKEN=$(curl -k -d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET" -d 'username=myuser' -d 'password=mypwd' -d 'grant_type=password' "https://localhost:8443/realms/$REALM/protocol/openid-connect/token")
echo $TOKEN 
ACCESS_TOKEN=$(echo $TOKEN | jq -r '.access_token')
echo $ACCESS_TOKEN
curl -X GET http://localhost:8081/hello  --header "Authorization: Bearer $ACCESS_TOKEN"
# insecure endpoint
curl localhost:8081/otherhello
}}}