h2 database Latest spring boot spring security spring-data unit test

Spring REST + Spring Safety Example – Mkyong.com

-logo

On this article, we’ll enhance the earlier spring REST validation example by including Spring Security to perform the requested URL authentication and authorization (REST API endpoints)

Methods Used:

  • Spring Boot 2.1.2. RELEASE
  • Spring 5.1.four.RELEASE
  • Spring Security 5.1.3.RELEASE
  • Spring Info JPA 2.1.four.RELEASE
  • H2 Memory Database 1.four.197
  • Tomcat Embed 9.0.14
  • JUnit four.12
  • Maven three
  • Java 8

1. Venture Directory

  Project Directory

2. Maven

Consists of Spring Safety Spring Safety and Spring Safety Check for Spring Safety Integration Check

pom.xml

4.0.zero

Spring Safety
jar
Spring Start REST API Example
1.0

] org.springframework.boot
spring-start
2.1.2.RELEASE

1.8
true
true

org.springframework.boot
spring-boot starter-net

org.springframework.boot
spring-boot boot-security

org.springframework.safety
spring-safety check
check

org.springframework.boot
spring-boot-boot-knowledge consultant

com.h2database
h2

org.springframework.boot
spring-boot-boot check
check

org.apache .httpcomponents
httpclient
four.5.7
check

org.springframewo rk.boot
spring-play-DevTools
true

org.springframework.boo t
spring-boot-maven plugin

true

org.apache.maven.plugins
] maven-surefire plugin
2.22.zero

Challenge dependencies:

> mvn dependence: tree

