diff --git a/README.md b/README.md index baea431..109c762 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,14 @@ The components you will build (and use) look like this: __Please check out the [complete documentation](application-architecture) for the sample application before starting with the first hands-on lab__. -All the code currently is build using: +All the code currently is build using + * [Spring Boot 2.4.x Release](https://spring.io/blog/2020/11/12/spring-boot-2-4-0-available-now) * [Spring Framework 5.3.x Release](https://spring.io/blog/2020/10/27/spring-framework-5-3-goes-ga) * [Spring Security 5.4.x Release](https://spring.io/blog/2020/09/10/spring-security-5-4-goes-ga) * [Spring Batch 4.3.x Release](https://spring.io/blog/2020/10/28/spring-batch-4-3-is-now-ga) -and is verified against the currently supported long-term version 11 of Java (The latest version 14 should work as well). +All code is verified against the currently supported long-term version 11 of Java (The latest version 14 should work as well). To check system requirements and setup for this workshop please follow the [setup guide](setup). diff --git a/bonus-labs/keycloak-test-containers/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java b/bonus-labs/keycloak-test-containers/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java index 22f4723..978ded7 100644 --- a/bonus-labs/keycloak-test-containers/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java +++ b/bonus-labs/keycloak-test-containers/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java @@ -7,7 +7,9 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.StringUtils; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Optional; @@ -17,7 +19,7 @@ @SuppressWarnings("unused") public class LibraryUserJwtAuthenticationConverter implements Converter { - private static final String GROUPS_CLAIM = "groups"; + private static final String SCOPE_CLAIM = "scope"; private static final String ROLE_PREFIX = "ROLE_"; private final LibraryUserDetailsService libraryUserDetailsService; @@ -37,19 +39,24 @@ public AbstractAuthenticationToken convert(Jwt jwt) { } private Collection extractAuthorities(Jwt jwt) { - return this.getGroups(jwt).stream() + return this.getScopes(jwt).stream() .map(authority -> ROLE_PREFIX + authority.toUpperCase()) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } @SuppressWarnings("unchecked") - private Collection getGroups(Jwt jwt) { - Object groups = jwt.getClaims().get(GROUPS_CLAIM); - if (groups instanceof Collection) { - return (Collection) groups; + private Collection getScopes(Jwt jwt) { + Object scopes = jwt.getClaims().get(SCOPE_CLAIM); + if (scopes instanceof String) { + if (StringUtils.hasText((String) scopes)) { + return Arrays.asList(((String) scopes).split(" ")); + } + return Collections.emptyList(); + } + if (scopes instanceof Collection) { + return (Collection) scopes; } - return Collections.emptyList(); } } diff --git a/bonus-labs/keycloak-test-containers/library-server-initial/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java b/bonus-labs/keycloak-test-containers/library-server-initial/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java index 22f4723..978ded7 100644 --- a/bonus-labs/keycloak-test-containers/library-server-initial/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java +++ b/bonus-labs/keycloak-test-containers/library-server-initial/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java @@ -7,7 +7,9 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.StringUtils; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Optional; @@ -17,7 +19,7 @@ @SuppressWarnings("unused") public class LibraryUserJwtAuthenticationConverter implements Converter { - private static final String GROUPS_CLAIM = "groups"; + private static final String SCOPE_CLAIM = "scope"; private static final String ROLE_PREFIX = "ROLE_"; private final LibraryUserDetailsService libraryUserDetailsService; @@ -37,19 +39,24 @@ public AbstractAuthenticationToken convert(Jwt jwt) { } private Collection extractAuthorities(Jwt jwt) { - return this.getGroups(jwt).stream() + return this.getScopes(jwt).stream() .map(authority -> ROLE_PREFIX + authority.toUpperCase()) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } @SuppressWarnings("unchecked") - private Collection getGroups(Jwt jwt) { - Object groups = jwt.getClaims().get(GROUPS_CLAIM); - if (groups instanceof Collection) { - return (Collection) groups; + private Collection getScopes(Jwt jwt) { + Object scopes = jwt.getClaims().get(SCOPE_CLAIM); + if (scopes instanceof String) { + if (StringUtils.hasText((String) scopes)) { + return Arrays.asList(((String) scopes).split(" ")); + } + return Collections.emptyList(); + } + if (scopes instanceof Collection) { + return (Collection) scopes; } - return Collections.emptyList(); } } diff --git a/bonus-labs/micronaut-server-app/README.md b/bonus-labs/micronaut-server-app/README.md index 2c218cd..dba484d 100644 --- a/bonus-labs/micronaut-server-app/README.md +++ b/bonus-labs/micronaut-server-app/README.md @@ -48,7 +48,7 @@ As this app uses the same Keycloak client configuration you can just use the sam We will use [Keycloak](https://keycloak.org) as identity provider. Please again make sure you have set up and running -keycloak as described in [Setup Keycloak](../setup_keycloak/README.md) +keycloak as described in [Setup Keycloak](../setup/README.md)
diff --git a/lab1/README.md b/lab1/README.md index 251efb3..440b2bf 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -451,9 +451,12 @@ If you have a look inside the _com.example.library.server.business.UserService_ you will notice that the corresponding method has the following authorization check: ```java -@PreAuthorize("hasRole('LIBRARY_ADMIN')") -public List findAll() { - return userRepository.findAll(); +public class UserService { + //... + @PreAuthorize("hasRole('LIBRARY_ADMIN')") + public List findAll() { + return userRepository.findAll(); + } } ``` @@ -461,9 +464,12 @@ The required authority _ROLE_LIBRARY_ADMIN_ does not match the mapped authority To solve this we would have to add the _SCOPE_xxx_ authorities to the existing ones like this: ```java -@PreAuthorize("hasRole('LIBRARY_ADMIN') || hasAuthority('SCOPE_library_admin')") -public List findAll() { - return userRepository.findAll(); +public class UserService { + //... + @PreAuthorize("hasRole('LIBRARY_ADMIN') || hasAuthority('SCOPE_library_admin')") + public List findAll() { + return userRepository.findAll(); + } } ``` @@ -506,7 +512,7 @@ In general, you have two choices here: In this workshop we will use the first approach and... - * ...read the authorization data from the _groups_ claim inside the JWT token + * ...read the authorization data from the _scope_ claim inside the JWT token * ...map to our local _LibraryUser_ by reusing the _LibraryUserDetailsService_ to search for a user having the same email as the _email_ claim inside the JWT token @@ -533,7 +539,7 @@ import java.util.stream.Collectors; @SuppressWarnings("unused") public class LibraryUserJwtAuthenticationConverter implements Converter { - private static final String GROUPS_CLAIM = "groups"; + private static final String SCOPE_CLAIM = "scope"; private static final String ROLE_PREFIX = "ROLE_"; private final LibraryUserDetailsService libraryUserDetailsService; @@ -560,12 +566,17 @@ public class LibraryUserJwtAuthenticationConverter } @SuppressWarnings("unchecked") - private Collection getGroups(Jwt jwt) { - Object groups = jwt.getClaims().get(GROUPS_CLAIM); - if (groups instanceof Collection) { - return (Collection) groups; + private Collection getScopes(Jwt jwt) { + Object scopes = jwt.getClaims().get(SCOPE_CLAIM); + if (scopes instanceof String) { + if (StringUtils.hasText((String) scopes)) { + return Arrays.asList(((String) scopes).split(" ")); + } + return Collections.emptyList(); + } + if (scopes instanceof Collection) { + return (Collection) scopes; } - return Collections.emptyList(); } } @@ -672,7 +683,7 @@ Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 cl Despite the fact that the _audience_ claim is not specified or mandatory for access tokens -specifying and validating the _audience_ claim of access tokens is strongly recommended to avoid misusing access tokens for other resource servers. +specifying and validating the _audience_ claim of access tokens is strongly recommended avoiding misusing access tokens for other resource servers. There is also a new [draft specification](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt) on the way to provide a standardized and interoperable profile as an alternative to the proprietary JWT access token layouts. diff --git a/lab1/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java b/lab1/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java index 464bbb9..978ded7 100644 --- a/lab1/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java +++ b/lab1/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java @@ -39,14 +39,14 @@ public AbstractAuthenticationToken convert(Jwt jwt) { } private Collection extractAuthorities(Jwt jwt) { - return this.getGroups(jwt).stream() + return this.getScopes(jwt).stream() .map(authority -> ROLE_PREFIX + authority.toUpperCase()) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } @SuppressWarnings("unchecked") - private Collection getGroups(Jwt jwt) { + private Collection getScopes(Jwt jwt) { Object scopes = jwt.getClaims().get(SCOPE_CLAIM); if (scopes instanceof String) { if (StringUtils.hasText((String) scopes)) { diff --git a/lab4/README.md b/lab4/README.md index 17f4542..ca5e99c 100644 --- a/lab4/README.md +++ b/lab4/README.md @@ -67,6 +67,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.jwt.Jwt; @@ -74,6 +75,7 @@ import java.time.Instant; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -84,44 +86,47 @@ import static org.mockito.BDDMockito.given; @ExtendWith(MockitoExtension.class) class LibraryUserJwtAuthenticationConverterTest { - @Mock - private LibraryUserDetailsService libraryUserDetailsService; + @Mock + private LibraryUserDetailsService libraryUserDetailsService; - @Test - void convertWithSuccess() { - Jwt jwt = Jwt.withTokenValue("1234") - .header("typ", "JWT") - .claim("sub", "userid") - .claim("groups", Collections.singletonList("library_user")) - .build(); - - given(libraryUserDetailsService.loadUserByUsername(any())) - .willReturn(new LibraryUser(UserBuilder.user().build())); - - LibraryUserJwtAuthenticationConverter cut = new LibraryUserJwtAuthenticationConverter(libraryUserDetailsService); - AbstractAuthenticationToken authenticationToken = cut.convert(jwt); - assertThat(authenticationToken).isNotNull(); - assertThat(authenticationToken.getAuthorities()).isNotEmpty(); - assertThat(authenticationToken.getAuthorities() - .iterator().next().getAuthority()).isEqualTo("ROLE_LIBRARY_USER"); - } - - @Test - void convertWithFailure() { - Jwt jwt = Jwt.withTokenValue("1234") - .header("typ", "JWT") - .claim("sub", "userid") - .claim("groups", Collections.singletonList("library_user")) - .build(); - - given(libraryUserDetailsService.loadUserByUsername(any())) - .willThrow(new UsernameNotFoundException("No user found")); + @Test + void convertWithSuccess() { + Jwt jwt = Jwt.withTokenValue("1234") + .header("typ", "JWT") + .claim("sub", "userid") + .claim("groups", Collections.singletonList("library_user")) + .claim("scope", "library_user openid profile") + .build(); + + given(libraryUserDetailsService.loadUserByUsername(any())) + .willReturn(new LibraryUser(UserBuilder.user().build())); + + LibraryUserJwtAuthenticationConverter cut = new LibraryUserJwtAuthenticationConverter(libraryUserDetailsService); + AbstractAuthenticationToken authenticationToken = cut.convert(jwt); + assertThat(authenticationToken).isNotNull(); + assertThat(authenticationToken.getAuthorities()).isNotEmpty(); + assertThat(authenticationToken.getAuthorities() + .stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())) + .containsAnyOf("ROLE_LIBRARY_USER"); + } - LibraryUserJwtAuthenticationConverter cut = new LibraryUserJwtAuthenticationConverter(libraryUserDetailsService); - assertThatExceptionOfType(UsernameNotFoundException.class).isThrownBy( - () -> cut.convert(jwt) - ); - } + @Test + void convertWithFailure() { + Jwt jwt = Jwt.withTokenValue("1234") + .header("typ", "JWT") + .claim("sub", "userid") + .claim("groups", Collections.singletonList("library_user")) + .claim("scope", "library_user openid profile") + .build(); + + given(libraryUserDetailsService.loadUserByUsername(any())) + .willThrow(new UsernameNotFoundException("No user found")); + + LibraryUserJwtAuthenticationConverter cut = new LibraryUserJwtAuthenticationConverter(libraryUserDetailsService); + assertThatExceptionOfType(UsernameNotFoundException.class).isThrownBy( + () -> cut.convert(jwt) + ); + } } ``` _LibraryUserJwtAuthenticationConverterTest_ diff --git a/lab4/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java b/lab4/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java index 22f4723..978ded7 100644 --- a/lab4/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java +++ b/lab4/library-server-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java @@ -7,7 +7,9 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.StringUtils; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Optional; @@ -17,7 +19,7 @@ @SuppressWarnings("unused") public class LibraryUserJwtAuthenticationConverter implements Converter { - private static final String GROUPS_CLAIM = "groups"; + private static final String SCOPE_CLAIM = "scope"; private static final String ROLE_PREFIX = "ROLE_"; private final LibraryUserDetailsService libraryUserDetailsService; @@ -37,19 +39,24 @@ public AbstractAuthenticationToken convert(Jwt jwt) { } private Collection extractAuthorities(Jwt jwt) { - return this.getGroups(jwt).stream() + return this.getScopes(jwt).stream() .map(authority -> ROLE_PREFIX + authority.toUpperCase()) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } @SuppressWarnings("unchecked") - private Collection getGroups(Jwt jwt) { - Object groups = jwt.getClaims().get(GROUPS_CLAIM); - if (groups instanceof Collection) { - return (Collection) groups; + private Collection getScopes(Jwt jwt) { + Object scopes = jwt.getClaims().get(SCOPE_CLAIM); + if (scopes instanceof String) { + if (StringUtils.hasText((String) scopes)) { + return Arrays.asList(((String) scopes).split(" ")); + } + return Collections.emptyList(); + } + if (scopes instanceof Collection) { + return (Collection) scopes; } - return Collections.emptyList(); } } diff --git a/lab4/library-server-complete/src/test/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverterTest.java b/lab4/library-server-complete/src/test/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverterTest.java index c6f7344..004ca9f 100644 --- a/lab4/library-server-complete/src/test/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverterTest.java +++ b/lab4/library-server-complete/src/test/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverterTest.java @@ -6,6 +6,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.jwt.Jwt; @@ -13,6 +14,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -32,6 +34,7 @@ void convertWithSuccess() { .header("typ", "JWT") .claim("sub", "userid") .claim("groups", Collections.singletonList("library_user")) + .claim("scope", "library_user openid profile") .build(); given(libraryUserDetailsService.loadUserByUsername(any())) @@ -42,7 +45,8 @@ void convertWithSuccess() { assertThat(authenticationToken).isNotNull(); assertThat(authenticationToken.getAuthorities()).isNotEmpty(); assertThat(authenticationToken.getAuthorities() - .iterator().next().getAuthority()).isEqualTo("ROLE_LIBRARY_USER"); + .stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())) + .containsAnyOf("ROLE_LIBRARY_USER"); } @Test @@ -51,6 +55,7 @@ void convertWithFailure() { .header("typ", "JWT") .claim("sub", "userid") .claim("groups", Collections.singletonList("library_user")) + .claim("scope", "library_user openid profile") .build(); given(libraryUserDetailsService.loadUserByUsername(any())) diff --git a/lab4/library-server-initial/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java b/lab4/library-server-initial/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java index 22f4723..978ded7 100644 --- a/lab4/library-server-initial/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java +++ b/lab4/library-server-initial/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java @@ -7,7 +7,9 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.StringUtils; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Optional; @@ -17,7 +19,7 @@ @SuppressWarnings("unused") public class LibraryUserJwtAuthenticationConverter implements Converter { - private static final String GROUPS_CLAIM = "groups"; + private static final String SCOPE_CLAIM = "scope"; private static final String ROLE_PREFIX = "ROLE_"; private final LibraryUserDetailsService libraryUserDetailsService; @@ -37,19 +39,24 @@ public AbstractAuthenticationToken convert(Jwt jwt) { } private Collection extractAuthorities(Jwt jwt) { - return this.getGroups(jwt).stream() + return this.getScopes(jwt).stream() .map(authority -> ROLE_PREFIX + authority.toUpperCase()) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } @SuppressWarnings("unchecked") - private Collection getGroups(Jwt jwt) { - Object groups = jwt.getClaims().get(GROUPS_CLAIM); - if (groups instanceof Collection) { - return (Collection) groups; + private Collection getScopes(Jwt jwt) { + Object scopes = jwt.getClaims().get(SCOPE_CLAIM); + if (scopes instanceof String) { + if (StringUtils.hasText((String) scopes)) { + return Arrays.asList(((String) scopes).split(" ")); + } + return Collections.emptyList(); + } + if (scopes instanceof Collection) { + return (Collection) scopes; } - return Collections.emptyList(); } } diff --git a/lab5/library-server-static-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java b/lab5/library-server-static-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java index 22f4723..978ded7 100644 --- a/lab5/library-server-static-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java +++ b/lab5/library-server-static-complete/src/main/java/com/example/library/server/security/LibraryUserJwtAuthenticationConverter.java @@ -7,7 +7,9 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.StringUtils; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Optional; @@ -17,7 +19,7 @@ @SuppressWarnings("unused") public class LibraryUserJwtAuthenticationConverter implements Converter { - private static final String GROUPS_CLAIM = "groups"; + private static final String SCOPE_CLAIM = "scope"; private static final String ROLE_PREFIX = "ROLE_"; private final LibraryUserDetailsService libraryUserDetailsService; @@ -37,19 +39,24 @@ public AbstractAuthenticationToken convert(Jwt jwt) { } private Collection extractAuthorities(Jwt jwt) { - return this.getGroups(jwt).stream() + return this.getScopes(jwt).stream() .map(authority -> ROLE_PREFIX + authority.toUpperCase()) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } @SuppressWarnings("unchecked") - private Collection getGroups(Jwt jwt) { - Object groups = jwt.getClaims().get(GROUPS_CLAIM); - if (groups instanceof Collection) { - return (Collection) groups; + private Collection getScopes(Jwt jwt) { + Object scopes = jwt.getClaims().get(SCOPE_CLAIM); + if (scopes instanceof String) { + if (StringUtils.hasText((String) scopes)) { + return Arrays.asList(((String) scopes).split(" ")); + } + return Collections.emptyList(); + } + if (scopes instanceof Collection) { + return (Collection) scopes; } - return Collections.emptyList(); } } diff --git a/setup/README.md b/setup/README.md index f333a05..8c71c70 100644 --- a/setup/README.md +++ b/setup/README.md @@ -82,7 +82,7 @@ If you have Docker installed then setting up Keycloak is quite easy. To configure and run Keycloak using docker 1. Open a new command line terminal window -2. Change directory to subdirectory _setup_keycloak_ of the workshop repository +2. Change directory to subdirectory _setup_ of the workshop repository 3. Open and edit the script _run_keycloak_docker.sh_ or _run_keycloak_docker.bat_ (depending on your OS) and adapt the value for _WORKSHOP_HOME_ to your local workshop repository directory 3. Save and execute the script _run_keycloak_docker.sh_ or _run_keycloak_docker.bat_ (depending on your OS) @@ -91,23 +91,25 @@ then Keycloak is configured and running. Now open your web browser and navigate to [localhost:8080/auth/admin](http://localhost:8080/auth/admin) and login using the credentials _admin_/_admin_. +If you see errors importing the workshop configuration then please re-check the value of the _WORKSHOP_HOME_ environment variable (step 2 above) so that the script can find the _keycloak_realm_workshop.json_ file to import. + ### Local Installation To set up [Keycloak](https://keycloak.org): -1. Download the [Standard Server Distribution of Keycloak (Version 10.0.x)](https://www.keycloak.org/downloads-archive.html). +1. Download the [Standard Server Distribution of Keycloak (Version 12.0.4 or later)](https://www.keycloak.org/downloads-archive.html). 2. Extract the downloaded zip/tar file __keycloak-x.x.x.zip__/__keycloak-x.x.x.tar-gz__ into a new local directory of your choice (this directory will be referenced as ____ in next steps) This workshop requires a pre-defined configuration for Keycloak (i.e. some OAuth2/OpenID Connect clients, and user accounts). To configure Keycloak you need to have checked out the GIT repository for this workshop. -All you need to configure Keycloak is located in the subdirectory _setup_keycloak_ of the repository. +All you need to configure Keycloak is located in the subdirectory _setup_ of the repository. -1. Change into the subdirectory _setup_keycloak_ of the workshop git repository -2. Open the file __import_keycloak_realm.sh__ or __import_keycloak_realm.bat__ (depending on your OS) in the _setup_keycloak_ subdirectory +1. Change into the subdirectory _setup_ of the workshop git repository +2. Open the file __import_keycloak_realm.sh__ or __import_keycloak_realm.bat__ (depending on your OS) in the _setup_ subdirectory and change the value of the environment variable _KEYCLOAK_HOME_ to your ____ of step 2 and save the file -3. Now open a new command-line terminal window, change into the subdirectory _setup_keycloak_ again and execute the provided script +3. Now open a new command-line terminal window, change into the subdirectory _setup_ again and execute the provided script __import_keycloak_realm.sh__ or __import_keycloak_realm.bat__ (depending on your OS). This starts a standalone Keycloak instance and automatically imports the required configuration. 4. Wait until the import has finished (look for a line like _Started 590 of 885 services_) then @@ -122,13 +124,13 @@ If all worked successfully you should see the settings page of the _Workshop_ re #### Startup Keycloak -You only have to do the initial setup section for local install once. +Please note: You have to do the initial setup section for local install variant only once. If you have stopped Keycloak and want to start it again then follow the next lines in this section. To startup [Keycloak](https://keycloak.org): -1. Open a terminal and change directory to sub directory __/bin__ and start Keycloak using -the __standalone.sh__(Linux or Mac OS) or __standalone.bat__ (Windows) scripts +1. Open a terminal and change directory to subdirectory __/bin__ and start Keycloak using +the __standalone.sh__(Linux or macOS) or __standalone.bat__ (Windows) scripts 2. Wait until keycloak has been started completely - you should see something like this `...(WildFly Core ...) started in 6902ms - Started 580 of 842 services` #### Remap default port of Keycloak @@ -136,7 +138,7 @@ the __standalone.sh__(Linux or Mac OS) or __standalone.bat__ (Windows) scripts In case port _8080_ does not work on your local machine (i.e. is used by another process) then you may have to change Keycloak to use another port. This can be done like this (e.g. for remapping port to 8090 instead of 8080): -On Linux/MAC: +On Linux/macOS: ``` ./standalone.sh -Djboss.socket.binding.port-offset=10 ```