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

   1 cd /tmp
   2 wget https://github.com/keycloak/keycloak/releases/download/14.0.0/keycloak-14.0.0.zip
   3 unzip -t keycloak-14.0.0.zip
   4 unzip keycloak-14.0.0.zip
   5 cd ~/tmp/keycloak-14.0.0/bin
   6 sh standalone.sh 
   7 http://localhost:8080/auth

Create admin user

Create realm

Add user myuser

Add user mysubtaskuser

Set user password myuser

Set user password mysubtaskuser

Create role USER

Create role USERSUBTASK

Associate role to user myuser

Associate role to user mysubtaskuser

Create keycloak client

client data

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

   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

   1 server.port=8081
   2 spring.security.oauth2.resourceserver.jwt.issuer-uri=http://debian:8080/realms/MyRealm
   3 #logging.level.root=DEBUG
   4 logging.level.root=INFO
   5 logging.file=/tmp/testout.log

keycloak (last edited 2023-06-13 09:23:09 by 127)