[INFO] — Maven Habit Plugin: three.1.1: Tree (default-cli) @ spring-rest-security —
[INFO] org.springframework.boot: spring security: jar: 1.zero
[INFO] + – org.springframework.boot: spring-boot-starter-net: jar: 2.1.2.RELEASE: compile
[INFO] | + – org.springframework.boot: spring-boot-starter: jar: 2.1.2.RELEASE: turn
[INFO] | | + – org.springframework.boot: spring-boot-starter-logging: jar: 2.1.2.RELEASE: compile
[INFO] | | | + – ch.qos.logback: logback-basic: jar: 1.2.3: translate
[INFO] | | | | – ch.qos.logback: logback-core: jar: 1.2.3: compile
[INFO] | | | + – org.apache.logging.log4j: log4j-to-slf4j: jar: 2.11.1: compile
[INFO] | | | | – org.apache.logging.log4j: log4j-api: jar: 2.11.1: compile
[INFO] | | | – org.slf4j: jul-to-slf4j: jar: 1.7.25: translate
[INFO] | | + – javax.annotation: javax.annotation-api: jar: 1.3.2: compile
[INFO] | | – org.yaml: snakeyaml: jar: 1.23: runtime
[INFO] | + – org.springframework.boot: spring-boot-starter-json: jar: 2.1.2.RELEASE: compile
[INFO] | | + – com.fasterxml.jackson.core: jackson-datasind: jar: 2.9.eight: compile
[INFO] | | | + – com.fasterxml.jackson.core: jackson: jar: 2.9.0: compile
[INFO] | | | com.fasterxml.jackson.core: jackson-core: jar: 2.9.eight: compile
[INFO] | | + – com.fasterxml.jackson.datatype: jackson-datatype-jdk8: jar: 2.9.eight: compile
[INFO] | | + – com.fasterxml.jackson.datatype: jackson-datatype-jsr310: jar: 2.9.eight: compile
[INFO] | | com.fasterxml.jackson.module: jackson-module-parameter-names: jar: 2.9.eight: assemble
[INFO] | + – org.springframework.boot: spring-boot-starter-tomcat: jar: 2.1.2.RELEASE: compile
[INFO] | | + – org.apache.tomcat.embed: tomcat-Embed-core: jar: 9.zero.14: compile
[INFO] | | + – org.apache.tomcat.embed: tomcat-embed: jar: 9.zero.14: compile
[INFO] | | – org.apache.tomcat.embed: tomcat-Embed-websocket: jar: 9.0.14: translate
[INFO] | + – org.hibernate.validator: Hibernate-Validator: jar: 6.0.14.Conclusion: compile
[INFO] | | + – javax.validation: validation-api: jar: 2.0.1. Conclusion: put together
[INFO] | | + – org.jboss.logging: jboss-logging: jar: three.three.2 Conclusion: assemble
[INFO] | | – com.fasterxml: Classmate: jar: 1.four.0: Translate
[INFO] | + – org.springframework: spring-net: jar: 5.1.4.RELEASE: put collectively
[INFO] | | – org.springframework: spring-beans: jar: 5.1.4.RELEASE: put together
[INFO] | – org.springframework: spring-webmvc: jar: 5.1.4.RELEASE: assemble
[INFO] | + – org.springframework: spring-context: jar: 5.1.four.RELEASE: compile
[INFO] | – org.springframework: spring-expression: jar: 5.1.4.RELEASE: compile
[INFO] + – org.springframework.boot: spring-boot-starter safety: jar: 2.1.2.RELEASE: compile
[INFO] | + – org.springframework: spring-aop: jar: 5.1.four.RELEASE: compile
[INFO] | + – org.springframework.security:spring-security-conf::r:5.1.3.RELEASE:compile
[INFO] | safety: protection – network: jar: 5.1.three.RELEASE: compile
[INFO] + – org.springframework.security:spring-safety-check:jar:5.1.3.RELEASE: check
[INFO] | + – org.springframework.safety-spring-security-core: jar: 5.1.three.RELEASE: compile
[INFO] | + – org.springframework: spring-core: jar: 5.1.four.RELEASE: compile
[INFO] | | – org.springframework: spring-jcl: jar: 5.1.four.RELEASE: put collectively
[INFO] | – org.springframework: spring check: jar: 5.1.four.RELEASE: check
[INFO] + – org.springframework.boot: spring-boot-starter-knowledge-jpa: jar: 2.1.2.RELEASE: compile
[INFO] | + – org.springframework.boot: spring-boot-starter-aop: jar: 2.1.2.RELEASE: compile
[INFO] | | – org.aspectj: aspectjweaver: jar: 1.9.2: compile
[INFO] | + – org.springframework.boot: spring-boot-starter-jdbc: jar: 2.1.2.RELEASE: Translate
[INFO] | | + – com.zaxxer: HikariCP: jar: 3.2.zero: translate
[INFO] | | – org.springframework: spring-jdbc: jar: 5.1.four.RELEASE: compile
[INFO] | + – javax.transaction: javax.transaction-api: jar: 1.3: translate
[INFO] | + – javax.xml.bind: jaxb-api: jar: 2.3.1: compile
[INFO] | | – javax.activation: javax.activation-api: jar: 1.2.0: translate
[INFO] | + – org.hibernate: Hibernate-core: jar: 5.three.7 Conclusion: assemble
[INFO] | | + – javax.persistence: javax.persistence-api: jar: 2.2: translate
[INFO] | | + – org.javassist: javassist: jar: 3.23.1-GA: put collectively
[INFO] | | + – internet.bytebuddy: byte-buddy: jar: 1.9.7: compile
[INFO] | | + – antlr: antlr: jar: 2.7.7: compile
[INFO] | | + – org.jboss: jandex: jar: 2.zero.5. Conclusion: put collectively
[INFO] | | + – org.dom4j: dom4j: jar: 2.1.1: compile
[INFO] | | – org.hibernate.widespread: Hibernate-Commons-annotations: jar: 5.zero.four.
[INFO] | + – org.springframework.knowledge:spring-knowledge-jpa:jar:2.1.4.RELEASE:compile
[INFO] | | + – org.springframework.knowledge:spring-knowledge-commons:jar:2.1.4.RELEASE:compile
[INFO] | | + – org.springframework: spring-orm: jar: 5.1.4.RELEASE: compile
[INFO] | | + – org.springframework: spring-tx: jar: 5.1.4.RELEASE: put collectively
[INFO] | | – org.slf4j: slf4j-api: jar: 1.7.25: compile
[INFO] | – org.springframework: spring elements: jar: 5.1.4.RELEASE: put together
[INFO] + – com.h2database: h2: jar: 1.four.197: put collectively
[INFO] + – org.springframework.boot: suspension begin check: jar: 2.1.2.RELEASE: check
[INFO] | + – org.springframework.boot: spring-boot-check: jar: 2.1.2.RELEASE: check
[INFO] | + – org.springframework.boot: spring-boot-check-autoconfigure: jar: 2.1.2.RELEASE: check
[INFO] | + – com.jayway.jsonpath: json-path: jar: 2.4.0: check
[INFO] | | – internet.minidev: json-sensible: jar: 2.three: check
[INFO] | | – internet.minidev: accessors-sensible: jar: 1.2: check
[INFO] | | – org.ow2.asm: asm: jar: 5.0.4: check
[INFO] | + – trains: trains: jar: 4.12: check
[INFO] | + – org.assertj: assertj-core: jar: three.11.1: check
[INFO] | + – org.mockito: mockito-core: jar: 2.23.four: check
[INFO] | | + – internet.bytebuddy: byte-guy-agent: jar: 1.9.7: check
[INFO] | | – org.objenesis: objenesis: jar: 2.6: check
[INFO] | + – org.hamcrest: hamcrest-core: jar: 1.three: check
[INFO] | + – org.hamcrest: hamcrest-library: jar: 1.3: check
[INFO] | + – org.skyscreamer: jsonassert: jar: 1.5.0: check
[INFO] | | com.vaadin.external.google:android-json:jar:zero.0.20131108.vaadin1:check
[INFO] | – org.xmlunit: xmlunit-core: jar: 2.6.2: check
[INFO] + – org.apache.httpcomponents: httpclient: jar: four.5.7: check
[INFO] | + – org.apache.httpcomponents: httpcore: jar: four.4.10: check
[INFO] | – Commons-codec: Commons-codec: jar: 1.11: check
[INFO] org.springframework.boot: spring-boot-devtools: jar: 2.1.2.RELEASE: compile (optionally available)
[INFO] + – org.springframework.boot: spring-boot: jar: 2.1.2.RELEASE: assemble
[INFO] org.springframework.boot: spring-boot-autoconfigure: jar: 2.1.2.RELEASE: assemble

