Poprzedni wpis dotyczący Keycloaka zakończyliśmy na jego budowie. Skoro już znamy budowę Keycloaka, wiemy jak go zainstalować pora na integrację z backendem naszej aplikacji. Słowo „Prosta” oznaczania najprostsza formę integracji Keycloaka ze Springiem, zaawansowana pojawi się niebawem.

Pierwszą rzeczą jaką musimy zrobić to przygotować serwer Keycloak, korzystając z docker-compose który znajdziemy na poprzednim wpisieNastępnie wchodzimy na stronę  http://localhost:8080/auth/admin/ logując się wcześniej ustawionymi danymi poprzez zmienne środowiskowe.

Fragment docker-compose.yml:

environment:
  - "KEYCLOAK_USER=admin"
  - "KEYCLOAK_PASSWORD=admin" 
Gdy udało się nam zalogować do panelu administratora, powinny się nam ukazać ustawienia realmu Master. W komercjnym świecie musimy stworzyć własny realm, gdyż master jest domyślnym, zawierający szereg administracjnych rzeczy.
Klikamy w lewym górnym rogu na Master, a następnie Add realm

Wpisujemy dowlona nazwe reprezentująca zbiór naszych aplikacji (nazwe projektu)

Oczywiśnie możemy importować konfiguracje z już istniejącego realmu i na bazie niego tworzyc. Klikamy Create

Poprawne stworzenie realma powinno sie zakończyć stroną administracjna jego

Kolejną rzeczą jest dodanie obiektu client, który reprezentuje naszą aplikacje (webowa, mobilna itd), w tym celu musimy przejść do zakładki Clients.

Nastepnie w prawym rogu, klikamy create i wpisujemy nazwe naszej aplikacji.

zostawiamy client-protocol „openid-connect” i klimamy save . Po poprawnym stworzeniu pojawi się na strona administracjna tego klienta


Ustawiamy Access Type na Confidential co daje nam to ze aby appka springowa mogła korzystać z tego clienta musi posiadać specjany secret. 

Service Accounts Enabled na ON co pozwala appce springowej na pobranie access tokenu dla swoich celów.

Authorization Enabled na ON co pozwala na uzywanie przez aplikacje Policy Decision Point keycloaka, czyli definiowanie polityk i uprawnien i ich przetwarzanie.

Zapisujemy ustawienia i przechodzimy do zakładki Credentials, kopjujemy Secret gdyż go ustawimy w aplikacji Springowej.

Kolejnym krokiem będzie utworzenie ról, definiujących posiadanie danych funkcjonalności tzn dostępów do konkretnych endpointów.Na potrzeby bloga stworze 2 role admin i user.

Przechodzimy do sekcji Roles we wcześniej zdefiniowanym kliencie -> Add Role i dodajemy Role

Następnie tworzymy role globalne w stworzonym realmie. Da nam to tą własność że będziemy mogli przypisać jedna role do usera, która będzie się różnie zachowywać w zależności od klienta – Rola globalna jakby agreguje specyficzne role klientów (aplikacji)

Teraz jak już wcześniej wspomniałem wiążemy 2 globalne role z rola danego klienta(aplikacji)

Globalna  rola Spring-app-test-admin -> lokalna rola klienta – admin, Globalna  rola Spring-app-test-user-> user. Tworzymy 2 uzytkownikow testowych (user-test,user-admin) i ustawiamy im hasla

Na koniec przypisujemy do naszych użytkowników role globalne

Kolejnym krokiem będzie konfiguracja aplikacji Spring Boota  z Keycloakiem. Pierwszym krokiem będzie wygenerowanie szkieletu aplikacji poprzez stronę start.spring.io

Istotnym elemnetem jest dodanie 2 zależnosci Spring Security i Spring Web a nastepnie generujemy projekt i otwieramy w naszym ulubionym IDE.

Pierwszym krokiem będzie dodanie zależności dla Keycloaka, w pliku pom.xml dodajemy odpowiednie wpisy

<dependencies>
   ...
   
   <dependency>
      <groupId>org.keycloak</groupId>
      <artifactId>keycloak-spring-boot-starter</artifactId>
      <version>10.0.2</version>
   </dependency>
  
   ...
</dependencies>

