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.

 

Zobacz kod na GitHubie i zapisz się na bezpłatny newsletter!

.

Leave a comment

Your email address will not be published.


*