| 
  
   Size: 1178 
  
  Comment:  
 | 
  
   Size: 31236 
  
  Comment:  
 | 
| Deletions are marked like this. | Additions are marked like this. | 
| Line 1: | Line 1: | 
| <<TableOfContents(2)>> | |
| Line 5: | Line 6: | 
| == 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.  | 
|
| Line 7: | Line 12: | 
| wget https://downloads.jboss.org/keycloak/10.0.2/keycloak-10.0.2.zip unzip -t keycloak-10.0.2.zip cd ~/keycloak-10.0.2/bin  | 
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  | 
| Line 12: | Line 19: | 
| # admin admin admin create  # http://localhost:8080/auth/admin/master/console/#/realms/master # Master, add realm, MyRealm , create # Users, add user, myuser # select user, credentials, mypwd mypwd, temporary off # Add role USER to MyRealm # Make user myuser have role USER # signout # http://localhost:8080/auth/realms/MyRealm/account/ # 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 6dfe5f84-d115-4d3e-8a56-a0fcf5b2f13e curl -d 'client_id=curl_confidential' -d 'client_secret=6dfe5f84-d115-4d3e-8a56-a0fcf5b2f13e' -d 'username=myuser' -d 'password=mypwd' -d 'grant_type=password' 'http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/token' }}}  | 
}}} === 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 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/bin sh kc.sh start sh kc.sh show-config rm server.keystore 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://localhost:8443/ * admin, 12345678, 12345678 * Open administration console === Create realm === * https://debian:8443/admin/master/console/#/master/realms * Click Create realm * Realm name: TestRealm * click on create === Create user === * https://debian:8443/admin/master/console/#/TestRealm/users * Create new user, * username: myuser * click create * select user myuser, * 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 -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" 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 }}} === Spring boot test project === {{{#!highlight sh mkdir -p ~/Documents/test-springboot-keycloak 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 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 # pwd secret keytool -import -alias debian -keystore src/main/resources/truststore.p12 -file ~/Downloads/debian.pem }}} ==== 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 }}}  | 
Contents
keycloak
Open Source Identity and Access Management.
OIDC
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
Create admin user
- 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
cUrl calls to test keycloak
   1 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')
   2 echo $ACCESS_TOKEN
   3 
   4 curl -X POST -d "access_token=$ACCESS_TOKEN" http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/userinfo | json_reformat
   5 
   6 curl -X GET -d "access_token=$ACCESS_TOKEN" http://localhost:8080/auth/realms/MyRealm/.well-known/openid-configuration | json_reformat 
   1 CLIENT_ID="curl_confidential"
   2 CLIENT_SECRET="3a862f1b-6687-4f7a-8e04-be494fca99e0"
   3 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)
   4 
   5 ACCESS_TOKEN=$(echo $TOKEN | jq -r '.access_token')
   6 REFRESH_TOKEN=$(echo $TOKEN | jq -r '.refresh_token')
   7 
   8 curl -X POST -d "access_token=$ACCESS_TOKEN" http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/userinfo
   9 
  10 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'
  11 
  12 curl -X POST -d "access_token=$ACCESS_TOKEN" http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/userinfo
  13 #
  14 
Setup keycloak as service in Raspberry pi
- /etc/init.d/keycloak
 
   1 #! /bin/sh
   2 ### BEGIN INIT INFO
   3 # Provides:          keycloak
   4 # Default-Start:     2 3 4 5
   5 # Default-Stop:
   6 # Short-Description: keycloak
   7 # Description:       keycloak
   8 ### END INIT INFO
   9 #
  10 # Some things that run always
  11 touch /var/lock/keycloak
  12 # Carry out specific functions when asked to by the system
  13 case "$1" in
  14   start)
  15     echo "Starting script keycloak "
  16     su pi -c "nohup /home/pi/keycloak-14.0.0/bin/standalone.sh &"
  17     ;;
  18   stop)
  19     echo "Stopping script keycloak"
  20     kill $(ps uax | grep keycloak | grep java | awk '//{print $2}')
  21     ;;
  22   status)
  23     echo "keycloak PID: $(ps uax | grep keycloak | grep java | awk '//{print $2}')"
  24     ;;
  25   *)
  26     echo "Usage: /etc/init.d/keycloak {start|stop|status}"
  27     exit 1
  28     ;;
  29 esac
  30 
  31 exit 0