<dependencyManagement>
  
   <dependencies>
      <dependency>
         <groupId>org.keycloak.bom</groupId>
         <artifactId>keycloak-adapter-bom</artifactId>
         <version>10.0.2</version>
         <type>pom</type>
         <scope>import</scope>
      </dependency>
   </dependencies>

</dependencyManagement> 

Kolejno edytujemy dane dostępowe do Keycloaka dla aplikacji w application.properties. Secret bedzie różnie generowany, wiec warto sprawdzić.

server.port                         = 8000
keycloak.realm                      = Projekt-testowy-sso
keycloak.auth-server-url            = http://localhost:8080/auth
keycloak.ssl-required               = external
keycloak.resource                   = spring-app-test
keycloak.credentials.secret         = 01822bf2-1177-49fe-a1cf-454b540a65a0
keycloak.use-resource-role-mappings = true
keycloak.bearer-only                = true 

Kolejnym krokiem jest utworzenie konfiguracji spring security w oparciu o KeycloakWebSecurityConfigurerAdapter rozszerzajajcy klase bazowa WebSecurityConfigurer, dzięki czemu mozemy przeciazyc odpowiednie metody.

package pl.devopsi.springkeycloaktest.configuration;

import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;


@Configuration  
@EnableWebSecurity 
@EnableGlobalMethodSecurity(jsr250Enabled = true) 
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {


    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }


    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }
    
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
            .anyRequest()
            .permitAll();
        http.csrf().disable();
    }
}
 

sessionAuthenticationStrategy() – definiuje session auth strategie
keycloakConfigResolver() – pozwala na uzywanie konfiguracji keycloaka poprzez properties
configure(AuthenticationManagerBuilder auth) -rejestruje KeycloakAuthenticationProvider w auth menadżerze
configure(HttpSecurity http) wymuaszmy aby wszystykie requesty wymagaly autoryzacji

Gdy juz mamy nasz konfiguracje Spring Security czas na testowe endpointy, które zweryfikują czy dany user ma odp role.

package pl.devopsi.springkeycloaktest.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.security.RolesAllowed;

@RestController
public class TestController {
    @RolesAllowed("user")
    @GetMapping("/user")
    public String getAdmin(){
        return "admin";
    }
    @RolesAllowed("admin")
    @GetMapping("/admin")
    public String getUser(){
        return "user";
    }
}
 

Tworzymy klasyczne kontrollery Spring MVC poprzez adnotacje @RestController i @GetMapping oraz nadajemy odpowiednie dostepy do endpointow poprzez @RolesAllowed, więc czas na testy.

Zacznijmy od pobrania access tokenu z keycloaka dla danego usera, podajac odpowiednie dane, ustawiane wcześniej

