source code demo: https://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
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 -
The pom.xml will have added the spring-security-oauth2 dependency
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 -
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.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.
The Maven Project is as follows -
The pom.xml will is as follows -
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"); } }
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 Maven Project is as follows -
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:8090Finally 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.
Enter the credentials as 'admin' and 'admin'
Authorize the Resource Owner to share the data
We can see that Resource Owner shares the authorization code with the Client Application.
Click on Get Employee Info Button.
Enter the credentials as 'admin' and 'admin'
Authorize the Resource Owner to share the data
We can see that Resource Owner shares the authorization code with the Client Application.
*********************************************************************
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 -
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
- 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
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.
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;
}
}
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 resourcespackage 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;
}
}
<%@ 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>
Click on Get Employee Info Button.
Enter the credentials as 'javainuse' and 'javainuse'
Authorize the Resource Owner to share the data
We see the json data as follows.
No comments:
Post a Comment