Keycloak 21.1.1 + SpringBoot 3.1 + Spring Security + AspectJ (AOP)
   1 cd ~
   2 wget https://github.com/keycloak/keycloak/releases/download/21.1.1/keycloak-21.1.1.zip
   3 unzip keycloak-21.1.1.zip
   4 cd keycloak-21.1.1/bin
   5 bash kc.sh start 
   6 bash kc.sh show-config
   7 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
   8 cp server.keystore ../conf
   9 ./kc.sh start-dev --hostname=debian --https-key-store-password=secret
  10 #Sign in to your account 
  11 #Master, Create realm, MyRealm , Create 
  12 #Users, Create new user, myuser, create 
  13 #select user, credentials, set password,  mypwd mypwd, temporary off , save, save password
  14 #Realm roles, create role, USER, save 
  15 #Users, myuser, role mapping, assign role USER 
  16 #signout
  17 #http://debian:8080/admin/master/console/#/MyRealm
  18 #My realm, clients, create client 
  19 #  client type: openid connect 
  20 #  client id:  curl_confidential
  21 #  next 
  22 #  client authentication: on 
  23 #  standard flow, direct access grants 
  24 #  next 
  25 #  valid redirect url http://localhost:8080
  26 #  save 
  27 #  tab credentials of curl_confidential 
  28 #  client secret regenerate -> Cymorm3jWN2b5z49dNASwPWwgY5zAsdV
  29   
  30 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'
  31 sudo apt install jq 
  32 
  33 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')
  34 echo $TOKEN 
  35 
  36 mkdir -p ~/Documents/test-springboot-keycloak/proj
  37 cd ~/Documents/test-springboot-keycloak/proj
  38 touch pom.xml
  39 touch src/main/java/com/example/demo/UserRole.java
  40 touch src/main/java/com/example/demo/RolesAspect.java
  41 touch src/main/java/com/example/demo/SecurityConfiguration.java
  42 touch src/main/java/com/example/demo/AdminRole.java
  43 touch src/main/java/com/example/demo/DemoApplication.java
  44 touch src/main/resources/application.properties
pom.xml
   1 <?xml version="1.0" encoding="UTF-8"?>
   2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   3         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   4         <modelVersion>4.0.0</modelVersion>
   5         <parent>
   6                 <groupId>org.springframework.boot</groupId>
   7                 <artifactId>spring-boot-starter-parent</artifactId>
   8                 <version>3.1.0</version>
   9                 <relativePath/> <!-- lookup parent from repository -->
  10         </parent>
  11         <groupId>com.example</groupId>
  12         <artifactId>demo</artifactId>
  13         <version>0.0.1-SNAPSHOT</version>
  14         <name>demo</name>
  15         <description>Demo project for Spring Boot</description>
  16         <properties>
  17                 <java.version>17</java.version>
  18         </properties>
  19         <dependencies>
  20                 <dependency>
  21                         <groupId>org.springframework.boot</groupId>
  22                         <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
  23                 </dependency>
  24                 <dependency>
  25                         <groupId>org.springframework.boot</groupId>
  26                         <artifactId>spring-boot-starter-security</artifactId>
  27                 </dependency>
  28                 <dependency>
  29                         <groupId>org.springframework.boot</groupId>
  30                         <artifactId>spring-boot-starter-web</artifactId>
  31                 </dependency>
  32                 <dependency>
  33                         <groupId>org.springframework.boot</groupId>
  34                         <artifactId>spring-boot-starter-aop</artifactId>
  35                 </dependency>                
  36         </dependencies>
  37         <build>
  38                 <plugins>
  39                         <plugin>
  40                                 <groupId>org.springframework.boot</groupId>
  41                                 <artifactId>spring-boot-maven-plugin</artifactId>
  42                         </plugin>
  43                 </plugins>
  44         </build>
  45 </project>
