Rest With Spring 

Web Service

Software System designed to support interportable machine to machine interaction over a network

3 Keys

  • Designed for machine-to-machine (or application to application ) interaction
  • Should be interoperable - Not platform dependent.
  • Should allow communication over a network.

Service Definition

  • Request/Response Format
  • Request Structure
  • Response Structure
  • Endpoint

 

Web Services Groups 

  • Soap Based
  • Rest Styled

SOAP and REST are not really comparable

SOAP

  • SOAP defines a specific way of building web services
  • SOAP uses XML for request excahange format.
  • SOAP defines following XML Response/Request structure

SOAP (Cont.)

  • Format
    • SOAP XML Request
    • SOAP XML Response
  • Transport
    • SOAP over MQ
    • SOAP over HTTP  
  • Service Definition
    • WSDL (Web Service Definition Language)
      • Endpoint
      • All Operations
      • Request Structure
      • Response Structure

 

REST (Representational State Transfer )

  • Makes the best use of HTTP
    • HTTP Methods (GET, PUT, POST...)
    • HTTP Status Codes (200, 404 ...)
  • When you create a resource in rest you define operations on it using the methods provided by HTTP i.e POST, GET etc
  • In REST you think in terms of resource which are in your application which you want to make available to other platforms.
  • REST does not care how you represent your resource (URI) i.e XML, JSON, HTML.
  • Transportation through HTTP.
  • Service Definition: No standard. WADL, Swagger etc. Its is optional to provide service definition

Best Practices for REST

  • Resource in the URI should be noun
  • For different operations use verbs defined by HTTP i.e POST, GET, PUT and DELETE
  • Response of API
    • Success 2**
    • Client Error 4**
    • Server Error 5**
  • Some common Reponse Status
    • 200 Success
    • 404 Resource not found
    • 400 Bad Request
    • 201 Created 
    • 401 Unauthorized
    • 500 Server Error
  • Use plurals e.g /users or /users/1

Sample Application

 

 

git@github.com:pulkitpushkarna/rest-with-spring.git

build.gradle

buildscript {
	ext {
		springBootVersion = '2.0.3.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'


repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.boot:spring-boot-starter-web')
	compile('org.springframework.boot:spring-boot-starter-data-jpa')
	runtime('mysql:mysql-connector-java')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

SpringRestApplication

package com.spring.rest.springrest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.spring.rest.springrest.repositories")
@EntityScan(basePackages = {"com.spring.rest.springrest.entities"})
public class SpringRestApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringRestApplication.class, args);
	}
}

application.properties

spring.datasource.url=jdbc:mysql://localhost/rest_with_spring
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create

Creating action to get user by Id and save User

package com.spring.rest.springrest.controllers;


import com.spring.rest.springrest.entities.User;
import com.spring.rest.springrest.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/")
    String helloWorld(){
        return "Hello World";
    }

    @GetMapping("/users")
    List<User> getUsers(){
        return userService.getAllUsers();
    }

    @GetMapping("/users/{id}")
    User getUserById(@PathVariable Long id){
        return userService.getUserById(id);
    }

    @PostMapping("/users")
    User saveUser( @RequestBody User user){
        userService.saveUser(user);
        return user;
    }
}
package com.spring.rest.springrest.services;

import com.spring.rest.springrest.entities.User;
import com.spring.rest.springrest.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class UserService {

    @Autowired
    UserRepository userRepository;

    public List<User> getAllUsers(){
        return (List<User>)userRepository.findAll();
    }

    public User getUserById(Long id){
        Optional<User> optional=userRepository.findById(id);
        return optional.isPresent() ? optional.get(): null;
    }

    public void saveUser(User user){
        userRepository.save(user);
    }
}

Add methods to retrieve user by Id and save user in user service

Enhancing post method to return the correct status code

@PostMapping("/users")
    ResponseEntity<User> saveUser(@RequestBody User user){
        userService.saveUser(user);

        URI uri=ServletUriComponentsBuilder
                .fromCurrentRequest()
                .path("{id}").buildAndExpand(user.getId()).toUri();

        return ResponseEntity.created(uri).build();
    }

Getting user with non existing Id