three. Spring Controller

Assessment the e-book driver again, later integrate Spring Safety to make sure REST endpoints.

BookController.java

package deal com.mkyong;

convey com.mkyong.error.BookNotFoundException;
convey com.mkyong.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.manufacturing unit.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.net.bind.annotation. *;

import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.Listing;
import java.util.Map;

@RestController
@Validated
public class BookController

@Autowired
a personal BookRepository archive;

// Discovery
@GetMapping ("/ Books")
Listing findAll ()
restore repository.findAll ();

// Save
@PostMapping ("/ books")
@ResponseStatus (HttpStatus.CREATED)
Ebook a new guide (@Valid @RequestBody E-book newBook)
backupbook.save (newBook);

// Discovery
@GetMapping ("/ books / id")
Ebook findOne (@PathVariable @Min (1) Long id)
restore archive.findById (id)
.orElseThrow (() -> new BookNotFoundException (s));

// Save or update
@PutMapping ("/ books / id")
Guide saveOrUpdate (@RequestBody Guide a brand new ebook, @PathVariable Lengthy id)

restore archive.findById (id)
.map (x ->
x.setName (newBook.getName ());
x.setAuthor (newBook.getAuthor ());
x.setPrice (newBook.getPrice ());
return warehouse.save (x);
)
.orElseGet (() ->
newBook.setId (id);
backupbook.save (newBook);
);