src/main/java/com/example/demo/UserRole.java
   1 package com.example.demo;
   2 
   3 import java.lang.annotation.Retention;
   4 import java.lang.annotation.Target;
   5 import java.lang.annotation.ElementType;
   6 import java.lang.annotation.RetentionPolicy;
   7 
   8 /**
   9  * Annotation to identify code associated with USER role
  10  * 
  11  */
  12 @Retention(RetentionPolicy.RUNTIME)
  13 @Target(ElementType.METHOD)
  14 public @interface UserRole {
  15 }
src/main/java/com/example/demo/RolesAspect.java
   1 package com.example.demo;
   2 
   3 import java.util.List;
   4 import java.util.ArrayList;
   5 import java.util.Map;
   6 
   7 import org.aspectj.lang.ProceedingJoinPoint;
   8 import org.aspectj.lang.annotation.Around;
   9 import org.aspectj.lang.annotation.Aspect;
  10 import org.springframework.stereotype.Component;
  11 import org.springframework.security.core.Authentication;
  12 import org.springframework.security.oauth2.jwt.Jwt;
  13 
  14 @Aspect
  15 @Component
  16 public class RolesAspect {
  17 
  18     private boolean hasRole(String role, ProceedingJoinPoint joinPoint) {
  19         if (joinPoint.getArgs() != null && joinPoint.getArgs().length >= 1) {
  20             Object authArg = joinPoint.getArgs()[0];
  21             if (Authentication.class.isAssignableFrom(authArg.getClass())) {
  22                 Jwt jwt = (Jwt) ((Authentication) authArg).getCredentials();
  23                 Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access");
  24                 List<String> roles = (ArrayList<String>) realmAccess.get("roles");
  25                 if (roles.contains(role)) {
  26                     return true;
  27                 }
  28             }
  29         }
  30 
  31         return false;
  32     }
  33 
  34     /**
  35      * Intercept stuff annotated with UserRole annotation
  36      * 
  37      * @param joinPoint
  38      * @return
  39      * @throws Throwable
  40      */
  41     @Around("@annotation(UserRole)")
  42     public Object interceptUserRoleAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
  43 
  44         if (hasRole("USER", joinPoint)) {
  45             return joinPoint.proceed();
  46         } else {
  47             System.out.println("USER role not found");
  48             return null;
  49         }
  50     }
  51 
  52     /**
  53      * Intercept stuff annotated with AdminRole annotation
  54      * 
  55      * @param joinPoint
  56      * @return
  57      * @throws Throwable
  58      */
  59     @Around("@annotation(AdminRole)")
  60     public Object interceptAdminRoleAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
  61 
  62         if (hasRole("ADMIN", joinPoint)) {
  63             return joinPoint.proceed();
  64         } else {
  65             System.out.println("ADMIN role not found");
  66             return null;
  67         }
  68     }
  69 }
src/main/java/com/example/demo/SecurityConfiguration.java
   1 package com.example.demo;
   2 
   3 import org.springframework.context.annotation.Bean;
   4 import org.springframework.context.annotation.Configuration;
   5 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
   6 import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
   7 import org.springframework.security.web.SecurityFilterChain;
   8 
   9 @Configuration
  10 public class SecurityConfiguration {
  11 
  12         @Bean
  13         protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  14                 return http.oauth2ResourceServer(
  15                                 (oauth2ResourceServer) -> {
  16                                         oauth2ResourceServer.jwt((jwt) -> {
  17                                                 jwt.decoder(null);
  18                                         });
  19                                 }).build();
  20         }
  21 
  22         @Bean
  23         public WebSecurityCustomizer webSecurityCustomizer() {
  24                 return (web) -> web.ignoring().requestMatchers("/otherhello**").requestMatchers("/static**");
  25         }
  26 }