@GetMapping("/users/{id}")
    User getUserById(@PathVariable Long id){
        User user=userService.getUserById(id);
        if(user==null){
            throw new UserNotFoundException("User not found!!");
        }
        return userService.getUserById(id);
    }
package com.spring.rest.springrest.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {

   public UserNotFoundException(String message){
        super(message);
    }
}

Generic Exception Handling for all resources

package com.spring.rest.springrest.exceptions;

import java.util.Date;

public class ExceptionResponse {

    private Date timestamp;
    private String message;
    private String details;

    public ExceptionResponse(Date timestamp, String message, String details) {
        this.timestamp = timestamp;
        this.message = message;
        this.details = details;
    }

    public Date getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Date timestamp) {
        this.timestamp = timestamp;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
    }
}
package com.spring.rest.springrest.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.Date;

@ControllerAdvice
@RestController
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request)  {
      ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(),ex.getMessage(),request.getDescription(false));
      return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(UserNotFoundException.class)
    public final ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(),ex.getMessage(),request.getDescription(false));
        return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
    }
}

Deleting the user

@DeleteMapping("/users/{id}")
    void deleteUser(@PathVariable Long id){
        User user=userService.getUserById(id);

        if(user==null) {
            throw new UserNotFoundException("User not found");
        }
        userService.deleteUser(user);

    }
//UserService.java

public void deleteUser(User user){
        userRepository.delete(user);
    }

Validating the Bean

package com.spring.rest.springrest.entities;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.Size;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Size(min = 2,message = "Name should have at least 2 characters")
    private String name;

    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
@PostMapping("/users")
    ResponseEntity<User> saveUser(@Valid @RequestBody User user){
        userService.saveUser(user);

        URI uri=ServletUriComponentsBuilder
                .fromCurrentRequest()
                .path("{id}").buildAndExpand(user.getId()).toUri();

        return ResponseEntity.created(uri).build();
    }
package com.spring.rest.springrest.exceptions;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@ControllerAdvice
@RestController
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @Autowired
    MessageSource messageSource;

    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request)  {
      ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(),ex.getMessage(),request.getDescription(false));
      return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(UserNotFoundException.class)
    public final ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(),ex.getMessage(),request.getDescription(false));
        return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
    }

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        List<String> errorMessageList=ex.getBindingResult().getAllErrors().stream().filter(e->e instanceof FieldError).map((e)->{

            FieldError fieldError = (FieldError) e;
            return   messageSource.getMessage(fieldError, null);
        }).collect(Collectors.toList());

        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(),"Validation Failed....",errorMessageList.toString());
        return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST);
    }
}

Exercise 1

  • Create an Entity Student with fields:
    • Name
    • Standard
    • age
    • city
    • percentage
  • Generate the bootstrap data for Student
  • Create Student Controller and introduce following actions in it:
    • Get employee List
    • Get Single Employee
    • Create Employee
    • Delete Employee
    • Update Employee

 

Exercise 1 (Cont.)

  • For Post Request send resource URI once the Student is created.
  • Create an UnChecked Exception StudentNotFound and Throw it in case of POST, PUT and DELETE if you don't find the User.
  • Create your customise response with timestamp, message and details and return your customise response on error
  • Validate the Student bean and show the relevant error messages in error response

Internationalization

Create 2 files in resources directory

messages.properties

good.morning.message=Good Morning

messages_fr.properties

good.morning.message=Bonjour
@GetMapping("/")
    String helloWorld(@RequestHeader(name = "Accept-Language",required = false)Locale locale){
        System.out.println("hello world");
        System.out.println(locale.getLanguage());
        return messageSource.getMessage("good.morning.message",null,locale);
    }

Accept-Language will be passed in header with language code

@Bean
   public ResourceBundleMessageSource messageSource() {
       ResourceBundleMessageSource source = new ResourceBundleMessageSource();
       source.setBasenames("i18n/messages");
       source.setUseCodeAsDefaultMessage(true);
       return source;
   }

Need to Configure this bean if you are not getting ResourceBudleMessageSource

More efficient way of introducing i18n

spring.mvc.locale=en
spring.mvc.locale-resolver=accept_header