// simply writer replace
@PatchMapping ("/ books / id")
Ebook patch (@RequestBody Map update, @PathVariable Long id)

restore archive.findById (id)
.map (x ->

String writer = update.get ("author");
if (! StringUtils.isEmpty)
x.setAuthor (writer);

// higher create a custom technique to replace the value =: newValue where id =: id
return warehouse.save (x);
other
throw a new BookUnSupportedFieldPatchException (replace.keySet ());

)
.orElseGet (() ->
throw new BookNotFoundException (s);
);

@DeleteMapping ("/ books / id")
void deleteBook (@PathVariable Lengthy id)
repository.deleteById (id);

P.S. Different elements or storage places usually are not listed right here, see previous spring REST validation instance

four. Spring Safety

4.1 Create a brand new @ Configuration class and increase WebSecurityConfigurerAdapter. Within the instance under, we use HTTP primary authentication to protect REST endpoints. Read the comment for self explanatory.

SpringSecurityConfig.java

package deal com.mkyong.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.net.builders.HttpSecurity;
import org.springframework.safety.config.annotation.net.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter

// Create 2 customers for demo
@Bypass
protected void configure (AuthenticationManagerBuilder auth) throws an exception

auth.inMemoryAuthentication ()
.withUser ("user"). password ("noop password"). roles ("USER")
.and()
.consumer ("admin") password ("noop password"). roles ("USER", "ADMIN");

// Shield endpoints with primary HTTP authentication
@Bypass
protected void configure (HttpSecurity http) throws an exception

http
// Primary HTTP authentication
.httpBasic ()
.and()
.authorizeRequests ()
.antMatchers (HttpMethod.GET, "/books/**").hasRole("USER")
.antMatchers (HttpMethod.POST, "/books").hasRole("ADMIN")
.antMatchers (HttpMethod.PUT, "/books/**").hasRole("ADMIN")
.antMatchers (HttpMethod.PATCH, "/books/**").hasRole("ADMIN")
.antMatchers (HttpMethod.DELETE, "/books/**").hasRole("ADMIN")
.and()
.csrf (). delete ()
.formLogin (). delete ();