src/main/java/com/example/demo/AdminRole.java
   1 package com.example.demo;
   2 
   3 import java.lang.annotation.Retention;
   4 import java.lang.annotation.Target;
   5 import java.lang.annotation.ElementType;
   6 import java.lang.annotation.RetentionPolicy;
   7 
   8 /**
   9  * Annotation to identify code associated with ADMIN role
  10  * 
  11  */
  12 @Retention(RetentionPolicy.RUNTIME)
  13 @Target(ElementType.METHOD)
  14 public @interface AdminRole {
  15 }
src/main/java/com/example/demo/DemoApplication.java
   1 package com.example.demo;
   2 
   3 import java.util.ArrayList;
   4 import java.util.List;
   5 import java.util.Map;
   6 
   7 import org.springframework.boot.SpringApplication;
   8 import org.springframework.boot.autoconfigure.SpringBootApplication;
   9 import org.springframework.web.bind.annotation.GetMapping;
  10 import org.springframework.web.bind.annotation.PathVariable;
  11 import org.springframework.web.bind.annotation.RestController;
  12 import org.springframework.security.core.Authentication;
  13 import org.springframework.security.core.GrantedAuthority;
  14 import org.springframework.security.oauth2.jwt.Jwt;
  15 
  16 @RestController
  17 @SpringBootApplication
  18 public class DemoApplication {
  19 
  20   public static void main(String[] args) {
  21     SpringApplication.run(DemoApplication.class, args);
  22   }
  23 
  24   @GetMapping("/otherhello")
  25   public String otherHello() {
  26     return "other hello";
  27   }
  28 
  29   @GetMapping("/hello")
  30   @UserRole
  31   public String hello(Authentication authentication) {
  32     String authorities = "";
  33     for (int authorityIndex = 0; authorityIndex < authentication.getAuthorities().size(); authorityIndex++) {
  34       GrantedAuthority ga = (GrantedAuthority) authentication.getAuthorities().toArray()[authorityIndex];
  35       authorities += ga.getAuthority() + " ";
  36     }
  37 
  38     Jwt jwt = (Jwt) authentication.getCredentials();
  39 
  40     Object[] keys = jwt.getClaims().keySet().toArray();
  41     String allkeys = "";
  42 
  43     String preferredUsername = jwt.getClaim("preferred_username").toString();
  44     Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access");
  45     List<String> roles = (ArrayList<String>) realmAccess.get("roles");
  46     System.out.println("Contains USER " + roles.contains("USER"));
  47 
  48     for (int j = 0; j < keys.length; j++) {
  49       allkeys = allkeys + " " + (String) keys[j] + ":" + jwt.getClaims().get(keys[j]).toString() + " ";
  50     }
  51 
  52     return "I am authenticated with user " + authentication.getName() + " Authorities: " + authorities + " Details: "
  53         + authentication.getDetails().toString() + " allKeys: " + allkeys + " ... " + preferredUsername + " ... "
  54         + roles;
  55   }
  56 
  57   @GetMapping("/helloAdmin")
  58   @AdminRole
  59   public String helloAdmin(Authentication authentication) {
  60     return "Hello ADMIN";
  61   }
  62 
  63   @GetMapping("/helloUser/{text}")
  64   @UserRole
  65   public String helloUser(Authentication authentication, @PathVariable String text) {
  66     return "Hello USER " + text;
  67   }
  68 }