application.properties

 @GetMapping("/")
    String helloWorld(){
        return messageSource.getMessage("good.morning.message",null,LocaleContextHolder.getLocale());
    }

Content Negotiaition

If you try to hit user API with Accept header value application/xml. You will get 406 Not Acceptable. In order to provide support for xml include the following dependency in build.gradle

compile ('com.fasterxml.jackson.dataformat:jackson-dataformat-xml')

For Post request you need to set the Accept:application/xml. Following will be the data format

<item>
        
        <name>Sunny</name>
        <age>46</age>
    </item>

Static Filtering 

If you want to filter some field from your object you can do it by using @JsonIgnore at the top of the field.

 

@JsonIgnoreProperties(value={"field1","field2"}) annotation is placed at the top of the class to ignore the fields in the reponse.

 

@Entity
@JsonIgnoreProperties(value = {"age"})
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Size(min = 2,message = "Name should have at least 2 characters")
    private String name;

    //@JsonIgnore
    private Integer age;

   //Getters and Setters
}

Dynamic Filtering 

package com.spring.rest.springrest.entities;

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("myFilter")
public class Employee {

    private String name;
    private Integer age;
    private Integer salary;

    public Employee(String name, Integer age, Integer salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSalary() {
        return salary;
    }

    public void setSalary(Integer salary) {
        this.salary = salary;
    }
}
package com.spring.rest.springrest.controllers;

import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.spring.rest.springrest.entities.Employee;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
public class EmployeeController {

    @GetMapping("/employee")
    MappingJacksonValue getEmployee(){
        Employee employee=new Employee("name1",23,23000);
        SimpleBeanPropertyFilter simpleBeanPropertyFilter=
                SimpleBeanPropertyFilter.filterOutAllExcept("name","salary");

        FilterProvider filterProvider=new SimpleFilterProvider()
                .addFilter("myFilter",simpleBeanPropertyFilter);

        MappingJacksonValue mappingJacksonValue= new MappingJacksonValue(employee);
        mappingJacksonValue.setFilters(filterProvider);
        return mappingJacksonValue;
    }

    @GetMapping("/employee-list")
    MappingJacksonValue getEmployeeList(){

        List<Employee> employeeList= Arrays.asList(new Employee("name1",23,23000),
                new Employee("name2",24,24000),
                new Employee("name3",25,25000));

        SimpleBeanPropertyFilter simpleBeanPropertyFilter=
                SimpleBeanPropertyFilter.filterOutAllExcept("name","age");

        FilterProvider filterProvider=new SimpleFilterProvider()
                .addFilter("myFilter",simpleBeanPropertyFilter);

        MappingJacksonValue mappingJacksonValue= new MappingJacksonValue(employeeList);
        mappingJacksonValue.setFilters(filterProvider);
        return mappingJacksonValue;

    }


}

Versioning

package com.spring.rest.springrest.versioning;

public class PersonV1 {

    private String name;

