Tutorial Implementasi OAuth2 menggunakan Keycloak pada Java Spring Boot #Part II (Spring Boot Implementasi)

Septian Reza Andrianto
4 min readJul 19, 2024

--

Hallo teman-teman semua, akhirnya bisa ngelanjutin materinya nih, belum yang baca tutorial #Part I nya, bisa baca disini ya https://medium.com/@septianrezaa/tutorial-implementasi-oauth2-menggunakan-keycloak-pada-java-spring-boot-part-i-keyloak-347d103962b2.
Pada part ini, kita akan pelajari cara mengimplementasikan keycloak yang telah berhasil kita konfigurasi dengan project spring boot kita.

Langsung saja, seperti biasa untuk project Spring Bootnya sendiri, bisa kita generate pada https://start.spring.io/.Disini saya menggunakan Maven, dengan versi Spring Boot 3.3.2, dengan Java 21, dan juga untuk dependency nya yang kita gunakan adalah :
1. Spring Boot DevTools,
2. Spring Web,
3. Lombok,
4. Spring Security,
5. OAuth2 Resource Server.
Setelah itu langsung saya kita klik button Generate.

Untuk structure projectnya sendiri kurang lebih seperti ini.

Langkah yang pertama kita lakukan adalah kita setting confignya pada file application.yml.
Jangan lupa untuk aplikasinya harus running di port 8085, karena port tersebut yang kita daftarkan pada keycloak,
untuk value issuer-uri dan jwk-set-uri kita bisa dapatkan dari endpoint berikut ini, http://localhost:8080/realms/belajar/.well-known/openid-configuration.

Selanjutnya kita buat package config kemudian dalam package tersebut kita buat file dengan nama WebSecurityConfig.java,
Pada config ini kita disable csrf,
kemudian kita authenticate semua request yang masuk,
untuk sessioManagementnya kita pilih STATELESS,

Kemudian dalam package config kita buat sebuah class baru dengan nama JwtAuthConverter.java, class ini dibuat karena pada defaultnya pada spirng security untuk rolenya terdapat ROLE_.

package com.rnd.belajar_keycloak.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {

@Value("${jwt.auth.converter.resource-id}")
private String resourceId;
@Value("${jwt.auth.converter.principle-attribute}")
private String principleAttribute;

private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();

@Override
public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {

Collection<GrantedAuthority> authorities = Stream.concat(jwtGrantedAuthoritiesConverter.convert(jwt).stream(),
extractResourceRoles(jwt).stream()).collect(Collectors.toSet());
return new JwtAuthenticationToken(jwt, authorities,
getPrincipalName(jwt));
}

private String getPrincipalName(Jwt jwt) {
String claimName = JwtClaimNames.SUB;
if (Objects.nonNull(principleAttribute)) {
claimName = principleAttribute;
}
return jwt.getClaim(claimName);

}

private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
Map<String, Object> resourceAccess;
Map<String, Object> resource;
Collection<String> resourceRoles;

if (Objects.isNull(jwt.getClaim("resource_access"))) {
return Set.of();
}

resourceAccess = jwt.getClaim("resource_access");
if (Objects.isNull(resourceAccess.get(resourceId))) {
return Set.of();
}
resource = (Map<String, Object>) resourceAccess.get(resourceId);
resourceRoles = (Collection<String>) resource.get("roles");
return resourceRoles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
}

Setelah itu kita buat Controllernya dengan nama TestController.java,
Didalam controller ini saya buat 2 buat endpoint, yaitu :
1. doTestUser(), dimana endpoint ini hanya bisa diakses oleh user yang rolenya sebagai client_user.
2. doTestAdmin(), dimana endpoint ini hanya bisa diakses oleh user yang rolenya sebagai client_admin.

Langsung saja kita test menggunakan Postman, disini pertama saya akan hit endpoint /token, untuk mendapatkan accessToken.
Pada responsenya terdapat attribute access_token, access_token tersebutlah yang akan kita gunkan sebagai berarer token, ketika ingin hit endpoint doTestUser() dan doTestAdmin().