src/main/resources/application.properties
Keycloak 26.3.0 + SpringBoot 3.5.3 + Spring Security + AspectJ (AOP)
   1 cd ~
   2 sudo apt install jq 
   3 wget https://github.com/keycloak/keycloak/releases/download/26.3.0/keycloak-26.3.0.tar.gz
   4 tar xvzf keycloak-26.3.0.tar.gz
   5 cd keycloak-26.3.0/bin
   6 sh kc.sh start 
   7 sh kc.sh show-config
   8 
   9 rm server.keystore 
  10 keytool -genkeypair -alias debian -keyalg RSA -keysize 2048 -validity 365 -keystore server.keystore -dname "cn=debian,o=Keycloak,c=PT" -keypass secret -storepass secret
  11 cp server.keystore ../conf
  12 
  13 ./kc.sh start --verbose --https-key-store-password=secret
Create a temporary administrative user
* Go to https://localhost:8443/ * admin, 12345678, 12345678 * Open administration console
Create realm
* https://debian:8443/admin/master/console/#/master/realms * Click Create realm * Realm name: TestRealm * click on create
Create user
* https://debian:8443/admin/master/console/#/TestRealm/users * Create new user, * username: myuser * click create * select user myuser, * 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
   1 CLIENT_ID=oidc_test_client 
   2 CLIENT_SECRET=F5yuRxN1kbMxI6oVISFlART276Ddi8vi
   3 REALM=TestRealm
   4 
   5 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"
   6 
   7 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")
   8 echo $TOKEN 
Spring boot test project
   1 mkdir -p ~/Documents/test-springboot-keycloak
   2 cd ~/Documents/test-springboot-keycloak
   3 touch pom.xml
   4 touch src/main/java/com/example/demo/UserRole.java
   5 touch src/main/java/com/example/demo/RolesAspect.java
   6 touch src/main/java/com/example/demo/SecurityConfiguration.java
   7 touch src/main/java/com/example/demo/AdminRole.java
   8 touch src/main/java/com/example/demo/DemoApplication.java
   9 touch src/main/resources/application.properties
pom.xml
   1 <?xml version="1.0" encoding="UTF-8"?>
   2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   3         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   4   <modelVersion>4.0.0</modelVersion>
   5   <parent>
   6     <groupId>org.springframework.boot</groupId>
   7     <artifactId>spring-boot-starter-parent</artifactId>
   8     <version>3.5.3</version>
   9     <relativePath/> <!-- lookup parent from repository -->
  10   </parent>
  11   <groupId>com.example</groupId>
  12   <artifactId>demo</artifactId>
  13   <version>0.0.2-SNAPSHOT</version>
  14   <name>demo</name>
  15   <description>Demo project for Spring Boot</description>
  16   <properties>
  17     <java.version>21</java.version>
  18   </properties>
  19   <dependencies>
  20     <dependency>
  21       <groupId>org.springframework.boot</groupId>
  22       <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
  23     </dependency>
  24     <dependency>
  25       <groupId>org.springframework.boot</groupId>
  26       <artifactId>spring-boot-starter-security</artifactId>
  27     </dependency>
  28     <dependency>
  29       <groupId>org.springframework.boot</groupId>
  30       <artifactId>spring-boot-starter-web</artifactId>
  31     </dependency>
  32     <dependency>
  33       <groupId>org.springframework.boot</groupId>
  34       <artifactId>spring-boot-starter-aop</artifactId>
  35     </dependency> 
  36   </dependencies>
  37   <build>
  38     <plugins>
  39       <plugin>
  40         <groupId>org.springframework.boot</groupId>
  41         <artifactId>spring-boot-maven-plugin</artifactId>
  42       </plugin>
  43     </plugins>
  44   </build>
  45 </project>
src/main/resources/application.properties
src/main/java/com/example/demo/AdminRole.java
   1 package com.example.demo;
   2 
   3 import java.lang.annotation.Retention;
   4 import java.lang.annotation.Target;
   5 import java.lang.annotation.ElementType;
   6 import java.lang.annotation.RetentionPolicy;
   7 
   8 /**
   9  * Annotation to identify code associated with ADMIN role
  10  * 
  11  */
  12 @Retention(RetentionPolicy.RUNTIME)
  13 @Target(ElementType.METHOD)
  14 public @interface AdminRole {
  15 }