    public PersonV1(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package com.spring.rest.springrest.versioning;

public class PersonV2 {

    private Name name;

    public PersonV2(Name name) {
        this.name = name;
    }

    public Name getName() {
        return name;
    }

    public void setName(Name name) {
        this.name = name;
    }
}
package com.spring.rest.springrest.versioning;

public class Name {

    private String firstName;
    private String secondName;

    public Name(String firstName, String secondName) {
        this.firstName = firstName;
        this.secondName = secondName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    public void setSecondName(String secondName) {
        this.secondName = secondName;
    }
}
package com.spring.rest.springrest.versioning;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PersonController {

    //URI Versioning

    @GetMapping("/person/V1")
    PersonV1 getPersonV1() {
        return new PersonV1("Peter Parker");
    }

    @GetMapping("/person/V2")
    PersonV2 getPersonV2() {
        return new PersonV2(new Name("Peter","Parker"));
    }

    //Parameter Versioning
    // /person/param?version=1
    @GetMapping(value = "/person/param",params = "version=1")
    PersonV1 getPersonParam1() {
        return new PersonV1("Peter Parker");
    }

    @GetMapping(value = "/person/param",params = "version=2")
    PersonV2 getPersonParam2() {
        return new PersonV2(new Name("Peter","Parker"));
    }


    //Header Versioning
    //API-VERSION 1 header required with request
    @GetMapping(value = "/person/header",headers = "API-VERSION=1")
    PersonV1 getPersonHeader1() {
        return new PersonV1("Peter Parker");
    }

    @GetMapping(value = "/person/header",headers = "API-VERSION=2")
    PersonV2 getPersonHeader2() {
        return new PersonV2(new Name("Peter","Parker"));
    }

    //Mime Type/Content Negotiation/Accept Header Versioning
    //Accept application/app-v1+json header required with request
    @GetMapping(value = "/person/produces",produces = "application/app-v1+json")
    PersonV1 getPersonProducer1() {
        return new PersonV1("Peter Parker");
    }

    @GetMapping(value = "/person/produces",produces = "application/app-v2+json")
    PersonV2 getPersonProducer2() {
        return new PersonV2(new Name("Peter","Parker"));
    }

}

Exercise 2

  • Perform i18n in your application for message "Have a nice day" for 2 languages English and German.
  • Provide the support for both JSON and XML in your Application.
  • Perform Static and Dynamic Filtering on Student entity.
  • Create 2 versions for Get Students API in one version we will return the percentage and in other we will not return the percentage. Perform following Versioning:
    • URI versioning
    • Parameter Versioning 
    • Header Versioning
    • Accept Header Versioning

Consuming API with REST Template

 @GetMapping("/post")
    public String getPost(){
        RestTemplate restTemplate = new RestTemplate();
        String url="https://jsonplaceholder.typicode.com/posts/1";
        ResponseEntity<String> response = restTemplate.getForEntity(url,String.class);
        System.out.println(response.getStatusCode().toString());
        System.out.println(response.getHeaders().toString());
        return response.getBody();
    }

Getting the Data in String Format

Getting and parsing the data to an Object

@GetMapping("/postObject")
    public Post getPostObject(){
        RestTemplate restTemplate = new RestTemplate();
        String url="https://jsonplaceholder.typicode.com/posts/1";
        ResponseEntity<Post> response = restTemplate.getForEntity(url,Post.class);
        System.out.println(response.getStatusCode().toString());
        System.out.println(response.getHeaders().toString());
        return response.getBody();
    }

Getting the List of Objects

 @GetMapping("/postList")
    public List<Post> getPostList(){
        RestTemplate restTemplate = new RestTemplate();
        String url="https://jsonplaceholder.typicode.com/posts";
        ResponseEntity<List<Post>> response = restTemplate
                .exchange(url, HttpMethod.GET, null,
                new ParameterizedTypeReference<List<Post>>(){});
        return response.getBody();
    }

Create Post

@PostMapping("/createPost")
    public ResponseEntity<Post> createPost(){
        String url="https://jsonplaceholder.typicode.com/posts";
        RestTemplate restTemplate= new RestTemplate();
        HttpHeaders httpHeaders= new HttpHeaders();
        httpHeaders.add("Content-type","application/json; charset=UTF-8");
        HttpEntity<Post> request=new HttpEntity<>(new Post(10000L,10000L,"title1","description1"),httpHeaders);
        Post post=restTemplate.postForObject(url,request,Post.class);
        System.out.println(post);
        return new ResponseEntity<Post>(post, HttpStatus.CREATED);
    }

Updating the POST

@PutMapping("/updatePost")
    public ResponseEntity<Post> updatePost(){
        String url="https://jsonplaceholder.typicode.com/posts/1";
        RestTemplate restTemplate= new RestTemplate();
        HttpHeaders httpHeaders= new HttpHeaders();
        httpHeaders.add("Content-type","application/json; charset=UTF-8");
        HttpEntity<Post> request=new HttpEntity<>(new Post(1L,100L,"title1","description1"),httpHeaders);
        return restTemplate.exchange(url,HttpMethod.PUT,request,Post.class);
    }

Deleting the POST

@DeleteMapping("/deletePost")
    public void deletePost(){
        String url="https://jsonplaceholder.typicode.com/posts/1";
        RestTemplate restTemplate= new RestTemplate();
        HttpHeaders httpHeaders= new HttpHeaders();
        httpHeaders.add("Content-type","application/json; charset=UTF-8");
        HttpEntity<Post> request=new HttpEntity<>(httpHeaders);
         restTemplate.exchange(url,HttpMethod.DELETE,request,Post.class);
    }

Exercise 3

  • Use RestTemplate to interact API's outside your application. Use Employee resource on http://dummy.restapiexample.com/. Try out all the API's of Employee

Rest Assured for API Testing

        testCompile('io.rest-assured:rest-assured:3.1.0')
	testCompile('com.jayway.restassured:rest-assured:2.3.3')
	testCompile 'org.hamcrest:hamcrest-all:1.3'

Gradle Dependencies for rest assured

For Testing with Rest Assured we will use following API's:

https://jsonplaceholder.typicode.com/

package com.spring.rest.springrest;


import com.jayway.restassured.RestAssured;
import com.jayway.restassured.response.Header;

import static org.hamcrest.Matchers.*;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.Map;

@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringRestApplicationTests {


    @Test
    public  void canary(){
        Assert.assertEquals(1,1);
    }

    @Test
    public void testingGoogle(){
        RestAssured.given().get("https://www.google.com/").then().statusCode(200);
    }

   
}

Canary Test 

    String uri="https://jsonplaceholder.typicode.com/posts/{id}";

  

    @Test
    public void validPostTest() {
	 RestAssured.given().pathParam("id",1)
               .get(uri).then()
               .statusCode(200).log().all();
    }

    @Test
    public void invalidPostTest() {
        RestAssured.given().pathParam("id",1000)
                .get(uri).then()
                .statusCode(404).log().all();
    }

Simple Test for Get 

More Tests for GET

        @BeforeClass
        public static void init(){
            RestAssured.baseURI="https://jsonplaceholder.typicode.com";
            RestAssured.basePath="/posts/{id}";
        }

        @Test
	public void postUserIdValueMatchingTest(){
        RestAssured.given().pathParam("id",1)
                .get().then()
                .body("userId", equalTo(1))
                .log().all();;
	}

    @Test
    public void invalidPostTest() {
        RestAssured.given().pathParam("id",1000)
                .get().then()
                .statusCode(404).log().all();
    }

Matching Values

@Test
	public void postUserIdValueMatchingTest(){
        RestAssured.given().pathParam("id",1)
                .get(uri).then()
                .body("userId", equalTo(1))
                .log().all();;
	}

    @Test
    public void postUserIdAndTitleValueMatchingTest(){
        RestAssured.given().pathParam("id",1)
                .get(uri).then()
                .body("userId", equalTo(1))
                .body("title",equalTo("sunt aut facere repellat provident occaecati excepturi optio reprehenderit"))
                .log().all();;
    }
@Test
    public void postCreationTest(){
        Map<String,String> map = new HashMap<>();
        map.put("userId","1");
        map.put("title","title1");
        map.put("body","body1");

        RestAssured.given().header(new Header("Content-type","application/json; charset=UTF-8"))
                .body(map)
                .when().post("https://jsonplaceholder.typicode.com/posts").then()
                .statusCode(201).log().all();

    }

Test for Post

Test for Put

@Test
    public void postUpdationTest(){
        Map<String,String> map = new HashMap<>();
        map.put("id","1");
        map.put("userId","1");
        map.put("title","title1");
        map.put("body","body1");
        RestAssured.given().pathParam("id",1).header(new Header("Content-type","application/json; charset=UTF-8"))
                .body(map)
                .when().put(uri).then()
                .statusCode(200).log().all();
    }

Test for Delete

  @Test
    public void postDeletionTest(){
        RestAssured.given().pathParam("id",1).header(new Header("Content-type","application/json; charset=UTF-8"))
                .when().delete(uri).then()
                .statusCode(200).log().all();
    }
package com.spring.rest.springrest;


import com.jayway.restassured.RestAssured;
import com.jayway.restassured.response.Header;

import static org.hamcrest.Matchers.*;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.Map;

@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringRestApplicationTests {


    @Test
    public  void canary(){
        Assert.assertEquals(1,1);
    }

    @Test
    public void testingGoogle(){
        RestAssured.given().get("https://www.google.com/").then().statusCode(200);
    }

    String uri="https://jsonplaceholder.typicode.com/posts/{id}";

    @BeforeClass
    public static void init(){
        RestAssured.baseURI="https://jsonplaceholder.typicode.com";
        RestAssured.basePath="/posts/{id}";
    }

	@Test
	public void validPostTest() {
		RestAssured.given().pathParam("id",1)
                .get(uri).then()
                .statusCode(200).log().all();
	}

    @Test
    public void invalidPostTest() {
        RestAssured.given().pathParam("id",1000)
                .get(uri).then()
                .statusCode(404).log().all();
    }


	@Test
	public void postUserIdValueMatchingTest(){
        RestAssured.given().pathParam("id",1)
                .get(uri).then()
                .body("userId", equalTo(1))
                .log().all();;
	}

    @Test
    public void postUserIdAndTitleValueMatchingTest(){
        RestAssured.given().pathParam("id",1)
                .get(uri).then()
                .body("userId", equalTo(1))
                .body("title",equalTo("sunt aut facere repellat provident occaecati excepturi optio reprehenderit"))
                .log().all();;
    }

    @Test
    public void postCreationTest(){
        Map<String,String> map = new HashMap<>();
        map.put("userId","1");
        map.put("title","title1");
        map.put("body","body1");

        RestAssured.given().header(new Header("Content-type","application/json; charset=UTF-8"))
                .body(map)
                .when().post("https://jsonplaceholder.typicode.com/posts").then()
                .statusCode(201).log().all();

    }

    @Test
    public void postUpdationTest(){
        Map<String,String> map = new HashMap<>();
        map.put("id","1");
        map.put("userId","1");
        map.put("title","title1");
        map.put("body","body1");
        RestAssured.given().pathParam("id",1).header(new Header("Content-type","application/json; charset=UTF-8"))
                .body(map)
                .when().put(uri).then()
                .statusCode(200).log().all();
    }

    @Test
    public void postDeletionTest(){
        RestAssured.given().pathParam("id",1).header(new Header("Content-type","application/json; charset=UTF-8"))
                .when().delete(uri).then()
                .statusCode(200).log().all();
    }

}

Following file will reside under test java

Exercise 4

  • Use Rest Assured to test all API's on http://dummy.restapiexample.com/

 OAuth2 with Spring Security

  • OAuth2 is a standard for authorization.
  • A high level goal of OAuth is allowing a Resource Owner to give access to a third party in a limited way, without giving away the password.

Roles and Actors in OAuth

  • The Resource Owner (the user) is capable of granting access to a Resource.

 

  • The Resource Server (the API) is the host of the protected Resources.

 

  • The Authorization Server is capable of issuing Access Tokens to the Client.

 

  • The Client (the front end app) is capable of making requests on behalf of the Resource Owner.

Getting Start with OAuth config in Spring Security

build.gradle

apply plugin: 'java'
apply plugin: org.springframework.boot.gradle.plugin.SpringBootPlugin

buildscript {
    ext {
        springBootVersion = "1.4.0.RELEASE"
        propdepsPluginVersion = "0.0.7"
    }
    repositories {
        jcenter()
        maven { url 'http://repo.spring.io/plugins-release' }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
        classpath("org.springframework.build.gradle:propdeps-plugin:$propdepsPluginVersion")
    }
}


repositories {
    mavenCentral()
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.security.oauth:spring-security-oauth2")
    compile("org.springframework.security:spring-security-jwt")
}

Authorization Server

package com.spring_security.oauth.config;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    AuthenticationManager authenticationManager;

    public AuthorizationServerConfiguration() {
        super();
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }


    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("live-test")
                .secret("abcde")
                .authorizedGrantTypes("password")
                .scopes("app")
                .accessTokenValiditySeconds(30);

    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
        authorizationServerSecurityConfigurer.allowFormAuthenticationForClients();
    }
}

Resource Server

package com.spring_security.oauth.config;

@Configuration
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    public ResourceServerConfiguration() {
        super();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        return authenticationProvider;
    }

