Friday, 14 July 2017

Spring MVC Java Config : Part 5 Secure RESTful API with Spring Security OAuth2

The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf.


1. TOOLS AND ENV

IDE : Spring Tool Suite 3.7.3
JDK :   1.8 Tomcat : 8.0.18
Spring : 4.2.6.RELEASE




2. OAUTH2 FLOW




Basically , resources are protected with access token, client could use refresh token to request an access token if authenticated with its credentials.




3. 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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.junjun.spring.tutorials</groupId>
<artifactId>spring-mvc-oauth2</artifactId>
<name>spring-4-mvc-sample</name>
<packaging>war</packaging>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
<org.springframework.version>4.2.6.RELEASE</org.springframework.version>
<org.spring.security.version>4.1.0.RELEASE</org.spring.security.version>
<slf4j.version>1.7.10</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- Spring JAXB support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- Spring Security Web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${org.spring.security.version}</version>
</dependency>
<!-- Spring Security Config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${org.spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.2.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
</plugins>
</build>
</project>
view raw pom.xml hosted with ❤ by GitHub



spring-security-oauth2 added to the dependency.




4. OAUTH2 CONFIG


package org.junjun.spring.tutorials.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
@Configuration
public class OAuth2Config {
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("springuser").secret("springpassword")
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.scopes("read", "write", "trust");
}
}
@Configuration
@EnableResourceServer
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers(HttpMethod.GET, "/api/**").and().authorizeRequests().anyRequest()
.access("#oauth2.hasScope('read')");
http.requestMatchers().antMatchers(HttpMethod.POST, "/api/**").and().authorizeRequests().anyRequest()
.access("#oauth2.hasScope('write')");
}
}
}



1. Technically authorization server and resource server can be hosted separately.
2. OAuth2 authentication manager is define in Spring Web Security in class SecurityConfig
3. You may refer to Spring documents for all possible values and use cases for grant types and scopes





5. JS AND JSP

(function() {
$(document).ready(function() {
// var token = $("meta[name='_csrf']").attr("content");
// var header = $("meta[name='_csrf_header']").attr("content");
//
// $(document).ajaxSend(function(e, xhr, options) {
// xhr.setRequestHeader(header, token);
// });
$.fn.serializeObject = function()
{
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || "");
} else {
o[this.name] = this.value || "";
}
});
return o;
};
$(document).on("#form-todo").submit(function(event) {
event.preventDefault();
return false;
});
$(document).on("click", "#link-logout", function(event) {
event.preventDefault()
$("#form-logout")[0].submit();
});
$(document).on("click", "#rest-create-btn", function() {
var form = $("#form-token");
var url = form.attr("action");
var token = "";
$.ajax({
type: "POST",
url: url,
data: form.serialize(),
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Basic c3ByaW5ndXNlcjpzcHJpbmdwYXNzd29yZA==" )
// base64 username:password
},
success: function(response)
{
if(response.access_token){
token = response.access_token;
form = $("#form-todo");
url = $("#rest-url-create").val();
var data = JSON.stringify(form.serialize());
data = JSON.stringify(form.serializeObject());
$.ajax({
type : "POST",
url : url,
headers: {
"Authorization":"Bearer "+token
},
data : data,
contentType: "application/json; charset=utf-8",
dataType: "json",
success : function(response) {
var output="<li>" + response.date + " : " +response.content + "</li>";
$("#ul-todo-list").append(output);
$("#todoContent").val("");
}
});
}
}
});
});
});
})();
view raw list.js hosted with ❤ by GitHub


<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="spring">
<meta name="keywords" content="spring">
<meta name="_csrf" content="${_csrf.token}" />
<meta name="_csrf_header" content="${_csrf.headerName}" />
<title></title>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
</head>
<body>
<header class="navbar navbar-default navbar-fixed-top bs-docs-nav"
role="banner">
<div class="container">
<nav class="collapse navbar-collapse bs-navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a id="link-logout" href="javascript:;"> Logout </a></li>
</ul>
</nav>
</div>
</header>
<div class="container" style="padding-top: 10%">
<div class="row">
<form:form id="form-todo" class="form-horizontal"
modelAttribute="newToDo" action="">
<input name="${_csrf.parameterName}" type="hidden"
value="${_csrf.token}">
<div class="form-group">
<div class="col-md-offset-2 col-md-6">
<form:input id="todoContent" class="form-control" path="content"
placeholder="" />
</div>
<div class="col-md-2">
<button id="rest-create-btn" class="btn btn-success btn-block">Create</button>
</div>
<label for="todoContent" class="col-md-4 control-label"
style="text-align: left;"><spring:bind path="content">
<c:if test="${status.error}">
<form:errors path="content" class="text-danger" />
</c:if>
</spring:bind></label>
</div>
</form:form>
</div>
<div class="row">
<div class="col-md-offset-2 col-md-10">
<ul id="ul-todo-list">
<c:forEach items="${toDoList}" var="toDo">
<li>${toDo.createdDateDisplay}&nbsp;:&nbsp;${toDo.content}</li>
</c:forEach>
</ul>
</div>
</div>
<input type="hidden" id="rest-url-create"
value="${pageContext.request.contextPath}/api/todo/create" /> <input
type="hidden" id="rest-url-create"
value="${pageContext.request.contextPath}/api/todo/create" />
</div>
<form id="form-token"
action="${pageContext.request.contextPath}/oauth/token" method="post">
<input name="grant_type" type="hidden" value="password" /> <input
name="username" type="hidden" value="springuser" /> <input
name="password" type="hidden" value="springpassword" />
</form>
<form id="form-logout"
action="${pageContext.request.contextPath}/logout" method="post">
<input name="${_csrf.parameterName}" type="hidden"
value="${_csrf.token}">
</form>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="${pageContext.request.contextPath}/js/todo/rest/list.js"></script>
</body>
</html>
view raw list.jsp hosted with ❤ by GitHub



1. Get OAUTH2 token before consuming api restful web service
2. csrf is disabled just to make example simple





6. TEST











7. SOURCE CODE



https://github.com/junjun-dachi/spring-tutorials/tree/master/05-spring-mvc-oauth2


8. Reference


https://spring.io/guides/tutorials/spring-boot-oauth2/
https://github.com/spring-projects/spring-security-oauth
https://github.com/spring-projects/spring-security-oauth/blob/master/docs/oauth2.md
https://raymondhlee.wordpress.com/2014/12/21/implementing-oauth2-with-spring-security/








1 comment:

  1. Anyone can take the 5-minute java test in the US as it is targeted to coders who want to test their Java knowledge.

    ReplyDelete

Flag Counter