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. 


Wednesday, December 12, 2018

Redis clustering for beginners


If you build an enterprise level application, caching is one of the most important areas that you need to consider according to your application rending data volume and transactions per time period. You must be aware of which data to be cached and which data should not be cached.

In this post, I am going to explain, how to create a Redis cluster environment.


Install Redis

You can get the installation details from here https://redis.io/topics/quickstart


wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make

If you are getting errors while make Redis, please try to install build-essential tcl.

sudo apt-get install build-essential tcl 

Configure Redis cluster configuration files

Redis cluster requires 3 master nodes. Then at least you have to create 6 nodes to have 3 master nodes and 3 cluster nodes, one replica per one master node. If you need 2 replicas for one master node, then you have to create 9 nodes. It means 3 master nodes and 2 replicas per master node. Here is one configuration file that can be used in Redis server.


port 7001
cluster-enabled yes
cluster-config-file cluster-node1.config
cluster-node-timeout 5000
appendonly yes
appendfilename node-1.config

dbfilename dump1.rdb


Here I mentioned only the most important configuration details, not all configurations. If you are going to create 6 nodes, you will have to create 6 config files with different portscluster-config-file, appendfilename and dbfilename

Now you have Redis server and 6 configuration files. Look at the following image.



Now you can start Redis servers one by one in different terminals as follows.

redis-server node1.config
redis-server node2.config
redis-server node3.config
....
....
redis-server node6.config




As you can see, I started node1 which is configured in port 7001. Now it is running.


Start Redis clusters

Now we can use Redis-cli commands to start and configure Redis server in cluster mode.

redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 –-cluster-replicas 1

According to this configuration it will create 3 master nodes and 3 replicas. It means one replica per one master node. Once you apply this command, it will show the configuration details and you have to enter "yes" to apply this configuration and start Redis servers. You can login to the node by using the port as redis-cli -c -p 7001 then you can get the node information by using "info". Please look the image below.






There are a set of redis-cli commands. You can find all details about Redis commands from https://redis.io/commands




Friday, August 3, 2018

How to use Rest Template with Spring Boot

This is very simple demonstration which shows how to use Rest Template with Spring Boot. In this example I will create Java APIs using Spring Boot to call another REST API to GET, POST and PATCH data.
To make it done, I'm using another REST application to work as a service. You can clone it using following git repository. This can be any application which creates REST APIs, may be Java, PHP, Python or whatever.
https://github.com/raviyasas/SpringBoot-REST-API-demo.git
This application will run on port 8090. Please read the ReadMe file for API details.
Now I am going to create another Spring Boot application to to call those APIs using Rest Template.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.app</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

User.java

package com.app.demo.model;

import lombok.Data;
import java.io.Serializable;

@Data
public class User implements Serializable {
    private String name;
    private String email;
}

ApiController.java

package com.app.demo.controller;

import com.app.demo.model.*;
import com.app.demo.service.ApiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/v2/users/")
public class ApiController {

    @Autowired
    private ApiService apiService;

    @GetMapping("/getData")
    public List<User> getData(){
        return apiService.getInfo();
    }

    @PostMapping("/postData")
    public User postData(@RequestBody User user){
        return apiService.saveData(user);
    }

    @PatchMapping("/patchData/{id}")
    public User getPatchData(@RequestBody User user, @PathVariable Long id){
        return apiService.patchData(user, id);
    }

    @DeleteMapping("/deleteData/{id}")
    public void deleteData(@PathVariable Long id){
        apiService.deleteData(id);
    }
}


ApiService.java

package com.app.demo.service;

import com.app.demo.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;

@Service
@Transactional
public class ApiService {

    @Autowired
    private RestTemplateBuilder restTemplateBuilder;

    public List<User> getInfo() {

        String url = "http://localhost:8090/api/user/users";

        ResponseEntity<List<User>> result = restTemplateBuilder.build().
                exchange(url,
                        HttpMethod.GET, null, new ParameterizedTypeReference<List<User>>() {});

        List<User> userList = new ArrayList<>();
        userList.addAll(result.getBody());

        return userList;
    }

    public User saveData(User requestUser) {

        String url = "http://localhost:8090/api/user/addUser";

        ResponseEntity<User> result = restTemplateBuilder.build().postForEntity(url, requestUser, User.class);

        User user = new User();
        user.setEmail(result.getBody().getEmail());
        user.setName(result.getBody().getName());

        return user;
    }