/*@Bean
publicDetailsService userDetailsService ()
// ok for demo
Consumer.UserBuilder users = Consumer.withDefaultPasswordEncoder ();

InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager ();
supervisor.createUser (customers.username ("user"). password ("password"). roles ("User"). build ());
manager.createUser (customers.username ("admin"). password ("password") roles ("USER", "ADMIN"). build ();
Director of the return;
* /

four.2 Prepared, the above Spring REST API endpoints are protected by Spring Security 🙂

Learn extra:

5. Spring Boot

Regular Spring Boot software launches REST endpoints and adds 3 books to H2 database for demo

StartBookApplication

package deal com.mkyong;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.math.BigDecimal;

@SpringBootApplication
public class StartBookApplication

// start all
public Static void primary (String [] args)
SpringApplication.run (StartBookApplication.class, args);

@Bean
CommandLineRunner initDatabase (BookRepository archive)
back args ->
repository.save (new guide ("Guide to Bodhisattva's Lifestyle", "Santideva", New BigDecimal ("15.41")));
repository.save (new guide (The Life Altering Magic of Life), Marie Kondo, New BigDecimal ("9.69")));
repository.save (new guide ("Refactoring: Improving current code design", "Martin Fowler", New BigDecimal ("47.99")));
;

6. Demo

6.1 Begin the Spring Boot software.

mvn spring-boot: run

6.2 Normal GET and POST return 401, all endpoints are protected, want authentication.

> curl localhost: 8080 / books

"Time stamp": "2019-02-25T04: 05: 14,709 + 0000"
"Status": 401
"Error": "Unauthorized",
"Message": "Unauthorized",
"Path": "/ books"

> curl -X POST localhost: 8080 / books -H "Content type: application / json"
-d "name": "ABC", "author": "mkyong", "price": "8.88"

"Time Stamp": "2019-02-25T04: 11: 17,150 + 0000"
"Status": 401
"Error": "Unauthorized",
"Message": "Unauthorized",
"Path": "/ books"

6.three Ship GET Request at Consumer Login

> curl localhost: 8080 / books -u consumer: password
[
“id”:1,”name”:”A Guide to the Bodhisattva Way of Life”,”author”:”Santideva”,”price”:15.41,
“id”:2,”name”:”The Life-Changing Magic of Tidying Up”,”author”:”Marie Kondo”,”price”:9.69,
“id”:three,”name”:”Refactoring: Improving the Design of Existing Code”,”author”:”Martin Fowler”,”price”:47.99
]

> curl localhost: 8080 / books / 1 -u admin: password

"Id": 1,
"Name": "Guide to Bodhisattva's Lifestyle",
"Writer": "Santideva",
"Price": 15.41

6.four Attempt sending a POST request with a consumer username, it returns 403, Forbidden error. It’s because the consumer has no right to ship the POST request.

> curl -X POST localhost: 8080 / books -H "Content type: application / json"
-d "name": "ABC", "author": "mkyong", "price": "8.88" -u consumer: password

"Time stamp": "2019-02-25T04: 16: 58,702 + 0000"
"Status": 403,
"Error": "Forbidden"
"Message": "Forbidden"
"Path": "/ books"

Evaluation the Spring Security setting. You want admin to send POST, PUT, PATCH, or DELETE request

SpringSecurityConfig.java

@Bypass
protected void configure (HttpSecurity http) throws an exception

http
// Primary HTTP authentication
.httpBasic ()
.and()
.authorizeRequests ()
.antMatchers (HttpMethod.GET, "/books/**").hasRole("USER")
.antMatchers (HttpMethod.POST, "/books").hasRole("ADMIN")
.antMatchers (HttpMethod.PUT, "/books/**").hasRole("ADMIN")
.antMatchers (HttpMethod.PATCH, "/books/**").hasRole("ADMIN")
.antMatchers (HttpMethod.DELETE, "/books/**").hasRole("ADMIN")
.and()
.csrf (). delete ()
.formLogin (). delete ();

6.5 Attempt to ship POST request by admin

> curl -X POST localhost: 8080 / books -H "Content type: application / json"
-d "name": "ABC", "author": "mkyong", "price": "8.88" -u admin: password

"Id": four,
"Name": "ABC"
"Author": "mkyong"
"Price": 8.88

> curl localhost: 8080 / books -u consumer: password

[
“id”:1,”name”:”A Guide to the Bodhisattva Way of Life”,”author”:”Santideva”,”price”:15.41,
“id”:2,”name”:”The Life-Changing Magic of Tidying Up”,”author”:”Marie Kondo”,”price”:9.69,
“id”:3,”name”:”Refactoring: Improving the Design of Existing Code”,”author”:”Martin Fowler”,”price”:47.99,
“id”:four,”name”:”ABC”,”author”:”mkyong”,”price”:8.88
]

7. Spring Safety Integration Check

7.1 Check @ WithMock Consumer and MockMvc

BookControllerTest.java

package deal com.mkyong;

import org.junit.Ennen;
import org.junit.Check;
import org.junit.runner.RunWith;
import org.springframework.beans.manufacturing unit.annotation.Autowired;
import org.springframework.boot.check.autoconfigure.net.servlet.AutoConfigureMockMvc;
import org.springframework.boot.check.context.SpringBootTest;
import org.springframework.boot.check.mock.mockito.MockBean;
import org.springframework.security.check.context.help.WithMockUser;
import org.springframework.check.context.ActiveProfiles;
import org.springframework.check.context.junit4.SpringRunner;
import org.springframework.check.net.servlet.MockMvc;

import java.math.BigDecimal;
import java.util.Elective;

import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.check.net.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.check.net.servlet.end result.MockMvcResultHandlers.print;
import static org.springframework.check.net.servlet.end result.MockMvcResultMatchers.jsonPath;
import static org.springframework.check.net.servlet.end result.MockMvcResultMatchers.status;

@RunWith (SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles ("test")
public class BookControllerTest

@Autowired
personal MockMvc mockMvc;

@MockBean
personal BookRepository mockRepository;

@Earlier than
public void init ()
Guide = New Ebook (1L, "Guide to Bodhisattva's Lifestyle", "Santideva", New BigDecimal ("15.41"));
when (mockRepository.findById (1 I)). thenReturn (Non-compulsory.of (letter));

// @ WithMockUser (username = "USER")
@WithMockUser ("USER")
@Check
public void find_login_ok () throws an exception

mockMvc.carry out (get ("/ books / 1"))
.andDo (print ())
.andExpect (state (). isOk ())
.andExpect (jsonPath ("$. id" is (1)))
.andExpect (jsonPath ("$. name" is a "Guide to Bodhisattva's Lifestyle")))
.andExpect (jsonPath ("$. author", on ("Santideva")))
and wait (jsonPath ("$. price", is (15,41)));

@Check
public void find_nologin_401 () throws an exception
mockMvc.perform (get ("/ books / 1"))
.andDo (print ())
.andExpect (state (). isUnauthorized ());

7.2 Check with TestRestTemplate

BookControllerRestTemplateTest.java

package deal com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Ennen;
import org.junit.Check;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.manufacturing unit.annotation.Autowired;
import org.springframework.boot.check.context.SpringBootTest;
import org.springframework.boot.check.mock.mockito.MockBean;
import org.springframework.boot.check.net.shopper.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.check.context.ActiveProfiles;
import org.springframework.check.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.Optionally available;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

@RunWith (SpringRunner.class)
@SpringBootTest (webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles ("test")
public class BookControllerRestTemplateTest

personal static last ObjectMapper om = new ObjectMapper ();

// @ WithMockUser doesn’t work with TestRestTemplate
@Autowired
personal TestRestTemplate restTemplate;

@MockBean
personal BookRepository mockRepository;

@Earlier than
public void init ()
E-book = New Ebook (1L, "Guide to Bodhisattva's Lifestyle", "Santideva", New BigDecimal ("15.41"));
when (mockRepository.findById (1 I)). thenReturn (Optionally available.of (letter));

@Check
public void find_login_ok () throws an exception

String expected = "id: 1, title:" Information to Bodhisattva's Way of life "by" Santideva ", Price: 15.41";

ResponseEntity answer = restTemplate
.withBasicAuth ("user", "password")
.getForEntity ("/ books / 1", String.class);

printJSON (answer);

assertEquals (MediaType.APPLICATION_JSON_UTF8, response.getHeaders (). getContentType ());
assertEquals (HttpStatus.OK, response.getStatusCode ());

JSONAssert.assertEquals (anticipated, reply.getBody (), false);

@Check
public void find_nologin_401 () throws an exception

String Expected = "" Standing ": 401," Error ":" Unauthorized "," Message ":" Unauthorized "," Path ":" / books / 1 "";

ResponseEntity reply = restTemplate
.getForEntity ("/ books / 1", String.class);

printJSON (answer);

assertEquals (MediaType.APPLICATION_JSON_UTF8, response.getHeaders (). getContentType ());
assertEquals (HttpStatus.UNAUTHORIZED, response.getStatusCode ());

JSONAssert.assertEquals (anticipated, answer.getBody (), false);

personal static blank printJSON (Object object)
String end result;
attempt
results = om.writerWithDefaultPrettyPrinter (). writeValueAsString (objects);
System.out.println (end result);
within the corridor (JsonProcessingException e)
e.printStackTrace ();

P.S @ WithMockUser doesn’t work with TestRestTemplate, we’d like authentication for ".withBasicAuth"

Obtain Supply

References