I. Setting up a Spring Boot basic service in a cloud based environment

elevysi · 05 April 2019 |

There can be as many of these as the business you handle requires; for instance the blogging and the social services of this web site are services implemented in the fashion described below. I am going to showcase a blogging application in which one can CRUD posts. For this, we will layer our application as follows:

  • a model layer;
  • a Controller layer;
  • a Service layer;
  • a Data Access layer;
  • a view layer ( will be left out for briefty reasons)

The service will need the following dependencies:

  • Web dependencies:
    • Spring-boot-starter-web : to make the application a web one with a server
    • Spring-boot-starter-thymeleaf : for rendering the views
  • Persistence dependencies:
    • Spring-boot-starter-data-jpa : for persisting our data
    • Mysql-connector-java : as a connector
  • Security Dependencies:
    • spring-boot-starter-security : to config the security
    • Spring-security-oauth2 : to enable the resource server
  • Cloud Dependencies:
    • Spring-boot-starter-actuator : spring boot monitoring
    • Spring-cloud-security : to include the application in our cloud based environment

Please note that adding the spring-boot-starter-security dependency automatically enables security into our application. If one wishes to test the application without security enabled, they can comment out the security dependencies.

<?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>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.tutorial.basic</groupId>
	<artifactId>basicservice</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>basicservice</name>
	<description>Basic Service</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>
	
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Greenwich.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		
<!-- 		Security -->

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		
		
		
<!-- 		Oauth2 -->
		<!-- @EnableOauth2Sso, Principal + Authorities Extractor-->
		<dependency>
			<groupId>org.springframework.security.oauth.boot</groupId>
			<artifactId>spring-security-oauth2-autoconfigure</artifactId>
			<version>2.1.2.RELEASE</version>
		</dependency>

<!-- 		Cloud -->

			
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-actuator</artifactId>
			</dependency>


			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-security</artifactId>
			</dependency>
			
			
		
	</dependencies>

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

</project>

Please note that pom.xml presented above lists more dependencies as they will be needed later on in the tutorial series. 
With the dependencies declared, let’s create a basic mysql database using the script below (for the time being a single table, i.e. posts)

CREATE DATABASE `basicdb` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE DATABASE baiscdb;
CREATE TABLE `posts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` mediumtext,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

With the corresponding table created, we can already create the model entity as shown below:

package com.tutorial.basic.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;


@Entity
@Table(name="posts")
public class Post {
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	private String content;
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
}

Since we already know what we want to achieve in the end (a basic service), we can start with the planning of security into our domain.

Security will be dependent on the implemented routing, i.e. some routes will be used for protected resources while some others will be used for public resources.
We also know that two request types make up this application; the service needs to handle api requests (acting as a Resource Server; i.e the application that delivers protected resources), as well as classic requests through the user interface. Eventually, different security schemes will be implemented for each of the specified request types. A fork of the service routes is then needed to address the two aspects of our service as follows:

  • A route for the Resource Server requests, i.e. /api/**
  • A route for the other requests, i.e. /ui/** in general, /login/**  for login pages and the root path /  for the home page
    • Within the user interface requests, some need to be public and will hence be prefixed by /ui/public/** to indicate that no prior authorization is needed for their access. Some others are requests to styling and javascript files that also need to be publicly available to the end-user; all the other requests shall imply some kind of authorization

These two route branches will be configured in two different configurations classes with each one configured to listen to its matching routes prefixes.

The security config of the part I refer to as the user interface is implemented as follows 

package com.tutorial.basic.config;

import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
	
	@Override
	protected void configure(HttpSecurity http) throws Exception{
		
		http.requestMatchers()
			.antMatchers("/" , "/ui/**", "/login")
			.and()
			.authorizeRequests()
			.antMatchers("/").permitAll()
			.antMatchers("/ui/public/**").permitAll()
			.anyRequest().authenticated()
			.and()
			.formLogin().permitAll()
			;
		
	}
	
	public void configure(WebSecurity webSecurity) throws Exception{
		webSecurity
			.ignoring()
			.antMatchers("/js/**")
			.antMatchers("/css/**")
			;
	}

}

We declare the routing explained earlier, tell the application to let through some resources such as the javascript and styling files when viewing the user interface. This part of the configuration listen and handles requests that match the indicated prefixes, i.e. /** for the home page, /ui/** for user interface requests and /login/** for GUI authentication requests.
Let’s address the application properties in the application.yml
file as shown below:

#application.yml
server:
  port: 1000
  servlet:
    session:
      cookie:
        name: TUTORIALBASESESSION
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/basicdb
    username: root
    password: root
  application:
    name: basicService

We can now run the application and see that each time a protected resource is requested, the user is redirected to an authentication form.

 

On the other hand, the api or resource server, whose the routes are prefixed with /api/** is given its configuration class as shown below:

package com.tutorial.basic.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;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;


@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{

		private static final String RESOURCE_ID = "basicServiceApi";
		
		@Override
		public void configure(ResourceServerSecurityConfigurer resources){
			resources.resourceId(RESOURCE_ID).stateless(false);
		}
		
		
		@Override
		public void configure(HttpSecurity http) throws Exception{
			http.requestMatchers()
				.antMatchers("/api/**")
				.and()
				.authorizeRequests()
				.antMatchers("/api/public/**").permitAll()
				.anyRequest().authenticated()
				;
		}
}

Note the use of the annotation @EnableResourceServer (declared in the dependency from spring-security-oauth2) which indicates that the current application holds protected resources as described in the Oauth2 specs.

From the above code, we have named the resource server as basicServiceApi and only clients whose scope include the specified resource server name will be able to successfully request resources from the current service.
More information about Oauth2 grant scopes can be found in the excellent book from Manning Publication, Oauh2 In Action.

Notice that we have defined a public route for the API for those resources we need to make publicly available without the need of authenticating.
For the protected part, as users navigate to one of the
/api/** routes (excluding the /api/public/** routes), users are presented with a “Full authentication is needed” page”.