MoinMoin Logo
  • Comments
  • Immutable Page
  • Menu
    • Navigation
    • RecentChanges
    • FindPage
    • Local Site Map
    • Help
    • HelpContents
    • HelpOnMoinWikiSyntax
    • Display
    • Attachments
    • Info
    • Raw Text
    • Print View
    • Edit
    • Load
    • Save
  • Login

Navigation

  • Start
  • Sitemap
Revision 26 as of 2023-05-24 19:10:57
  • keycloak

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

   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

  • 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

   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

   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
  • MoinMoin Powered
  • Python Powered
  • GPL licensed
  • Valid HTML 4.01