src/main/java/com/example/demo/DemoApplication.java
   1 package com.example.demo;
   2 
   3 import java.util.ArrayList;
   4 import java.util.List;
   5 import java.util.Map;
   6 
   7 import org.springframework.boot.SpringApplication;
   8 import org.springframework.boot.autoconfigure.SpringBootApplication;
   9 import org.springframework.web.bind.annotation.GetMapping;
  10 import org.springframework.web.bind.annotation.PathVariable;
  11 import org.springframework.web.bind.annotation.RestController;
  12 import org.springframework.security.core.Authentication;
  13 import org.springframework.security.core.GrantedAuthority;
  14 import org.springframework.security.oauth2.jwt.Jwt;
  15 
  16 @RestController
  17 @SpringBootApplication
  18 public class DemoApplication {
  19 
  20   public static void main(String[] args) {
  21     System.setProperty("javax.net.ssl.trustStore","truststore.p12");
  22     System.setProperty("javax.net.ssl.trustStorePassword","secret");
  23     System.setProperty("javax.net.ssl.trustStoreType","PKCS12");
  24     SpringApplication.run(DemoApplication.class, args);
  25   }
  26 
  27   @GetMapping("/otherhello")
  28   public String otherHello() {
  29     return "other hello";
  30   }
  31 
  32   @GetMapping("/hello")
  33   @UserRole
  34   public String hello(Authentication authentication) {
  35     String authorities = "";
  36     for (int authorityIndex = 0; authorityIndex < authentication.getAuthorities().size(); authorityIndex++) {
  37       GrantedAuthority ga = (GrantedAuthority) authentication.getAuthorities().toArray()[authorityIndex];
  38       authorities += ga.getAuthority() + " ";
  39     }
  40 
  41     Jwt jwt = (Jwt) authentication.getCredentials();
  42 
  43     Object[] keys = jwt.getClaims().keySet().toArray();
  44     String allkeys = "";
  45 
  46     String preferredUsername = jwt.getClaim("preferred_username").toString();
  47     Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access");
  48     List<String> roles = (ArrayList<String>) realmAccess.get("roles");
  49     System.out.println("Contains USER " + roles.contains("USER"));
  50 
  51     for (int j = 0; j < keys.length; j++) {
  52       allkeys = allkeys + " " + (String) keys[j] + ":" + jwt.getClaims().get(keys[j]).toString() + " ";
  53     }
  54 
  55     return "I am authenticated with user " + authentication.getName() + " Authorities: " + authorities + " Details: "
  56         + authentication.getDetails().toString() + " allKeys: " + allkeys + " ... " + preferredUsername + " ... "
  57         + roles;
  58   }
  59 
  60   @GetMapping("/helloAdmin")
  61   @AdminRole
  62   public String helloAdmin(Authentication authentication) {
  63     return "Hello ADMIN";
  64   }
  65 
  66   @GetMapping("/helloUser/{text}")
  67   @UserRole
  68   public String helloUser(Authentication authentication, @PathVariable String text) {
  69     return "Hello USER " + text;
  70   }
  71 
  72 }