    public User patchData(User requestUser, Long id) {

        String url = "http://localhost:8090/api/user/patchUser/" + id;

        Object result = restTemplateBuilder.build().patchForObject(url, requestUser, User.class);

        User user = new User();

        if(result != null) {
            if (requestUser.getName() != null) {
                user.setName(((User) result).getName());
            }
            if (requestUser.getEmail() != null) {
                user.setEmail(((User) result).getEmail());
            }
        }
        return user;
    }

    public void deleteData(Long id) {

        String url = "http://localhost:8090/api/user/deleteUser/" + id;

        try{
            restTemplateBuilder.build().delete(url);
        }catch (Exception e){
            e.getMessage();
        }
    }
}

You can use following curl commands to GET, POST, PATCH and DELETE data. My application is running on port 8088, by default it will run on port 8080. You can change it by setting server.port=8088 in application.properties

Get user data

curl -XGET 'http://localhost:8088/api/v2/users/getData/'


Add new user

curl -XPOST -H "Content-type: application/json" -d '{
    "name": "testUser",
    "email": "testuser@live.com"
}' 'http://localhost:8088/api/v2/users/postData/' 


Update a user

curl -XPATCH -H "Content-type: application/json" -d '{
    "email": "newEmail@live.com"

}' 'http://localhost:8088/api/v2/users/patchData/2'


Remove a user

curl -XDELETE 'http://localhost:8088/api/v2/users/deleteData/2'


You can download the complete project from here.

https://github.com/raviyasas/SpringBoot-RestTemplate-demo.git 



Friday, July 6, 2018

Integrate Keycloak with Spring Security

Keycloak can be very easily integrate with Spring Security. In this post I'm going to demonstrate, How to integrate Keycloak with Spring Security and How it works with Single Sign on.

If you are familiar with Spring Security, I think you may know about the Security Configuration file which extends WebSecurityConfigurerAdapter Similar to this, we can use KeycloakWebSecurityConfigurerAdapter to integrate Keycloak with Spring Security. If you follow Keycloak documentation, you will be able to find the Spring Security Adapter in Securing Apps tab. We have to follow few steps.
  • Add Keycloak Spring Security dependency in pom.xml
  • Add Keycloak configuration file which annotated @KeycloakConfiguration
  • Add Keycloak properties to application.properties file.
Lets create a very simple Spring Boot application as a web client. Here I use FreeMarker templates.

Project Structure




pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.example</groupId>
 <artifactId>product-app</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>product-app</name>
 <description>Demo project for Spring Boot</description>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.3.RELEASE</version>
  <relativePath/>
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
  <keycloak.version>3.4.2.Final</keycloak.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-freemarker</artifactId>
  </dependency>
  <dependency>
   <groupId>org.keycloak</groupId>
   <artifactId>keycloak-spring-boot-starter</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

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

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.keycloak.bom</groupId>
    <artifactId>keycloak-adapter-bom</artifactId>
    <version>${keycloak.version}</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>

</project>


ProductController.java

package com.example.productapp.controller;

import com.example.productapp.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

@Controller
public class ProductController {

    @Autowired
    private ProductService productService;

 @GetMapping(path = "/products")
 public String getProducts(Model model){
  model.addAttribute("products", productService.getProducts());
  return "products";
 }

 @GetMapping(path = "/logout")
 public String logout(HttpServletRequest request) throws ServletException {
  request.logout();
  return "/";
 }
}


ProductService.java

package com.example.productapp.service;

import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;

@Service
public class ProductService{

    public List getProducts(){
        return Arrays.asList("Mazda","Toyota","Audi");
    }
}


SecurityConfig.java

package com.example.productapp.security;

import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

 @Autowired
 public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
  KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
  keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
  auth.authenticationProvider(keycloakAuthenticationProvider);
 }

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

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

 @Override
 protected void configure(HttpSecurity http) throws Exception{
  super.configure(http);
  http.authorizeRequests()
    .antMatchers("/products*").hasRole("user")
    .anyRequest().permitAll();
 }
}


application.properties file

server.port=8090
keycloak.enabled=true
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.realm=api
keycloak.resource=client1
keycloak.public-client=true

index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Home</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
</head>

<body class="text-center">