    @Autowired
    public void configureGlobal(final AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(authenticationProvider());
    }

    @Override
    public void configure(final HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .csrf().disable();
    }
}

GrantAuthorityImpl

package com.spring_security.oauth.model;

import org.springframework.security.core.GrantedAuthority;

public class GrantAuthorityImpl implements GrantedAuthority {

    String authority;

    @Override
    public String getAuthority() {
        return authority;
    }
}

User Entity

import java.util.List;

public class User implements UserDetails {

    private String username;
    private String password;
    List<GrantAuthorityImpl> grantAuthorities;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public List<GrantAuthorityImpl> getAuthorities() {
        return grantAuthorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", grantAuthorities=" + grantAuthorities +
                '}';
    }
}

UserDetailService

package com.spring_security.oauth.service;

import com.spring_security.oauth.model.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class AppUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDetails userDetails = new User(username, "pass");
        return userDetails;
    }
}

Introduce Controller action

package com.spring_security.oauth;

import javax.annotation.security.RolesAllowed;

@Controller
@SpringBootApplication
@RolesAllowed("ROLE_ANONYMOUS")
public class OauthApplication {

	@RequestMapping("/")
	@ResponseBody
	String home() {
		return "Hello World!";
	}

	public static void main(String[] args) {
		SpringApplication.run(OauthApplication.class, args);
	}
}

