Sunday, August 8, 2021

OAuth 2.0 Authorization code flow (Spring boot source code)

In this tutorial we will be implementing the Client Application and the Resource Server. The flow we will be implementing is as follows -



source code demohttps://github.com/WeLook-team/nguyen-giang-tip.blogspot.com/tree/main/Spring/security/oauth2/authorization_code

  • The Resource Owner will ask the Client Application to get some data from the Resource Server.
  • The Resource Server asks the Resource Owner to authenticate itself and as for authorization to share data.
  • After successful authentication the Resource Server shares an authorization code with the client application
In the next tutorial we will see how using the authorization code
boot-40_8



Resource Server Application
In a previous tutorial we had implemented an Application with Simple Login Page using Spring Boot Security. We will quickly create a similar project which will authenticate and return json data. Also we will be configuring the authorization server. The Maven project will be as follows -

boot-401_1
The pom.xml will have added the spring-security-oauth2 dependency


<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.oauth</groupId>
    <artifactId>boot-sec</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>boot-resource-server</name>
    <description>Demo project for Spring Boot OAuth</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.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.security.oauth</groupId>
            <artifactId>spring-security-oauth2</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-security</artifactId>
        </dependency>

    </dependencies>

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

</project>



Define the Spring Boot bootstrap class having the SpringBootApplication annotation.
package com.javainuse;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootResourceServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootResourceServerApplication.class, args);
    }
}
Define the model class Employee. We will be return this model class as JSON response.
package com.javainuse.model;

public class Employee {

    private String empId;
    private String empName;

    public String getEmpId() {
        return empId;
    }

    public void setEmpId(String empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    @Override
    public String toString() {
        return "Employee [empId=" + empId + ", empName=" + empName + "]";
    }

}
Define the controller that exposes a GET REST endpoint to return JSON as response.
package com.javainuse.controllers;

import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.javainuse.model.Employee;

@Controller
public class EmployeeController {

    @RequestMapping(value = "/user/getEmployeesList", produces = "application/json")
    @ResponseBody
    public List<Employee> getEmployeesList() {
        List<Employee> employees = new ArrayList<>();
        Employee emp = new Employee();
        emp.setEmpId("emp1");
        emp.setEmpName("emp1");
        employees.add(emp);
        return employees;

    }

}
Finally we will be configuring security. In this configuration we specify which urls are to be intercepted, and are to be accessed by which users and having which roles.
package com.javainuse.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class EmployeeSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/user/getEmployeesList")
            .hasAnyRole("ADMIN").anyRequest().authenticated().and().formLogin()
            .permitAll().and().logout().permitAll();

        http.csrf().disable();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationMgr) throws Exception {
        authenticationMgr.inMemoryAuthentication().withUser("admin").password("admin")
            .authorities("ROLE_ADMIN");
    }
}