<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
    <main role="main" class="inner cover">
        <h1 class="cover-heading">Apache Keycloak SSO demonstration</h1>
        <p class="lead">
        </p>
        <p class="lead">
            <a href="/products" class="btn btn-lg btn-secondary">Search products</a>
        </p>
    </main>
</div>

</body>
</html>


products.ftl

<#import "/spring.ftl" as spring>

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Keycloak</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
          integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">

</head>

<body class="text-center">
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">

    <main role="main" class="inner cover">
        <h1 class="cover-heading">Apache Keycloak SSO demonstration</h1>

        <div style="width: 18rem; padding: 10px; display: block; margin-left: auto;margin-right: auto">
            <ul class="list-group">
                <#list products as product>
                    <li class="list-group-item">${product}</li>
                </#list>
            </ul>
        </div>

        <p class="lead">
            <a href="/logout" class="btn btn-lg btn-secondary">Logout</a>
        </p>
    </main>
</div>

</body>
</html>



Configure Keycloak server

I already configured Keycloak in this post. Since this is a client web application, we have to use Access type as public in Keycloak client configuration settings. Public clients do not require a client secret. 

Click to enlarge 

You have to do the same thing here,you can use any Realm name and client name for Keycloak configurations and you can use same data in Keycloak properties in application.properties file.

Run your application

Start the Keycloak server and run Spring Boot application. In my application Keycloak server is running on port 8080 and Spring Boot application is running on port 8090. 





Once you click on Search products, you will be redirected to Keycloak login screen. Because we have secured /products* URL pattern.


Once you provide authentication details, you will be able to access the secured content. 



Now you can see how Keycloak works with Spring Security. Lets see how we can use it on Single Sign On (SSO).

Keycloak with Single Sign On

You can create same kind of another application which is running on another port. application.property file and service file are different.

application.properties file

server.port=8092
keycloak.enabled=true
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.realm=api
keycloak.resource=client2
keycloak.public-client=true


As you can see keycloak.realm is same for both applications (api), but keycloak.resource is different (client1, client2)

ProductService.java

package com.example.productapp.service;

import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;

@Service
public class ProductService{
    public List getProducts(){
        return Arrays.asList("Apple","Sony","Nokia", "Mi");
    }
}

And here Service is also different. Then run this Spring Boot application parallel with last application. Now two applications are running on port 8090 and 8092. Keycloak server is also running on port 8080.

Then you can try http://localhost:8092/products and you will be able to access this secured endpoint without going to Keycloak login page.  




Now you can see how SSO works with Keycloak and Spring Security. Here is the Git repository related to this project.


git clone https://github.com/raviyasas/SpringBoot-Keycloak-SpringSecurity-demo.git




Tuesday, June 26, 2018

Customizing Keycloak themes

When you integrating Keycloak with your web application, you may think to customize the default Keycloak theme. Keycloak templates are written using FreeMarker templates. You can customize it very easily. 

First you need to login as admin.




Go to Realm settings and select Themes tab.




Now you can see some options about themes and language support. There are several options to change the default theme. It can be changed the Login theme, Account theme, Admin console theme…etc. By default, two themes are provided (base and keycloak) for each option.

Before adding new themes

  • Before adding new themes, some configurations need to be done.
  • According to Keycloak documentation theme caching and template caching can be disabled. This allows user to edit themes without restarting the Keycloak server.
  • To change this, go to the standalone directory and then go inside configuration.
                    Keycloak -> standalone -> configuration -> standalone.xml
  • There are several files, but only standalone.xml should be changed. 
  • Please change it as follows


But please remember to re-enable it on production 

Add new themes

  • New themes can be added where Keycloak server is installed.
  • Basically Keycloak server looks like this. Please refer following image.

New themes should be added to themes directory.

  • By default, three Themes are available (base, keycloak and keycloak-preview)
  • Base theme consists of basic template files for each page without styles. It acts as the parent.

  • Keycloak theme extends Base theme and it has some specific styles.
  • Documentation says not to edit preloaded themes, instead create your own theme and extends the base theme according to your requirements.
  • You can change themes for account, admin, email, login and welcome pages. Above directory names should be exactly same. You cannot change those directory names.


Create your own theme

