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();
}
}
Do you want to avoid your applications to deal with login forms, authenticating and managing users? Then an identity and access management (IAM) solution is what you need. Keycloak is an open source IAM widely used platform which can add authentication and authorization to applications with minimum effort. Features as social login, user federation, identity brokering are available out of the shelf.
In this article I’m going to describe which is the minimum configuration needed to bring Keycloak to life with Docker or docker-compose, and finally have your own development instance to start playing with. You will be able to setup Keycloak with a realm, create clients and users, authenticate them and generate JWT access tokens.
It is assumed that you have Docker and docker-compose already installed and ready to use.
Start Keycloak using Docker or doker-compose
One alternative to start Keycloak fast is to use the containerized Docker version. For example, you could start Keycloak using the command below and we’ll have a running instance right the way.
docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:11.0.2
Keycloak v11.0.2 starts on port 8080 and then you could access to the HTTP administrative console and login with admin/admin credentials. Assuming that you install Keycloak on your localhost
then the console is accessible at http://localhost:8080/auth. That’s all.
However, Keycload starts by default with H2 as internal database which is not recommended for production, therefore below you can find another example with a docker-compose YAML file where Keycloak is configured to start with postgres as underlying DB. Just bring the docker-compose YAML file up and the two containers with Keycloak and Postgres will spin up.
docker-compose up -d
Following this link you can find other examples for MariaDB, Oracle or MSSQL databases.
version: '3'
volumes:
postgres_data:
driver: local
services:
postgres:
image: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: password
keycloak:
image: quay.io/keycloak/keycloak:latest
environment:
DB_VENDOR: POSTGRES
DB_ADDR: postgres
DB_DATABASE: keycloak
DB_USER: keycloak
DB_SCHEMA: public
DB_PASSWORD: password
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
ports:
- 8080:8080
depends_on:
- postgres
Configure Keycloak
Create a Realm
Assuming your Keycloak instance runs on localhost, login to the Administrative Console at http://localhost:8080/auth then create a Realm. In Keycloak terminology a Ream is similar to a tenant. A realm groups a set of users, roles and settings.
Add a client
In order to make your external applications to use Keycloak to secure themselves then it’s needed to create a client. Clients are entities that can request Keycloak to authenticate a user. Go to Clients main menu and then click Create.
You have to provide a client ID (is-keyloak in my example) and pick openid-connect as authentication protocol. More info about OpenID connect here
Then you have to change the Access Type to confidential from the dropdown box and click ON
buttons for Authorization and Service Accounts fields. The last one will allow your client to authenticate to Keycloak and retrieve access tokens dedicated to this client.
Add a user
The last think you need before starting playing around is to create a user. Users are entities that are able to log into your application. They can have attributes associated with themselves like email, username, address, phone number, and birth day. They can be assigned group membership and have specific roles assigned to them.
Go to Users menu and then click on Add User
Type a username and then click Save. As username you’re not forces to use and email or something else. Just pick whatever is convenient for you and go ahead with the credentials.
From the Credentials tab set a password for your user and click OFF
on temporary to avoid changing the password after the first login. Finally click Set Password button and you’re good to go.
Test the connectivity with CURL
You can check now if the user you’ve just created can authenticate into Keycloak. For that you could use CURL. You need to send a POST request using the format below. Note that the URL contains the realm name.
curl --request POST '<KEYCLOAK_SERVER_URL>/auth/realms/<REALM_NAME>/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<CLIENT_ID>' \
--data-urlencode 'client_secret=<CLIENT_SECRET>' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=<USERNAME>' \
--data-urlencode 'password=<PASSWORD>'
Here is an concrete example for my setup. The client secret is could be found under Credentials section for your client.
curl --request POST 'http://192.168.56.105:8090/auth/realms/IS%20Test/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=is-keycloak' \
--data-urlencode 'client_secret=cff1bdcd-05e7-4862-a670-2a8b79a7721c' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=ioans' \
--data-urlencode 'password=secret'
If everything goes fine you get a 200 OK response with the JWT access token as part of the response. See an example below:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxWU80anAzSGlSLXEtZC1jbHhpcDdjWVE5QlVZYl8yd2o4cUNVVUNCUTVJIn0.eyJleHAiOjE2MDI1OTMwNTUsImlhdCI6MTYwMjU5Mjc1NSwianRpIjoiYzg0ZWYwMjQtYzdhZi00YTUzLWJjM2ItZmQ3NzAzZTMxNmRlIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNTYuMTA1OjgwOTAvYXV0aC9yZWFsbXMvSVMlMjBUZXN0IiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjQ3NjBkOTA5LTE5MzMtNDRiZC1iZGM2LWVhYWExN2JiYTY2MSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImlzLWtleWNsb2FrIiwic2Vzc2lvbl9zdGF0ZSI6IjZmNzI5ZGY4LTlhMzktNGY5OS1iMzJmLWI2ODhjZWE2NGQ1YyIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiaW9hbnMifQ.XfDwgMdduCm3kJrX6uRXYf0cEQaByIAIGnCACIt28x-JgcR3Xaqnn-x3E58pvUw4S8Ph8v9R7k-VvZgaBjRay0bzMTyvou_SED1HFTUINnMflQGx3GNlGSIQzs8gHwY18rSDi8OZulmSc4kuwCKL-Qh3X8B894HWJXnoIne33VTc2H8HEDPhD4j1QSq9Krsc7AeZSDhCN4VAR7F6leXuhWJ-H1UzpXq4wS0IlBYD8dBtehHRWgp4Il38RPJnk9fJQuMLUECElrXSwTvlx_iMDQVTytGBklKeq9wwDKiXKp44WtWtZnjVUpLZ51fbVR3iBa95y1zfaROU7mki-34bJQ",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxYmNiYzM1ZS03MTI3LTQ4M2ItYmE1Yy1jYTZkMzJjYWMxNGIifQ.eyJleHAiOjE2MDI1OTQ1NTUsImlhdCI6MTYwMjU5Mjc1NSwianRpIjoiMGVmNWE4YjYtNDNiZi00NDQzLWI0ZjMtNjgxMjE3YjhlMzIxIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNTYuMTA1OjgwOTAvYXV0aC9yZWFsbXMvSVMlMjBUZXN0IiwiYXVkIjoiaHR0cDovLzE5Mi4xNjguNTYuMTA1OjgwOTAvYXV0aC9yZWFsbXMvSVMlMjBUZXN0Iiwic3ViIjoiNDc2MGQ5MDktMTkzMy00NGJkLWJkYzYtZWFhYTE3YmJhNjYxIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6ImlzLWtleWNsb2FrIiwic2Vzc2lvbl9zdGF0ZSI6IjZmNzI5ZGY4LTlhMzktNGY5OS1iMzJmLWI2ODhjZWE2NGQ1YyIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSJ9.igm8BHoVr43BOCvUQlMO4Z9vlJgEVya81o4doQGYhRo",
"token_type": "bearer",
"not-before-policy": 0,
"session_state": "6f729df8-9a39-4f99-b32f-b688cea64d5c",
"scope": "email profile"
}
On jwt.io website you could decode the response JWT access token and see details like the expiration time, issuer, users roles etc.
Hope this article helped you to quickly setup a basic configuration and understand the necessary steps to integrate your clients with Keycloak.
You can use SSH protocol to connect and authenticate to remote GitHub servers. Using SSH keys you can authenticate to GitHub without providing your credentials every time you connect.
It’s common that you need to connect to different GitHub servers from the same computer simultaneously. It could be your personal account to github.com
and the one from work, like work.github.com
or any other remote GitHub server. Anyway, how to configure your SSH client to support this setup it’s not so straight forward. I’m describing below the steps to follow to accomplish this.
Generate a new SSH for your GitHub account
From Git Bash generate a new public/private RSA key using ssh-keygen as below. You need to provide the email address for your GitHub account.
$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
When you’re prompted, enter a file name where you want to save the keys and a secure passphrase. Let’s say you generate a key for your personal GitHub account and name it id_rsa_personal. Public (id_rsa_personal.pub
) and private (id_rsa_personal
) keys are generated. Then upload them to your GitHub server.
More details about generating SSH keys and how to add new SSH key to your GitHub account here.
Add the new SSH keys to .ssh home folder
Copy your newly SSH keys to ~/.ssh
folder. Assuming you generated a key for your personal account in GitHub and that you already have another one for your work account, you should have under ~/.ssh folder 4 files.
id_rsa_personal.pub
id_rsa_personal
id_rsa.pub
id_rsa
Create a SSH config file
If you don’t have it already create a ~/.ssh/config
file and add the following to it. I still assume that you have 2 accounts, the personal one and the other for work. As shown below you need to provide the host addresses and usernames for your accounts. It is also important to specify the right SSH keys to be used for each account using IdentityFile
property (highlighted below).
# Work account
Host work github.work.com
HostName github.work.com
IdentityFile ~/.ssh/id_rsa
User work_user
# Personal account
Host personal github.com
HostName github.com
IdentityFile ~/.ssh/id_rsa_personal
User personal_user
Check the connectivity and troubleshoot
In order to check the connectivity to your GitHub servers using SSH-RSA keys run the command below.
ssh -vT git@github.com
It runs in verbose mode just to provide you all the details in case you need to troubleshoot later. You need to specify the GitHub host, in my case github.com
and git
as username. Don’t use your GitHub account username, it won’t work. Just git
as above.
If you get the message You've successfully authenticated, but GitHub does not provide shell access.
then you’re all good. Your setup is correct and you could carry on to clone repositories or interact with your GitHub remotes using RSA keys.
In case the command returns git@github.com: Permission denied (publickey)
then check once again your SSH config file and make sure the right RSA key is is used for your server.
OpenSSH_7.9p1, OpenSSL 1.1.1a 20 Nov 2018
debug1: Reading configuration data ~/.ssh/config
...
debug1: Host 'github.com' is known and matches the RSA host key.
debug1: Found key in ~/.ssh/known_hosts:4
debug1: rekey after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: rekey after 134217728 blocks
...
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Offering public key: ~/.ssh/id_rsa_personal RSA SHA256:9lQpW+Fadq8+ORpWHFXsYJsD0p8r explicit
debug1: Server accepts key: ~/.ssh/id_rsa_personal RSA SHA256:9lQpW+Fadq8+ORpWHFXsYJsD0p8rv explicit
debug1: Authentication succeeded (publickey).
...
You've successfully authenticated, but GitHub does not provide shell access.
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: channel 0: free: client-session, nchannels 1
Transferred: sent 3488, received 2724 bytes, in 0.2 seconds
Bytes per second: sent 15908.8, received 12424.2
debug1: Exit status 1