Berikut untuk cURL nya :
curl — location ‘http://localhost:8080/realms/belajar/protocol/openid-connect/token' \
— header ‘Content-Type: application/x-www-form-urlencoded’ \
— data-urlencode ‘grant_type=password’ \
— data-urlencode ‘client_id=belajar-keycloak’ \
— data-urlencode ‘username=reza’ \
— data-urlencode ‘password=reza’

Selanjutnya kita coba hit endpoint doTestAdmin(), tanpa memasukan token kedalam header. Jika dilihat responsenya adalah 401. yang artinya user tersebut tidak sah mengakses endpoint tersebut.
Berikut cURL nya:
curl — location ‘http://localhost:8085/test/doTestAdmin' \
— header ‘Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxMmxNd0VIMHU0SElvTEVKMHZiVlJpaVM3M2xaeUZTWDlZMGtRZ0xMV3pVIn0.eyJleHAiOjE3MjE0MTI4MTIsImlhdCI6MTcyMTQxMjUxMiwianRpIjoiOGM0NzEwMTctYzFhOC00YmNlLWE4ZjAtNDdjYmYwZmNhM2I3IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9iZWxhamFyIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImVkNmEwYTk0LWQyNzMtNDQ4ZC04YWVkLTBjZDkwNjYzMmY2NyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImJlbGFqYXIta2V5Y2xvYWsiLCJzaWQiOiJjMmUyMDIzMy1hMGEyLTRlMzEtYjQyNy03MWVhZDgwNzdlNWMiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwiYWRtaW4iLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYmVsYWphciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImJlbGFqYXIta2V5Y2xvYWsiOnsicm9sZXMiOlsiY2xpZW50X2FkbWluIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJTZXB0aWFuIFJlemEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJyZXphIiwiZ2l2ZW5fbmFtZSI6IlNlcHRpYW4iLCJmYW1pbHlfbmFtZSI6IlJlemEiLCJlbWFpbCI6InJlemFAdGVzdC5jb20ifQ.OJEb0RvdP8uuYKLDYmCedDU_NF51auORuS7Uao3tiRL63bM6Tzlf-rjge07pj11KaLEtSEzSRZdWWQRqirsG3VSQTqFLVqezxKpBx7s6ue8rARLR-a7Is6UGsqRi-KxYQUS_4Kl35AUJDwTX4cmd68idUHR8Hje1lrIsdBhKfPvb3P-71s7tTjdPk3TzFrz0Np1X0tAsnCcvjZ57hc2MII_MOMBxmrNjoPTzSMhkbLuu1VvbuT1oxxnjU8EkfmoyH4q-ZWSKGj9q8p5y_a9h6imM2nvgE8JT4G4n9N37Nir8ugfJs7kv2sNJuEYJ6HKQQczY9b4xQj2PwgkGJzmwMA’

Selanjutnya kita coba akses endpoint doTestAdmin() dengan memasukan token pada headernya. Sekraang kita mendapatkan responseCode 200, yang artnya kita telah berhasil mengakses endpoint tersebut.

Sekarrang kita coba endpoint doTestUser(), disini kita mendapatkan response 403 / forbidden. User ini dilarang mengkases endpoint doTestUser(), karena endpoint ini hanya boleh dikases dengan user yang memiliki role client_user.

Sedangkan kalau kita cek pada https://jwt.io/, access_token dari user yang kita pakai tersebut adalah user dengan role client_admin.

Cukup sekian tutorial singkat ini dibuat, semoga bermanfaat.
Jika klian bingung kalian bisa liat full source codenya di github.

Terima kasih.

Github Link: https://github.com/septianrezaandrianto/rnd-keycloak

--

--

Septian Reza Andrianto

I'm a Software Engineer with more than three years experiences