Post Request for Authentication

Request:

http://localhost:8080/oauth/token <Post Request>

grant_type : password

client_id : live-test

username : user

password : pass

client_secret : abcde

 

Response:

{
  "access_token": "fc132af8-e5ad-4318-8f29-b3cf95b51161",
  "token_type": "bearer",
  "expires_in": 29,
  "scope": "app"
}

Now lets hit the controller action

 

Request:

localhost:8080/

 <Header > Authorization bearer <access-token>

 

JWT token

A simple high level structure of JWT Token is:

headers.payloads.signature

Configuration changes for JWT Token in Authorization Server

 @Bean
    JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey("1234");
        return jwtAccessTokenConverter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

@Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .authenticationManager(authenticationManager)
                .accessTokenConverter(accessTokenConverter());
    }

Configuration changes in Authorization Server for Refresh Token

    @Autowired
    UserDetailsService userDetailsService;

    @Bean
    @Primary
    DefaultTokenServices tokenServices(){
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("live-test")
                .secret("abcde")
                .authorizedGrantTypes("password","refresh_token")
                .refreshTokenValiditySeconds(30 * 24 * 3600)
                .scopes("app")
                .accessTokenValiditySeconds(30);

    }

     @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .userDetailsService(userDetailsService)
                .authenticationManager(authenticationManager);
    }

