Users and roles management using Keycloak Java APIs
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(); } } |
No comments:
Post a Comment