Integracja Keycloak z Spring Boot
Integracja Keycloak z Spring Boot
Keycloak to serwer uwierzytelniania oraz autoryzacji użytkowników. Posiada prosty panel administracyjny pozwalający w intuicyjny sposób zarządzać użytkownikami oraz ich uprawnieniami. Dla uproszczenia serwer Keycloaka uruchomimy lokalnie na swojej maszynie. Pobieramy spakowany plik ze strony – https://www.keycloak.org/downloads.html. Na chwile pisania tego artykułu jest to wersja 15.0.2. Przechodzimy do folderu ./bin i uruchamiamy Keycloaka poleceniem:
standalone.bat
serwer znajduję się pod adresem:
http://localhost:8080/auth/
Należy utworzyć inicjalnego użytkownika z prawami administratora. Po tej operacji logujemy się już jako administrator do Keycloaka:
http://localhost:8080/auth/admin/
Tworzymy tzw. Realm czyli przestrzeń w której będziemy zarządzać użytkownikami oraz ich uprawnieniami:
Tworzymy klienta – aplikacja która będzie korzystała z Keycloaka do uwierzytelniania i autoryzacji użytkowników:
Konfigurujemy klienta:
- „Access Type” – confidential
- „Service Accounts Enabled” – ON
- „Authorization Enabled” – ON
W zakładce ‚credentials‚ widoczny jest client-secret który wymagany będzie w konfiguracji aplikacji Spring Boota ze względu na ustawienie parametru „Access Type” = confidential:
W zakładce ‚Roles‚ dodajemy role na poziomie klienta. Dla przykładu dodajmy 3 podstawowe role:
- User,
- Moderator,
- Admin.
Następnie tworzymy globalne role – tzw. ‚Realm Role‚ jak poniżej:
Dodajmy w zakładce ‚Users‚ kolejno użytkowników przypisując im odpowiednio globalne role oraz nadając im przykładowe hasła:
analogicznie postępujemy dla użytkowników ‚User‚ oraz ‚Admin‚.
Generowanie Tokena dostępowego JWT
Otwieramy link: OpenID Endpoint Configuration:
Kopiujemy token_endpoint:
http://localhost:8080/auth/realms/pljavaleader/protocol/openid-connect/token
I z użyciem dowolnego klienta Rest HTTP np. Advanced REST Client wysyłamy żądanie POST:
Zdekodujmy access_token do postaci czytelnej z użyciem popularnego narzędzia https://jwt.io/:
Widać, że token ten dotyczy użytkownika user który posiada:
- globalną role: app-user,
- rolę na poziomie klienta: User.
Konfiguracja aplikacji opartej o Spring Boot z wykorzystaniem Keycloak
Tworzymy nowy projekt Spring Boota – plik pom.xml – niezbędne zależności:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</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-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-spring-boot-starter</artifactId> <version>9.0.2</version> </dependency> </dependencies>
dodajemy sekcję dependency-management:
<dependencyManagement> <dependencies> <dependency> <groupId>org.keycloak.bom</groupId> <artifactId>keycloak-adapter-bom</artifactId> <version>9.0.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
oraz kompletujemy plik application.properties na podstawie danych skonfigurowanych wcześniej w Keycloak:
server.port = 8000 keycloak.realm = pljavaleader keycloak.auth-server-url = http://localhost:8080/auth keycloak.ssl-required = external keycloak.resource = spring-boot-keycloak keycloak.credentials.secret = c2e6ac81-f2ab-4c78-a41c-891a767ec6f5 keycloak.use-resource-role-mappings = true
Jako, że aplikacja działać będzie na przykładowym porcie 8000 dodajmy w konfiguracji Keycloaka adres na który zostaniemy przekierowani po prawidłowym zalogowaniu się:
Tworzymy przykładowy RestController gdzie dostęp do poszczególnych endpointów zabezpieczony jest odpowiednimi rolami:
@RestController @RequestMapping("/api") public class Api { @RolesAllowed({"User", "Moderator", "Admin"}) @RequestMapping(value = "/readPost", method = RequestMethod.GET) public ResponseEntity<String> readPost() { return ResponseEntity.ok("USER ACCESS"); } @RolesAllowed("Moderator") @RequestMapping(value = "/modifyPost", method = RequestMethod.GET) public ResponseEntity<String> modifyPost() { return ResponseEntity.ok("MODERATOR ACCESS"); } @RolesAllowed("Admin") @RequestMapping(value = "/deletePost", method = RequestMethod.GET) public ResponseEntity<String> deletePost() { return ResponseEntity.ok("ADMIN ACCESS"); } }
oraz klasę konfiguracyjną w której definiujemy to, że:
- Keycloak będzie odpowiedzialny za świadczenie usług autentykacji,
- wskazujemy strategię autentykacji która rejestruje sesję użytkownika po jego poprawnym zalogowaniu się do aplikacji (w przypadku kiedy komunikacja byłaby tylko między serwisami – NullAuthenticatedSessionStrategy)
- wskazujemy, że plikiem konfiguracyjnym Keycloaka będzie application.properties a nie domyślny plik keycloak.json.
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(jsr250Enabled = true) public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); http.authorizeRequests() .anyRequest() .permitAll(); http.csrf().disable(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); auth.authenticationProvider(keycloakAuthenticationProvider); } @Bean @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } @Bean public KeycloakConfigResolver KeycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); } }
PUBLIC client
Przejdźmy w przeglądarce na adres:
http://localhost:8000/api/readPost
zostanie nam przedstawiona strona logowania:
Po wpisaniu prawidłowych danych zostaniemy przekierowani na stronę:
http://localhost:8000/api/readPost
z wynikiem:
USER ACCESS
BEARER-ONLY client
Zmieńmy:
- „Access Type” – confidential
wygenerujemy token JWT i zmieńmy
- „Access Type” – Bearer-only
Teraz po wejściu na:
http://localhost:8000/api/readPost
otrzymamy informację o braku dostępu z powodu nie przesłania tokenu JWT:
Z użyciem Advanced Rest Client użyjmy go jako nagłówek przesyłany w żądaniu do API w formie:
- authorization : Bearer wygenerowany_token_jwt
Dostęp do API został przyznany.
CONFIDENTIAL client
Zmieńmy:
- „Access Type” – confidential
i w konfiguracji aplikacji w pliku application.properties dodajmy parametr:
keycloak-bearer-only = true
wygenerujmy analogiczny token JWT:
Z użyciem Advanced Rest Client użyjmy go jako nagłówek przesyłany w żądaniu do API w formie:
- authorization : Bearer wygenerowany_token_jwt
Dostęp do API został przyznany.
Leave a comment