Refresh Token

Request:

http://localhost:8080/oauth/token

grant_type : refresh_token

refresh_token: <refresh_token>

client_id:<client_id>

client_secret:<client_secret>

 

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjQyMzk4MzUsInVzZXJfbmFtZSI6InVzZXIiLCJqdGkiOiI4MWJkNjQ3Ny02NmY3LTRjNWUtODdkOS1lZmE2NjdlMzhmZDAiLCJjbGllbnRfaWQiOiJsaXZlLXRlc3QiLCJzY29wZSI6WyJhcHAiXX0.lNu44yo3HgJ2EkTcRHKj4mu9Rq6Nhi9IMXT_SM1PIxk",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjQyNjk1MDQsInVzZXJfbmFtZSI6InVzZXIiLCJqdGkiOiI2NTk5NTdiMC04NzM4LTRjNjktYTk5My01Y2Q5YzQ1NTg3NzMiLCJjbGllbnRfaWQiOiJsaXZlLXRlc3QiLCJzY29wZSI6WyJhcHAiXSwiYXRpIjoiODFiZDY0NzctNjZmNy00YzVlLTg3ZDktZWZhNjY3ZTM4ZmQwIn0.Vx2dKyTympi6VTBnGid4uW5yGavcsRvwD5ezy1b3M8I",
  "expires_in": 29,
  "scope": "app",
  "jti": "81bd6477-66f7-4c5e-87d9-efa667e38fd0"
}

Gradle project with oauth dependencies

git@github.com:pulkitpushkarna/spring-oauth-boiler-plate-code.git

Exercise 5

Create a Spring Boot Application and Implement OAuth in it.

Rest With Spring

By Pulkit Pushkarna

Rest With Spring

  • 1,291