Sunday, September 8, 2019

Use Keycloak Admin REST APIs with legacy applications

As I mentioned in the previous post, Keycloak is the most commonly used security framework in modern applications. But somehow you may have to use Keycloak with your legacy applications also. Here I am going to use three Keycloak Admin REST APIs for the following flows.  
  1. Get a Keyclaok bearer token
  2. Check the username exists or not
  3. Create a new user in Keycloak database
Older applications are built using very older frameworks such as Struts and older Java versions. So here I used very basic Java libraries to call Keycloak APIs with simple coding methods. You need to run Keyclaok server and check all configurations such as client id, grant type, port, realm...etc. for more info, please visit Integrate Keycloak with Spring Boot 

Get a Keycloak bearer token


import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Map;

public class GetToken {

    public static void main(String[] args) throws IOException {
        new GetToken().getBearerToken();
    }

    public String getBearerToken() throws IOException{

        String client_id = "login-app";
        String grant_type = "client_credentials";
        String client_secret = "6b574a0b-6624-4d7a-9ac6-485c1fdcc9e0";
        String port = "8081";
        String realm = "api";
        String server = "localhost";
        String content_type = "application/x-www-form-urlencoded";
        String method_type = "POST";
        String encode_type = "UTF-8";

        URL url = new URL("http://"+ server +":"+ port +"/auth/realms/"+ realm +"/protocol/openid-connect/token");

        Map params = new LinkedHashMap();
        params.put("client_id", client_id);
        params.put("grant_type", grant_type);
        params.put("client_secret", client_secret);

        StringBuilder postData = new StringBuilder();
        for(Map.Entry param : params.entrySet()){
            if (postData.length() != 0) postData.append('&');
            postData.append(URLEncoder.encode(param.getKey(), encode_type));
            postData.append('=');
            postData.append(URLEncoder.encode(String.valueOf(param.getValue()), encode_type));
        }

        byte[] postDataBytes = postData.toString().getBytes(encode_type);

        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        conn.setRequestMethod(method_type);
        conn.setRequestProperty("Content-Type", content_type);
        conn.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));
        conn.setDoOutput(true);
        conn.getOutputStream().write(postDataBytes);

        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), encode_type));
        String line;
        StringBuilder result = new StringBuilder();

        while ((line = in.readLine()) != null) {
            result.append(line);
        }
        String response = result.toString();

        String[] array = response.split(",");
        String[] accessToken = array[0].split(":");
        String token = accessToken[1].replace("\"","");

        System.out.println(token);

        return token;
    }
}

Before we use any Keycloak REST API, we need to get the access token to call any Admin REST API. In this class, getBearerToken() method will return only the access token. This token can be used in the Authorization header in the request that you need. I have mentioned all properties in the same method, but according to your application, you can import properties in the way you do it. 



Check the username exists or not


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class CheckUsername {

    public static void main(String args[]) throws IOException {
       new CheckUsername().checkUsername("user1");
    }

    public Boolean checkUsername(String username) throws IOException {

        String port = "8081";
        String realm = "api";
        String server = "localhost";
        String content_type = "application/json";
        String method_type = "GET";
        String encode_type = "UTF-8";
        String bearerToken = new GetToken().getBearerToken();

        URL url = new URL("http://" + server + ":" + port + "/auth/admin/realms/" + realm + "/users?username=" + username);

        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        conn.setRequestMethod(method_type);
        conn.setRequestProperty("Content-Type", content_type);
        conn.setRequestProperty("Authorization", "Bearer " + bearerToken.trim());
        conn.setDoOutput(true);

        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), encode_type));
        String line;
        StringBuilder result = new StringBuilder();

        while ((line = in.readLine()) != null) {
            result.append(line);
        }
        String response = result.toString();

        if(response.contentEquals("[]")){
            return false;
        }else {
            return true;
        }
    }
}

To use this API, we need to get the Keycloak bearer token. In this example, I have called the previous method to get the token (new GetToken().getBearerToken()) and I pass the username as a parameter in URL.


Create a new user in Keycloak database


import com.google.gson.Gson;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;

public class CreateNewUser implements Serializable {

    public static void main(String args[]) throws IOException {
        new CreateNewUser().createUser("user1", "├╝ser1@gmail.com", "user1");
    }


    public int createUser(String username, String email, String password) throws IOException {

        String grant_type = "password";
        String port = "8081";
        String realm = "api";
        String server = "localhost";
        String content_type = "application/json";
        String method_type = "POST";
        String encode_type = "UTF-8";
        String bearerToken = new GetToken().getBearerToken();
        int responseCode = 0;

        ArrayList arrayList = new ArrayList();

        Credentials credentials = new Credentials();
        if (grant_type != null) {
            credentials.setType(grant_type);
        }
        credentials.setTemporary(true);
        if (password != null) {
            credentials.setValue(password);
        }
        arrayList.add(credentials);

        PostBody postBody = new PostBody();
        if (username != null) {
            postBody.setUsername(username);
        }
        if (email != null) {
            postBody.setEmail(email);
        }
        postBody.setCredentials(arrayList);

        try {

            Gson gson = new Gson();
            String jsonString = gson.toJson(postBody);

            URL url = new URL("http://" + server + ":" + port + "/auth/admin/realms/" + realm + "/users/");

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod(method_type);
            conn.setRequestProperty("Content-Type", content_type);
            conn.setRequestProperty("Accept", content_type);
            conn.setDoOutput(true);
            conn.setRequestProperty("Authorization", "Bearer " + bearerToken.trim());

            OutputStream os = conn.getOutputStream();
            byte[] input = jsonString.getBytes(encode_type);
            os.write(input, 0, input.length);


            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), encode_type));
            StringBuilder response = new StringBuilder();
            String responseLine = null;
            while ((responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }
            responseCode = conn.getResponseCode();

            System.out.println(responseCode);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return responseCode;
    }


    public class PostBody implements Serializable {
        private String email;
        private String username;
        private ArrayList credentials;

        public PostBody() {
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(String email) {
            this.email = email;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public ArrayList getCredentials() {
            return credentials;
        }

        public void setCredentials(ArrayList credentials) {
            this.credentials = credentials;
        }
    }

    public class Credentials implements Serializable {
        private String value;
        private Boolean temporary;
        private String type;

        public Credentials() {
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public Boolean getTemporary() {
            return temporary;
        }

        public void setTemporary(Boolean temporary) {
            this.temporary = temporary;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }
    }
}


According to the Keycloak documentation, you can add or change any property you want. Here I use some very simple properties. I used the Gson library, you can use any library which suited to your requirements. 

These are just very simple examples that show, how to use Keycloak REST APIs in your older applications. You can adjust it according to the requirement.  

Please look at the Keycloak official documentation for more information about the Admin REST APIs and more.