Let’s create a new theme called “myTheme”. In this theme I am going customize the login page. Create a folder inside the Theme directory and that folder name becomes the name of the theme. Here is the flow. Please use exact words for directories and files.
  1. Go to Keycloak then themes
  2. Create a directory called “myTheme”. This will be the theme name.
  3. Go to myTheme directory
  4. Create a directory called login. This specify that you are going to change the login theme. 
  5. Go inside of login directory
  6. Create a directory called resources and create a property file called theme.properties. Resources directory contains all resources files such as styles and images. 
  7. theme.properties file contains property definitions.
  8. Go inside of the resources directory and create two directories called css and img. 


What is theme.property file

theme.property file consists some basic configuration things.
  1. Parent class (parent=keycloak) 
  2. Where style sheets are located (styles=css/styles.css) 
  3. Keycloak tags related to Bootstrap tags (kcHtmlClass=login-pf) 

Here is the sample theme .property file.

parent=keycloak 
import=common/keycloak 

styles=lib/patternfly/css/patternfly.css lib/zocial/zocial.css css/login.css css/logo.css css/styles.css 
kcHtmlClass=login-pf 
kcContentClass=col-sm-12 col-md-12 col-lg-12 container 
kcContentWrapperClass=row 
kcHeaderClass=col-xs-12 col-sm-8 col-md-8 col-lg-7 
kcFeedbackAreaClass=col-md-12 
kcAlertIconClasserror=pficon pficon-error-circle-o 
kcFormAreaClass=col-xs-12 col-sm-8 col-md-8 col-lg-7 login 
kcFeedbackErrorIcon=pficon pficon-error-circle-o

Change styles related to your theme

Inside of the css directory you can create style sheets as you preferred. Once you create a stylesheet, it needs to be mentioned in the theme.properties file.

      styles=css/login.css

Change the template structure
  • If you want to change the structure of the template, you need to change the related FreeMaker template files.
  • There are two ways to change the template.
                     Change the Base template – NOT RECOMMENDED 
                     Override the template and change it – BEST OPTION

  • As I mentioned, ‘override the template’ is the best option.
  • Just what you need to do is, get a copy from template file and save it in your theme location.
  • Then you can edit saved template according to your requirements and you can add new styles as mentioned above.



Friday, May 18, 2018

Integrate Keycloak with Spring Boot


Security, I know this is so boring. But this is must. Years ago we have to expend much more time to implement the security of our enterprise applications, basically I'm talking about Web application security. But nowadays there are some Identity management tools that can be used to make easier our lives.

What is an Identity management tool ?

Simply it can be used as a security provider. We can delegate our security to a third party. It is just like hiring a security person or hiring a bodyguard. 

What is Keycloak ?

This is one of the best tools that can be used as an authentication management tool. Actually it is an identity provider. You can very easily integrate it to your Spring Boot applications and if you want you can integrate it with Spring Security also.

Advantages of Keycloak ?
  • It is light weight and fast.
  • Single sign on
  • It uses standard protocols such as OAuth 2.0, OpenID, SAML 2.0...etc.
  • Can be managed easily. 
  • Very easy to use with Keycloak adapters.
  • Very easy to enable social logins.

Here I'm not going to explain more details about Security concerns. I just wanted to explain the integration of Keycloak with Spring Boot.

First of all lets create a simple web application using Spring Boot. Go through following link to get started with Spring Boot if you are new to Spring Boot.

http://www.javafoundation.xyz/2017/06/spring-boot-hello-world-project.html#.W77OhWgzZnK

Here I'm going to create a simple web application to demonstrate this. 

ReservationController.java

package com.app.api;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

@Controller
public class ReservationController {

    @GetMapping("/")
    public String home(){
        return "index";
    }

    @GetMapping("/reservation")
    public String getReservations(){
        return "reservations";
    }

    @GetMapping("/logout")
    public String logout(HttpServletRequest request) throws ServletException {
        request.logout();
        return "index";
    }
}


index.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>

    <h1>Home page</h1>
    <a href="/reservation">My Reservations</a>
</body>
</html>


reservations.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Reservations</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>

    <h2>Reservations page</h2>

    <a href="/logout">Logout</a>

</body>
</html>


You can download the full project from here.

https://github.com/raviyasas/keycloak-api-parent.git

I changed the default server port to 8090 in application.properties file. Just go to following links and check your application is working or not. 

http://localhost:8090/
http://localhost:8090/reservation