Next we configure an authorization server using EnableAuthorizationServer annotation.
The server is customized by extending the class AuthorizationServerConfigurerAdapter which provides empty method implementations for the interface AuthorizationServerConfigurer.
The authorization server does not secure the authorization end point i.e. /oauth/authorize. The configure method here injects the Spring Security authentication manager.
Using in memory client service we setup the clients that can access the server.
package com.javainuse.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("javainuse").secret("secret").authorizedGrantTypes("authorization_code")
            .scopes("read").authorities("CLIENT");
    }
}
  • Client Application
    We will create the client application. This application will ask the resource server we created above for JSON data.
    As explained previously we have assumed that this Client Application is already registered to the Resource Server, and has got the client id as 'javainuse' and secret key as 'secret'
    According to OAuth spec, it should ask for authorization at the default uri /authorize.
    We can change this default uri according to the requirement but we will be using the default one only in this example.
    Along with the default uri we should also send the following parameters.
    • response_type - REQUIRED. Value MUST be set to "code".
    • client_id - REQUIRED. The client identifier obtained during registration. In our case it is 'javainuse'
    • redirect_uri - OPTIONAL. After successful authorization, the resource owner should redirect to this uri.
    • scope - OPTIONAL. The scope of the access request. Can be either Read or Write. We will be using Read value.
    The above parameters should be "application/x-www-form-urlencoded" format.

    The Maven Project is as follows -

    boot-40_3
    The pom.xml will is as follows -
    <?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.oauth</groupId>
    	<artifactId>boot-client-application</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<packaging>jar</packaging>
    
    	<name>boot-client-application</name>
    	<description>Demo project for Spring Boot</description>
    
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>1.5.2.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-web</artifactId>
    		</dependency>
    
    		<dependency>
    			<groupId>org.apache.tomcat.embed</groupId>
    			<artifactId>tomcat-embed-jasper</artifactId>
    		</dependency>
    
    		<dependency>
    			<groupId>javax.servlet</groupId>
    			<artifactId>jstl</artifactId>
    		</dependency>
    
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    
    </project>
    
    Create the Controller class with the getEmployeeInfo method which returns a jsp page.
    package com.javainuse.controllers;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    public class EmployeeController {
    
        @RequestMapping(value = "/getEmployees", method = RequestMethod.GET)
        public ModelAndView getEmployeeInfo() {
            return new ModelAndView("getEmployees");
        }
    }

  • Define the following properties -
    spring.mvc.view.prefix:/WEB-INF/jsp/
    spring.mvc.view.suffix:.jsp
    
    server.port:8090
    
    Finally create the Spring Boot Bootstrap class with SpringBootApplication annotation.
    package com.javainuse;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class SpringBootFormHandingApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBootFormHandingApplication.class, args);
        }
    }
    
    Net create the getEmployees.jsp using which we will POST a request to /authorize in form encoded url format.
    <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>Get Employees</title>
    </head>
    <body>
        <h3 style="color: red;">Get Employee Info</h3>
    
        <div id="getEmployees">
            <form:form action="http://localhost:8080/oauth/authorize"
                method="post" modelAttribute="emp">
                <p>
                    <label>Enter Employee Id</label>
                     <input type="text" name="response_type" value="code" /> 
                     <input type="text" name="client_id" value="javainuse" />
                     <input type="text" name="redirect_uri" value="http://localhost:8090/showEmployees" />
                     <input type="text" name="scope" value="read" /> 
                     <input type="SUBMIT" value="Get Employee info" />
            </form:form>
        </div>
    </body>
    </html>

    Next start the boot-resource-server and the boot-client-application. Go to localhost:8090/getEmployees
    Click on Get Employee Info Button.
    boot-40_4
    Enter the credentials as 'admin' and 'admin'
    boot-401_2
    Authorize the Resource Owner to share the data
    boot-40_6
    We can see that Resource Owner shares the authorization code with the Client Application.
    boot-40_7

    *********************************************************************


    Fetching and using the Access Token


    In previous tutorial we learnt OAuth2 - Getting the Authorization Code.
    In this tutorial we will see how to use the authorization code to get the access token and then get the json data using the access token. We had in the previous tutorial done the following -
    • The Resource Owner will ask the Client Application to get some data from the Resource Server.
    • The Resource Server asks the Resource Owner to authenticate itself and as for authorization to share data.
    • After successful authentication the Resource Server shares an authorization code with the client application
    In the next tutorial we will do the following flow -
    • The Client Application using the Authorization code and Secret key ask for the Access Token from the Resource Server.
    • The Resource Server shares the Access Token with the Client Application.
    • Using the shared Access Token the Client Application can now get the required JSON data from the Resource Server

    boot-41_3

    Getting the Access Token


    For getting the access token from the resource server the changes are only required at the client application end.
    In a previous tutorial we had implemented code to get the Authorization code from the Resource Server.
    Using the Authorization Code received from the resource server we can get the access token.
    boot-40_7
    As can be seen the authorization code is received as a request parameter. And the resource server is trying to contact the client application using the redirect uri. So we will write a controller to get the Authorization code as a request parameter. Then using this authorization code we get the Access Token.

    package com.oauth.controllers;
    
    import java.io.IOException;
    import java.util.Arrays;
    
    import org.apache.commons.codec.binary.Base64;
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.client.RestTemplate;
    import org.springframework.web.servlet.ModelAndView;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    
    @Controller
    public class EmployeeController {
    
    	@RequestMapping(value = "/getEmployees", method = RequestMethod.GET)
    	public ModelAndView getEmployeeInfo() {
    		return new ModelAndView("getEmployees");
    	}
    
    	@RequestMapping(value = "/showEmployees", method = RequestMethod.GET)
    	public ModelAndView showEmployees(@RequestParam("code") String code) throws JsonProcessingException, IOException {
    		ResponseEntity<String> response = null;
    		System.out.println("Authorization Code------" + code);
    
    		RestTemplate restTemplate = new RestTemplate();
    
    		// According OAuth documentation we need to send the client id and secret key in the header for authentication
    		String credentials = "javainuse:secret";
    		String encodedCredentials = new String(Base64.encodeBase64(credentials.getBytes()));
    
    		HttpHeaders headers = new HttpHeaders();
    		headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
    		headers.add("Authorization", "Basic " + encodedCredentials);
    
    		HttpEntity<String> request = new HttpEntity<String>(headers);
    
    		String access_token_url = "http://localhost:8080/oauth/token";
    		access_token_url += "?code=" + code;
    		access_token_url += "&grant_type=authorization_code";
    		access_token_url += "&redirect_uri=http://localhost:8090/showEmployees";
    
    		response = restTemplate.exchange(access_token_url, HttpMethod.POST, request, String.class);
    
    		System.out.println("Access Token Response ---------" + response.getBody());
    
    		return null;
    	}
    }
    
       
    Start the client application and the resource server. Go to localhost:8090/getEmployees and follow the same steps we followed in previous tutorials.
    boot-41_1
    We can see that the client application is getting the access token as response.

    Using the Access Token to get the JSON data


    • Resource Server Changes
      In the Resource Server module we add a configuration class. This class allows any request with valid access token and scope to get the requested resource. We use this to configure the access rules for secure resources
      package com.javainuse.config;
      
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.config.annotation.web.builders.HttpSecurity;
      import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
      import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
      
      @Configuration
      @EnableResourceServer
      class ResourceServer extends ResourceServerConfigurerAdapter {
          //Here we specify to allow the request to the url /user/getEmployeesList with valid access token and scope read 
      	@Override
      	public void configure(HttpSecurity http) throws Exception {
      		http.requestMatchers().antMatchers("/user/getEmployeesList/**").and().authorizeRequests().anyRequest()
      				.access("#oauth2.hasScope('read')");
      	}
      }
      
    • Spring Security maintains a filter chain internally where each of the filters has a particular responsibility and filters are added or removed from the configuration depending on which services are required. The ordering of the filters is important as there are dependencies between them. If using Spring 1.5 and above there is a ResourceServerProperties issue Next in the properties file add the following property
      security.oauth2.resource.filter-order = 3
      
    • Client Application Changes
      First create the domain class Employee in the client application, similar to the resource server module.
      package com.oauth.model;
      
      public class Employee {
      
          private String empId;
          private String empName;
      
          public String getEmpId() {
              return empId;
          }
      
          public void setEmpId(String empId) {
              this.empId = empId;
          }
      
          public String getEmpName() {
              return empName;
          }
      
          public void setEmpName(String empName) {
              this.empName = empName;
          }
      
          @Override
          public String toString() {
              return "Employee [empId=" + empId + ", empName=" + empName + "]";
          }
      
      }

    In the client application using the access token as validation call the url to get the JSON data.
    package com.oauth.controllers;
    
    import java.io.IOException;
    import java.util.Arrays;
    
    import org.apache.commons.codec.binary.Base64;
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.client.RestTemplate;
    import org.springframework.web.servlet.ModelAndView;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.oauth.model.Employee;
    
    
    @Controller
    public class EmployeeController {
    
    	@RequestMapping(value = "/getEmployees", method = RequestMethod.GET)
    	public ModelAndView getEmployeeInfo() {
    		return new ModelAndView("getEmployees");
    	}
    
    	@RequestMapping(value = "/showEmployees", method = RequestMethod.GET)
    	public ModelAndView showEmployees(@RequestParam("code") String code) throws JsonProcessingException, IOException {
    		ResponseEntity<String> response = null;
    		System.out.println("Authorization Ccode------" + code);
    
    		RestTemplate restTemplate = new RestTemplate();
    
    		String credentials = "javainuse:secret";
    		String encodedCredentials = new String(Base64.encodeBase64(credentials.getBytes()));
    
    		HttpHeaders headers = new HttpHeaders();
    		headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
    		headers.add("Authorization", "Basic " + encodedCredentials);
    
    		HttpEntity<String> request = new HttpEntity<String>(headers);
    
    		String access_token_url = "http://localhost:8080/oauth/token";
    		access_token_url += "?code=" + code;
    		access_token_url += "&grant_type=authorization_code";
    		access_token_url += "&redirect_uri=http://localhost:8090/showEmployees";
    
    		response = restTemplate.exchange(access_token_url, HttpMethod.POST, request, String.class);
    
    		System.out.println("Access Token Response ---------" + response.getBody());
    
    		// Get the Access Token From the recieved JSON response
    		ObjectMapper mapper = new ObjectMapper();
    		JsonNode node = mapper.readTree(response.getBody());
    		String token = node.path("access_token").asText();
    
    		String url = "http://localhost:8080/user/getEmployeesList";
    
    		// Use the access token for authentication
    		HttpHeaders headers1 = new HttpHeaders();
    		headers1.add("Authorization", "Bearer " + token);
    		HttpEntity<String> entity = new HttpEntity<>(headers1);
    
    		ResponseEntity<Employee[]> employees = restTemplate.exchange(url, HttpMethod.GET, entity, Employee[].class);
    		System.out.println(employees);
    		Employee[] employeeArray = employees.getBody();
    
    		ModelAndView model = new ModelAndView("showEmployees");
    		model.addObject("employees", Arrays.asList(employeeArray));
    		return model;
    	}
    }


  • Next we will define the jsp named showEmployees to display the Employee info recieved.
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <%@page session="false"%>
    <html>
    <head>
    <title>Show Employees</title>
    </head>
    <body>
    
    	<h3 style="color: red;">Show All Employees</h3>
    	<ul>
    		<c:forEach var="listValue" items="">
    			<li></li>
    		</c:forEach>
    	</ul>
    </body>
    </html>
    
  • Next start the boot-resource-server and the boot-client-application. Go to localhost:8090/getEmployees
    Click on Get Employee Info Button.
    boot-40_4
    Enter the credentials as 'javainuse' and 'javainuse'
    boot-401_2
    Authorize the Resource Owner to share the data
    boot-40_6
    We see the json data as follows.
    boot-41_2

































































































    No comments:

    Post a Comment

    So sánh các GitFlow model và áp dụng với CICD

    https://medium.com/oho-software/so-s%C3%A1nh-c%C3%A1c-gitflow-model-v%C3%A0-%C3%A1p-d%E1%BB%A5ng-v%E1%BB%9Bi-cicd-b6581cfc893a