Users and roles management using Keycloak Java APIs

Users and roles management using Keycloak Java APIs

150 150 ioan.surariu

Intro

The scope of this article is to offer an introduction to the Keycloack Java API. I’m using a Spring Boot application where I created a REST controller which performs basic user management operations. The source code of the project could be found on GitHub at https://github.com/ioansurariu/keycloak

My application in Maven driven and it uses Spring Security and Spring Keycloak starters as dependencies therefore your are going to see into the project pom.xml file the dependency structure as below. I’m using Keycloak version 11.0.0

The application plays the role of a Keycloak client and it uses keycloak-admin-client library to connect to the Keycloak Admin REST API. The full documentation for the REST API could be found here.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
	<groupId>org.keycloak</groupId>
	<artifactId>keycloak-spring-boot-starter</artifactId>
	<version>${keycloak.version}</version>
</dependency>

<dependency>
	<groupId>org.keycloak</groupId>
	<artifactId>keycloak-admin-client</artifactId>
	<version>${keycloak.version}</version>
</dependency>

Configure the Spring Boot app and run it

To use the app you need Maven and a Keycloak server running. If you don’t have already a Keycloak server just read my article where I described how to get started using Docker.

In order to run the app just compile it with Maven and then simply shoot the command

./mvnw spring-boot:run

In the application.yaml file you find the app configuration, including the port number where the embedded Tomcat web-server starts (8888 in my case) and the Keycloak client configuration.

For the Keycloak client configuration it’s mandatory to specify the realm (IS Test), the address where it runs (http://192.168.56.105:8090/auth) and the resource (is-keycloak). The resource actually corresponds to the client name you created under your realm to allow external applications to use Keycloak as an authentication point.

# General setting
is:
  keycloak:
    admin:
      user: admin
      password: admin
server:
  port: 8888

# Keycloak settings
keycloak:
  realm: IS Test
  auth-server-url: http://192.168.56.105:8090/auth
  ssl-required: none
  resource: is-keycloak
  use-resource-role-mappings: true
  bearer-only: true
  cors: true
  principal-attribute: preferred_username

Spring Security web configuration

Since my application uses Spring Security it requires a web security configuration class that extends WebSecurityConfigurerAdapter. Keycloak provides KeycloakWebSecurityConfigurerAdapter as a convenient base class for this.

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SpringSecurity extends KeycloakWebSecurityConfigurerAdapter {
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider authenticationProvider = keycloakAuthenticationProvider();
        authenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(authenticationProvider);
    }

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

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.cors().and().csrf().disable();
        http.authorizeRequests()
                .anyRequest()
                .permitAll();
    }
}

I won’t go into too much details but it worth mentioning that method configureGlobal() enables SimpleAuthorityMapper class to make sure the users’ roles are not prefixed with keyword ‘ROLE_’.

The another method, keycloakConfigResolver defines that we want to use the Spring Boot properties file support instead of the default keycloak.json. Therefore you could find all the Keycloak configuration under applicaiton.yaml file for my project.

Finally, method sesessionAuthenticationStrategy() indicates that all the sessions are registered on the Spring security context and configure() says that all the HTTP requests are permitted in order to ease the setup.

Java examples for Keycloak Admin REST API

In src/main/java/ro/surariu/ioan/controller/KeycloakController.java class you find a REST controller with several endpoints which exemplifies how to use the Keycloak Admin REST API to control user management or roles.

Create user

@PostMapping("/user")
public ResponseEntity<String> createUser(@RequestParam String username, @RequestParam String password) {
	if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
		return ResponseEntity.badRequest().body("Empty username or password");
	}
	CredentialRepresentation credentials = new CredentialRepresentation();
	credentials.setType(CredentialRepresentation.PASSWORD);
	credentials.setValue(password);
	credentials.setTemporary(false);
	UserRepresentation userRepresentation = new UserRepresentation();
	userRepresentation.setUsername(username);
	userRepresentation.setCredentials(Arrays.asList(credentials));
	userRepresentation.setEnabled(true);
	Map<String, List<String>> attributes = new HashMap<>();
	attributes.put("description", Arrays.asList("A test user"));
	userRepresentation.setAttributes(attributes);
	Keycloak keycloak = getKeycloakInstance();
	Response result = keycloak.realm(keycloakRealm).users().create(userRepresentation);
	return new ResponseEntity<>(HttpStatus.valueOf(result.getStatus()));
}

Retrieve the list of users

@GetMapping("/users")
public List<UserRepresentation> getUsers() {
	Keycloak keycloak = getKeycloakInstance();
	List<UserRepresentation> userRepresentations = keycloak.realm(keycloakRealm).users().list();
	return userRepresentations;
}

Update user

@PutMapping("/user")
public ResponseEntity<UserRepresentation> updateUserDescriptionAttribute(@RequestParam String username,
																		 @RequestParam String description) {
	Keycloak keycloak = getKeycloakInstance();
	Optional<UserRepresentation> user = keycloak.realm(keycloakRealm).users().search(username).stream()
			.filter(u -> u.getUsername().equals(username)).findFirst();
	if (user.isPresent()) {
		UserRepresentation userRepresentation = user.get();
		UserResource userResource = keycloak.realm(keycloakRealm).users().get(userRepresentation.getId());
		Map<String, List<String>> attributes = new HashMap<>();
		attributes.put("description", Arrays.asList(description));
		userRepresentation.setAttributes(attributes);
		userResource.update(userRepresentation);
		return ResponseEntity.ok().body(userRepresentation);
	} else {
		return ResponseEntity.badRequest().build();
	}
}

Delete user

@DeleteMapping("/user")
public void deleteUser(String username) {
	Keycloak keycloak = getKeycloakInstance();
        UsersResource users = keycloak.realm(keycloakRealm).users();
        users.search(username).stream()
                .forEach(user -> keycloak.realm(keycloakRealm).users().delete(user.getId()));
}

Get the client roles

@GetMapping("/roles")
public ResponseEntity<List<RoleRepresentation>> getRoles() {
	Keycloak keycloak = getKeycloakInstance();
	ClientRepresentation clientRepresentation = keycloak.realm(keycloakRealm).clients().findByClientId(keycloakClient).get(0);
	List<RoleRepresentation> roles = keycloak.realm(keycloakRealm).clients().get(clientRepresentation.getId()).roles().list();
	return ResponseEntity.ok(roles);
}

Get roles by username

@GetMapping("/roles-by-user")
public ResponseEntity<List<RoleRepresentation>> getRolesByUser(@RequestParam String username) {
	Keycloak keycloak = getKeycloakInstance();
	Optional<UserRepresentation> user = keycloak.realm(keycloakRealm).users().search(username).stream()
			.filter(u -> u.getUsername().equals(username)).findFirst();
	if (user.isPresent()) {
		UserRepresentation userRepresentation = user.get();
		UserResource userResource = keycloak.realm(keycloakRealm).users().get(userRepresentation.getId());
		ClientRepresentation clientRepresentation = keycloak.realm(keycloakRealm).clients().findByClientId(keycloakClient).get(0);
		List<RoleRepresentation> roles = userResource.roles().clientLevel(clientRepresentation.getId()).listAll();
		return ResponseEntity.ok(roles);
	} else {
		return ResponseEntity.badRequest().build();
	}
}