curl --location --request POST 'http://localhost:8080/auth/realms/Projekt-testowy-sso/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: JSESSIONID=6B7B9E9100D31DB815B2CE345AF86446.63bcd1903719; JSESSIONID=6B7B9E9100D31DB815B2CE345AF86446' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=spring-app-test' \
--data-urlencode 'client_secret=01822bf2-1177-49fe-a1cf-454b540a65a0' \
--data-urlencode 'username=user-admin' \
--data-urlencode 'password=test' 
http://localhost:8080/auth/realms/Projekt-testowy-sso/protocol/openid-connect/token
Jak widzimy url odwoluje sie do naszego REALMU oraz serwera keycloaka na odp porcie
Zwrotka tak wyglada:
{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJodlZhTlRGYllIU0JHWHhWSEYtcUJ3bTRyVWxwQVB5a19uc0RySEhRWlQ0In0.eyJleHAiOjE2MTU0NjM4MjksImlhdCI6MTYxNTQ2MzUyOSwianRpIjoiOGIwMDZlNTUtMzY0NC00MzgwLTg4OTAtZDdlNWJkNDNhMjQ2IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL1Byb2pla3QtdGVzdG93eS1zc28iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiOTcyOTNkOGItZjdlNi00NmVjLThhMmItZThmNmUyNjMxNmQ1IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic3ByaW5nLWFwcC10ZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6Ijc4M2FlMzc5LWRkZDMtNGQ1Yy1hOWI5LTFjZDc5YjY4NGRhNyIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsic3ByaW5nLWFwcC10ZXN0LWFkbWluIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7InNwcmluZy1hcHAtdGVzdCI6eyJyb2xlcyI6WyJhZG1pbiJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyLWFkbWluIn0.gHZxSfUwyWVxF3uOEIjx505zVe2LW_7PXpSRmNr2UrYkllq2WkR1WxBUpOsxjjzO0iGdCJbJAdYoCMnd1MY-MmhwseKEenfV4v1DxFNaTO1jSppt1oXAV_GMlcFI4_hE9jBD-d7pDR69oQmjYJr3QDbNGyDnKzPMrvDWyl_jatD6ek5Js8ogvFGRhDW7W64x8zaDBvbg47xNE-gbzIGfnglz2GkDbzyw1cE7PvxnncUhMhkNcsCriBeBiztJa4jQQLAL05YvtN1bXC05hab6JIEKYFLqI_a8R2krfFNhD0IGYySq_Jlifh9-JgkB8H25d4AtYUtaS1KcNQWuhbKJ_Q",
    "expires_in": 300,
    "refresh_expires_in": 1800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiNmQ5MWQxYi1kNzllLTQ0MzEtYjRmNS0xMjEwOWZhMTBlZDcifQ.eyJleHAiOjE2MTU0NjUzMjksImlhdCI6MTYxNTQ2MzUyOSwianRpIjoiYTcxNTYzYjItNGE2Zi00ZTVmLTk5ZTYtNDE3ZDA0ZWQ5ZjFjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL1Byb2pla3QtdGVzdG93eS1zc28iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvUHJvamVrdC10ZXN0b3d5LXNzbyIsInN1YiI6Ijk3MjkzZDhiLWY3ZTYtNDZlYy04YTJiLWU4ZjZlMjYzMTZkNSIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJzcHJpbmctYXBwLXRlc3QiLCJzZXNzaW9uX3N0YXRlIjoiNzgzYWUzNzktZGRkMy00ZDVjLWE5YjktMWNkNzliNjg0ZGE3Iiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIn0.hNWAl6xpzzi0ja-qvRVN_a2V0YyNjZONUE_ohX5EEXM",
    "token_type": "Bearer",
    "not-before-policy": 0,
    "session_state": "783ae379-ddd3-4d5c-a9b9-1cd79b684da7",
    "scope": "email profile"
} 
Następnie kopjujemy access_token i wklejamy do headera http, ktory zawiera nasz docelowy request http://localhost:8000/admin
curl --location --request GET 'http://localhost:8000/admin' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJodlZhTlRGYllIU0JHWHhWSEYtcUJ3bTRyVWxwQVB5a19uc0RySEhRWlQ0In0.eyJleHAiOjE2MTU0NjM4MjksImlhdCI6MTYxNTQ2MzUyOSwianRpIjoiOGIwMDZlNTUtMzY0NC00MzgwLTg4OTAtZDdlNWJkNDNhMjQ2IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL1Byb2pla3QtdGVzdG93eS1zc28iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiOTcyOTNkOGItZjdlNi00NmVjLThhMmItZThmNmUyNjMxNmQ1IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic3ByaW5nLWFwcC10ZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6Ijc4M2FlMzc5LWRkZDMtNGQ1Yy1hOWI5LTFjZDc5YjY4NGRhNyIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsic3ByaW5nLWFwcC10ZXN0LWFkbWluIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7InNwcmluZy1hcHAtdGVzdCI6eyJyb2xlcyI6WyJhZG1pbiJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyLWFkbWluIn0.gHZxSfUwyWVxF3uOEIjx505zVe2LW_7PXpSRmNr2UrYkllq2WkR1WxBUpOsxjjzO0iGdCJbJAdYoCMnd1MY-MmhwseKEenfV4v1DxFNaTO1jSppt1oXAV_GMlcFI4_hE9jBD-d7pDR69oQmjYJr3QDbNGyDnKzPMrvDWyl_jatD6ek5Js8ogvFGRhDW7W64x8zaDBvbg47xNE-gbzIGfnglz2GkDbzyw1cE7PvxnncUhMhkNcsCriBeBiztJa4jQQLAL05YvtN1bXC05hab6JIEKYFLqI_a8R2krfFNhD0IGYySq_Jlifh9-JgkB8H25d4AtYUtaS1KcNQWuhbKJ_Q' \
--header 'Cookie: JSESSIONID=7D7D4C5CA8A7ACEC93276BDE350DCF19' 

Caly kodzik znajdziemy na naszym Githubie

Leave a Comment

Image

Launch your rocket!

Leave your email - we'll get back to you as soon as possible!