Keycloak with Spring Boot and Kotlin — Part 2 -Admin Client

1. Introduction

2. Run Keycloak Server

docker run -p 8090:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:11.0.3

3. Create and Configure Admin Client

3.1. Create New Client

Image is a screenshot showing how to create an admin client inside the Keycloak Admin Console.
Image is a screenshot showing how to create an admin client inside the Keycloak Admin Console.

3.2. Configure Access Type

The image shows a screenshot from Keycloak Admin Console showing how to set Access Type for new client.
The image shows a screenshot from Keycloak Admin Console showing how to set Access Type for new client.

3.3. Obtain Client Credentials

Image contains a screenshot from Keycloak client app showing how to get credentials for Keycloak client.
Image contains a screenshot from Keycloak client app showing how to get credentials for Keycloak client.
The above value will be used later in our Spring Boot application to set up the connection.

3.4. Assign Roles to Service Account

The image is a screenshot from Keycloak panel showing how to assign roles to the service account
The image is a screenshot from Keycloak panel showing how to assign roles to the service account

4. Set Up Spring Boot Application

4.1. Imports

implementation("org.keycloak:keycloak-spring-boot-starter:11.0.3")
implementation("org.keycloak:keycloak-admin-client:11.0.3")
implementation("org.springframework.boot:spring-boot-starter-web")

4.2. Configure Application Properties

keycloak:
realm: example
resource: admin-spring-boot
auth-server-url: http://localhost:8090/auth
credentials:
secret: <KEYCLOAK-CLIENT-CREDENTIALS>

4.3. Create Keycloak Client Config

@Configuration
class KeycloakClientConfig(
@Value("\${keycloak.credentials.secret}")
private val secretKey: String,
@Value("\${keycloak.resource}")
private val clientId: String,
@Value("\${keycloak.auth-server-url}")
private val authUrl: String,
@Value("\${keycloak.realm}")
private val realm: String
) {

@Bean
fun keycloak(): Keycloak {
return KeycloakBuilder.builder()
.grantType(CLIENT_CREDENTIALS)
.serverUrl(authUrl)
.realm(realm)
.clientId(clientId)
.clientSecret(secretKey)
.build()
}
}

4.4. Manage Roles

@Service
class RoleService(
private val keycloak: Keycloak,
@Value("\${keycloak.realm}")
private val realm: String
) {
fun create(name: String) {
val role = RoleRepresentation()
role.name = name

keycloak
.realm(realm)
.roles()
.create(role)
}

fun findAll(): List<RoleRepresentation> =
keycloak
.realm(realm)
.roles()
.list()

fun findByName(roleName: String): RoleRepresentation =
keycloak
.realm(realm)
.roles()
.get(roleName)
.toRepresentation()
}
@RestController
@RequestMapping("/api/role")
class RoleController(
private val roleService: RoleService
) {
@GetMapping
fun findAll() =
roleService.findAll()

@PostMapping
fun createRole(@RequestParam name: String) =
roleService.create(name)
}
curl --location --request POST 'localhost:8080/api/role?name=ROLE_EXAMPLE'
curl 'localhost:8080/api/role'
The image shows the view of all roles in Keycloak from Keycloak Admin Console.
The image shows the view of all roles in Keycloak from Keycloak Admin Console.

4.5. Manage Groups

@Service
class GroupService(
private val keycloak: Keycloak,
@Value("\${keycloak.realm}")
private val realm: String
) {
fun create(name: String) {
val group = GroupRepresentation()
group.name = name

keycloak
.realm(realm)
.groups()
.add(group)
}

fun findAll(): List<GroupRepresentation> =
keycloak
.realm(realm)
.groups()
.groups()
}
@RestController
@RequestMapping("/api/group")
class GroupController(
private val groupService: GroupService
) {
@GetMapping
fun findAll() =
groupService.findAll()

@PostMapping
fun createGroup(@RequestParam name: String) =
groupService.create(name)
}
curl --location --request POST 'localhost:8080/api/group?name=example_group'
curl 'localhost:8080/api/group'
Image shows the screenshot from Keycloak Admin Console showing created groups.
Image shows the screenshot from Keycloak Admin Console showing created groups.

4.6. Manage Users