src/main/java/com/example/demo/RolesAspect.java
   1 package com.example.demo;
   2 
   3 import java.util.List;
   4 import java.util.ArrayList;
   5 import java.util.Map;
   6 
   7 import org.aspectj.lang.ProceedingJoinPoint;
   8 import org.aspectj.lang.annotation.Around;
   9 import org.aspectj.lang.annotation.Aspect;
  10 import org.springframework.stereotype.Component;
  11 import org.springframework.security.core.Authentication;
  12 import org.springframework.security.oauth2.jwt.Jwt;
  13 
  14 @Aspect
  15 @Component
  16 public class RolesAspect {
  17 
  18     private boolean hasRole(String role, ProceedingJoinPoint joinPoint) {
  19         if (joinPoint.getArgs() != null && joinPoint.getArgs().length >= 1) {
  20             Object authArg = joinPoint.getArgs()[0];
  21             if (authArg != null && Authentication.class.isAssignableFrom(authArg.getClass())) {
  22                 Jwt jwt = (Jwt) ((Authentication) authArg).getCredentials();
  23                 Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access");
  24                 List<String> roles = (ArrayList<String>) realmAccess.get("roles");
  25                 if (roles.contains(role)) {
  26                     return true;
  27                 }
  28             }
  29         }
  30 
  31         return false;
  32     }
  33 
  34     /**
  35      * Intercept stuff annotated with UserRole annotation
  36      * 
  37      * @param joinPoint
  38      * @return
  39      * @throws Throwable
  40      */
  41     @Around("@annotation(UserRole)")
  42     public Object interceptUserRoleAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
  43 
  44         if (hasRole("USER", joinPoint)) {
  45             return joinPoint.proceed();
  46         } else {
  47             System.out.println("USER role not found");
  48             return null;
  49         }
  50     }
  51 
  52     /**
  53      * Intercept stuff annotated with AdminRole annotation
  54      * 
  55      * @param joinPoint
  56      * @return
  57      * @throws Throwable
  58      */
  59     @Around("@annotation(AdminRole)")
  60     public Object interceptAdminRoleAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
  61 
  62         if (hasRole("ADMIN", joinPoint)) {
  63             return joinPoint.proceed();
  64         } else {
  65             System.out.println("ADMIN role not found");
  66             return null;
  67         }
  68     }
  69 }
src/main/java/com/example/demo/SecurityConfiguration.java
   1 package com.example.demo;
   2 
   3 import org.springframework.context.annotation.Bean;
   4 import org.springframework.context.annotation.Configuration;
   5 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
   6 import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
   7 import org.springframework.security.web.SecurityFilterChain;
   8 
   9 @Configuration
  10 public class SecurityConfiguration {
  11 
  12         @Bean
  13         protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  14                 return http.oauth2ResourceServer(
  15                                 (oauth2ResourceServer) -> {
  16                                         oauth2ResourceServer.jwt((jwt) -> {
  17                                                 jwt.decoder(null);
  18                                         });
  19                                 }).build();
  20         }
  21 
  22         @Bean
  23         public WebSecurityCustomizer webSecurityCustomizer() {
  24                 return (web) -> web.ignoring().requestMatchers("/otherhello**").requestMatchers("/static**");
  25         }
  26 }
src/main/java/com/example/demo/UserRole.java
   1 package com.example.demo;
   2 
   3 import java.lang.annotation.Retention;
   4 import java.lang.annotation.Target;
   5 import java.lang.annotation.ElementType;
   6 import java.lang.annotation.RetentionPolicy;
   7 
   8 /**
   9  * Annotation to identify code associated with USER role
  10  * 
  11  */
  12 @Retention(RetentionPolicy.RUNTIME)
  13 @Target(ElementType.METHOD)
  14 public @interface UserRole {
  15 }
get keycloak server cert debian.pem
Build and run
Call secure endpoint
   1 CLIENT_ID=oidc_test_client 
   2 CLIENT_SECRET=F5yuRxN1kbMxI6oVISFlART276Ddi8vi
   3 REALM=TestRealm
   4 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")
   5 echo $TOKEN 
   6 ACCESS_TOKEN=$(echo $TOKEN | jq -r '.access_token')
   7 echo $ACCESS_TOKEN
   8 curl -X GET http://localhost:8081/hello  --header "Authorization: Bearer $ACCESS_TOKEN"
   9 # insecure endpoint
  10 curl localhost:8081/otherhello