How to use Keycloak server
  • Download Keycloak server on https://www.keycloak.org/downloads.html then download the Standalone server distribution zip file.


  • Unzip it in your machine.
  • Go to bin folder. If you are on Windows OS just you can run standalone.bat file or if you are using a Linux distribution, you may use standalone.sh file to start the server. 
  • Normally it will run on port 8080. But you can change it as your wish, go to standalone\configuration\standalone.xml and change the port.
  • Once you run the server, go to http://localhost:8080/auth/

You will be able to see as follows.


This is to create an admin user. You can give any username and password you like. Once you create an admin user, you will be redirected to following page. 


You can see the red square "Administration Console". Just click it and then you will be redirected to following page which asks your admin credentials for the admin you've already created.



Once you are logged, then you can see below page.


Then you have to create a Realm. Just point your mouse to left side and then you will be able to see a button like below image.


Then you can create a realm by giving a realm name. 


Once you create a realm you will be able to see following page.



Then you need to create a client for you application. Click on Client in the left menu.


Then you can give any name for Client ID, I'm using it as "reservations". Once you created a client, scroll down and then you will be able to see "Valid redirect URIs".


It is the entry point to your application after you logged in to it. You may use the port which your Spring Boot application is running. My application is running on port 8090.

Then you have to add new Role. Go to Roles in the menu and click on Add Role.




Once you click, you will be able to see like this.


Add role name, I use it as "user". We need to use this role in our Spring Boot application. Once you save your role, Click again on Roles in menu. Then go to Default Roles.


Then you need to add your role to Default Roles. Select your role and click Add selected.

Now you can create a new user. Click on Users in the menu.


Then click on Add users and give an username and click save. I used it as "client".


Once you saved, just go to Credentials tab in your user and reset your password. Turn off temporary tab for the password. 

Now everything is done on Keycloak configuration side. Ready to go with Spring Boot app.

Configure Spring Boot application

It is actually simple application. What you need to do is, add Keycloak dependencies to your pom.xml and add configurations to application.properties file. 

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.app</groupId>
 <artifactId>api</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>api</name>
 <description>Demo project for Spring Boot</description>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.14.BUILD-SNAPSHOT</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
  <keycloak.version>3.4.2.Final</keycloak.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
   <groupId>org.keycloak</groupId>
   <artifactId>keycloak-spring-boot-starter</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.keycloak.bom</groupId>
    <artifactId>keycloak-adapter-bom</artifactId>
    <version>${keycloak.version}</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>

 <repositories>
  <repository>
   <id>spring-snapshots</id>
   <name>Spring Snapshots</name>
   <url>https://repo.spring.io/snapshot</url>
   <snapshots>
    <enabled>true</enabled>
   </snapshots>
  </repository>
  <repository>
   <id>spring-milestones</id>
   <name>Spring Milestones</name>
   <url>https://repo.spring.io/milestone</url>
   <snapshots>
    <enabled>false</enabled>
   </snapshots>
  </repository>
 </repositories>

 <pluginRepositories>
  <pluginRepository>
   <id>spring-snapshots</id>
   <name>Spring Snapshots</name>
   <url>https://repo.spring.io/snapshot</url>
   <snapshots>
    <enabled>true</enabled>
   </snapshots>
  </pluginRepository>
  <pluginRepository>
   <id>spring-milestones</id>
   <name>Spring Milestones</name>
   <url>https://repo.spring.io/milestone</url>
   <snapshots>
    <enabled>false</enabled>
   </snapshots>
  </pluginRepository>
 </pluginRepositories>


</project>


application.properties

#server port of Spring Boot application is running
server.port=8090

#specify the url that keycloak server is running
keycloak.auth-server-url=http://localhost:8080/auth

#realm name which you used when you configured keycloak
keycloak.realm=api
keycloak.public-client=true
#client name you created
keycloak.resource=reservations

#created user, when you configured keycloak
keycloak.security-constraints[0].auth-roles[0]=user
#url pattern needs to be secured
keycloak.security-constraints[0].security-collections[0].patterns[0]=/reservation/*


This is a simple example of Spring Boot and Keycloak integration. You will be able to see more details, if you can go to Realm settings and then Login tab. Then you can very easily add functionalities for User registration, Remember me, Forgot password, Verify email...etc. You can download the full Spring Boot project from below link.

https://github.com/raviyasas/SpringBoot-Keycloak-demo.git