class UserRequest(
val username: String,
val password: String
)
@Service
class UserService(
private val keycloak: Keycloak,
@Value("\${keycloak.realm}")
private val realm: String
) {
fun findAll(): List<UserRepresentation> =
keycloak
.realm(realm)
.users()
.list()

fun findByUsername(username: String): List<UserRepresentation> =
keycloak
.realm(realm)
.users()
.search(username)

fun findById(id: String): UserRepresentation =
keycloak
.realm(realm)
.users()
.get(id)
.toRepresentation()

fun assignToGroup(userId: String, groupId: String) {
keycloak
.realm(realm)
.users()
.get(userId)
.joinGroup(groupId)
}

fun assignRole(userId: String, roleRepresentation: RoleRepresentation) {
keycloak
.realm(realm)
.users()
.get(userId)
.roles()
.realmLevel()
.add(listOf(roleRepresentation))
}

fun create(request: UserRequest): Response {
val password = preparePasswordRepresentation(request.password)
val user = prepareUserRepresentation(request, password)

return keycloak
.realm(realm)
.users()
.create(user)
}

private fun preparePasswordRepresentation(
password: String
): CredentialRepresentation {
val cR = CredentialRepresentation()
cR.isTemporary = false
cR.type = CredentialRepresentation.PASSWORD
cR.value = password
return cR
}

private fun prepareUserRepresentation(
request: UserRequest,
cR: CredentialRepresentation
): UserRepresentation {
val newUser = UserRepresentation()
newUser.username = request.username
newUser.credentials = listOf(cR)
newUser.isEnabled = true
return newUser
}
}
fun create(request: UserRequest): Response {
val password = preparePasswordRepresentation(request.password)
val user = prepareUserRepresentation(request, password)

return keycloak
.realm(realm)
.users()
.create(user)
}

private fun preparePasswordRepresentation(
password: String
): CredentialRepresentation {
val cR = CredentialRepresentation()
cR.isTemporary = false
cR.type = CredentialRepresentation.PASSWORD
cR.value = password
return cR
}

private fun prepareUserRepresentation(
request: UserRequest,
cR: CredentialRepresentation
): UserRepresentation {
val newUser = UserRepresentation()
newUser.username = request.username
newUser.credentials = listOf(cR)
newUser.isEnabled = true
return newUser
}
@RestController
@RequestMapping("/api/user")
class UserController(
private val userService: UserService,
private val roleService: RoleService
) {
@GetMapping
fun findAll() =
userService.findAll()

@GetMapping("/{id}")
fun findById(@PathVariable id: String) =
userService.findById(id)

@GetMapping("/username/{username}")
fun findByUsername(@PathVariable username: String) =
userService.findByUsername(username)

@PostMapping
fun create(@RequestBody userRequest: UserRequest): ResponseEntity<URI> {
val response = userService.create(userRequest)

if (response.status != 201)
throw RuntimeException("User was not created")

return ResponseEntity.created(response.location).build()
}

@PostMapping("/{userId}/group/{groupId}")
fun assignToGroup(
@PathVariable userId: String,
@PathVariable groupId: String
) {
userService.assignToGroup(userId, groupId)
}

@PostMapping("/{userId}/role/{roleName}")
fun assignRole(
@PathVariable userId: String,
@PathVariable roleName: String
) {
val role = roleService.findByName(roleName)

userService.assignRole(userId, role)
}
}
curl -i --location --request POST 'localhost:8080/api/user' \
--header 'Content-Type: application/json' \
--data-raw '{
"username": "user",
"password": "password"
}'
Location: http://localhost:8090/auth/admin/realms/example/users/[CREATED-USER-ID]
The image shows the Users page inside the Keycloak Admin Console.
The image shows the Users page inside the Keycloak Admin Console.
curl --location --request POST 'localhost:8080/api/user/35520db6-3df0-4693-b8b1-f9abc09d7b86/group/7eb8db7a-7693-4df3-b682-d955da67d453'
the image shows the view of user groups.
the image shows the view of user groups.
curl --location --request POST 'localhost:8080/api/user/35520db6-3df0-4693-b8b1-f9abc09d7b86/role/ROLE_EXAMPLE'
the image shows the view of user groups.
the image shows the view of user groups.

5. Summary

Hi, my name is Piotr and I am the founder of Codersee- a technical blog, where I am teaching programming by practical step by step tutorials.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store