Initializing a RESTful service with Spring boot :
Spring Initializer http://start.spring.io/ is great tool to bootstrap your Spring Boot projects.
Specify group name, artifact name and other configs such as Maven project , selecting spring boot version .. Select appropriate dependencies .
POSTMAN:
Postman is a wonderful REST client and it has all features needed for accessing Rest services. Downloading Chrome Postman plugin will install. We can fire all GET , POST , PUT, DELETE requests from it.
Creating Hello World Rest Service :
@RestController
public class HelloWorldController {
@GetMapping(path = “/hello-world”)
public String helloWorld() {
return “Hello World”;
} }
Example to explain GET Mapping :
UserDaoService – Fetches the data and handles data bases interactions. DAO pattern example.
@Component
public class UserDaoService {
private static List<User> users = new ArrayList<>();
private static int usersCount = 3; . . .
}
Code for GET mapping method example :
@RestController
public class UserResource {
@Autowired
private UserDaoService service;
@GetMapping(“/users”)
public List<User> retrieveAllUsers() {
return service.findAll();
}
@GetMapping(“/users/{id}”)
public User retrieveUser(@PathVariable int id) {
return service.findOne(id);
}
POST Mapping Code :
// input – details of user
// output – CREATED & Return the created URI
@PostMapping(“/users”)
public void createUser(@RequestBody User user){
User savedUser = service.save(user);
}
Enhancing POST Method to return correct HTTP Status Code and Location
// input – details of user
// output – CREATED & Return the created URI
@PostMapping(“/users”)
public ResponseEntity<Object> createUser(@RequestBody User user) {
User savedUser = service.save(user);
// CREATED
// /user/{id} savedUser.getId()
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path(“/{id}”)
.buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
Code for GET , POST and DELETE :
public User retrieveUser(@PathVariable int id) {
User user = service.findOne(id);
if(user==null)
throw new UserNotFoundException(“id-“+ id);
return user;
}
@DeleteMapping(“/users/{id}”)
public void deleteUser(@PathVariable int id) {
User user = service.deleteById(id);
if(user==null)
throw new UserNotFoundException(“id-“+ id);
}
//
// input – details of user
// output – CREATED & Return the created URI
@PostMapping(“/users”)
public ResponseEntity<Object> createUser(@RequestBody
User user)
{
User savedUser = service.save(user);
// CREATED
// /user/{id} savedUser.getId()
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path(“/{id}”)
.buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
Exception Handling Code :
To make these exceptions applicable to all controllers we needs a centralized customized exception handling. It is done by extending ResponseEntityExceptionHandler..
@ControllerAdvice // Share across multiple controllers. We are making all controllers aware of all exceptions.
@RestController // Since it is providing response back in case if error happens
public class CustomizedResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public final ResponseEntity<Object>
handleAllExceptions(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new
Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity(errorDetails,
HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(UserNotFoundException.class)
public final ResponseEntity<Object>
handleUserNotFoundException(UserNotFoundException ex,
WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new
Date(),
ex.getMessage(),
request.getDescription(false));
return new ResponseEntity(errorDetails,
HttpStatus.NOT_FOUND);
}
}
Simple UserNotFoundException:
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
Adding validations:
Step 1 : Define validations in POJO .
User.java is a pojo and it has name and birthDate attributes.
@Size(min=2, message=”Name should have atleast 2 characters”)
private String name;
@Past
private Date birthDate;
Step 2: @valid annotation
public ResponseEntity<Object> createUser(@Valid
@RequestBody User user) {
HATEOAS :
HATEOAS (Hypermedia as the Engine of Application State) is a constraint of the REST application architecture. A hypermedia-driven site provides information to navigate the site’s REST interfaces dynamically by including hypermedia links with the responses.
As an example when a particular user details are fetched , we can return a link that can be accessed to fetch all users. In short we will be sending additional details in terms of links in addition to what was requested for in the response.
Implementing HATEOAS In Restful services :
POM Dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
Green Colored will explain on what is needed for implementing HATEOAS link
@GetMapping(“/users/{id}”)
public Resource<User> retrieveUser(@PathVariable int id)
{
User user = service.findOne(id);
if(user==null)
throw new UserNotFoundException(“id-“+ id);
//”all-users”, SERVER_PATH + “/users”
//retrieveAllUsers
Resource<User> resource = new Resource<User>(user);
ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
resource.add(linkTo.withRel(“all-users”));
//HATEOAS
return resource;
}
Content Negotiation – Implementing Support for XML . If we want the response to be in XML format , it can be done by specifying the dependency. By default json is the output but we if specify in ACCEPT header as XML (meaning – we need the response in XML) , we need the following dependency.
/pom.xml Modified
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
SWAGGER :
Swagger is one of the popular documentation formats for Rest services. For Webservices we have WSDL that describes the contract to the clients and similarly Swagger helps in documenting web services.
For this we need to add dependencies :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
We need to configure Swagger now .
@Configuration // Tells Spring that it is a configuration Bean
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2);
}}
we can access by http://localhost:8080/v2/api_docs or http://localhost:8080/swagger-ui.html
Spring Boot Actuator
Spring Boot Actuator is a sub-project of Spring Boot. It adds several production grade services to your application with little effort on your part. It is very important to have monitoring against our micro services and actuator helps us with these metrics .
HAL Browser is a tool built from the creator of the HAL Specification. There are a few tweaks that are made to integrate seamlessly with Spring Boot. It specifies how to hyperlink API so that we can easily browse through the API . The services are actually exposed by actuator but the HAL browser makes it easy for viewing / navigating to different links.
Step 1 : Add the dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
Step 2: Configure application properties.
application.properties Modified
management.endpoints.web.exposure.include=*
actuator URL : http://localhost:8080/actuator
hal browser : just type http://localhost:8080/
Versioning :
Option 1: URI Versioning (Used by Twitter)
Basic approach using URI’s (http://api.yourservice.com/v1/companies or http://api.yourservice.com/v2/companies
@GetMapping(“v1/person”)
public PersonV1 personV1() {
return new PersonV1(“Bob Charlie”);
}
@GetMapping(“v2/person”)
public PersonV2 personV2() {
return new PersonV2(new Name(“Bob”, “Charlie”));
}
Option 2: Request param versioning (Used by amazon)
http://api.yourservice.com//person/param?version=”1″
@GetMapping(value = “/person/param”, params =
“version=1”)
public PersonV1 paramV1()
{
return new PersonV1(“Bob Charlie”);
}
@GetMapping(value = “/person/param”, params =
“version=2”)
public PersonV2 paramV2() {
return new PersonV2(new Name(“Bob”, “Charlie”));
}
Option 3 : Using Header param
@GetMapping(value = “/person/header”, headers = “X-APIVERSION=
1″)
public PersonV1 headerV1() {
return new PersonV1(“Bob Charlie”);
}
@GetMapping(value = “/person/header”, headers = “X-APIVERSION=
2″)
public PersonV2 headerV2() {
return new PersonV2(new Name(“Bob”, “Charlie”));
}
With option 1 and 2 , we are polluting the URI space. With option 3 , we are problematically setting the headers to include the version and hence much cleaner but we can’t bookmark the URL and at the same time it is much complex since we have to set the header in the code instead of using option1 and option 2. All are popular options and depends upon the company.
Authentication :
- Simple Authentication
- Digest Authentication
- OAuth Authentication
Simple Authentication :
Option 1 :
We can do this by adding the following 2 steps :
Adding Dependency :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Adding the below params in application.properties
spring.security.filter.dispatcher-types=request
spring.security.user.name=username
spring.security.user.password=password
The header request should have the username and password when they are trying to sen d the request.
Option 2:
In the context of a HTTP transaction, basic access authentication is a method for an HTTP user agent to provide a user name and password when making a request.
HTTP Basic authentication implementation is the simplest technique for enforcing access controls to web resources because it doesn’t require cookies, session identifier and login pages. Rather, HTTP Basic authentication uses static, standard HTTP headers which means that no handshakes have to be done in anticipation.
When the user agent wants to send the server authentication credentials it may use the Authorization header. The Authorization header is constructed as follows:
1) Username and password are combined into a string “username:password”
2) The resulting string is then encoded using Base64 encoding
3) The authorization method and a space i.e. “Basic ” is then put before the encoded string.
For example, if the user agent uses ‘Aladdin’ as the username and ‘open sesame’ as the password then the header is formed as follows:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
At API Server Side :
Provide a class that has annotation @EnableWebSecurity and extends from WebSecurityConfigurerAdapter to implement following two methods.
1) configure(HttpSecurity http) – to configure the BasicAuthenticationEntryPoint to be used when authentication fails or a request is made without authentication. If we don’t provide this then Spring’s default implementation will direct the user to a login screen which may or may not exist in your application.
2) configureGlobal(AuthenticationManagerBuilder auth) – to configure the inMemoryAuthentication (list of users and their bcrypted passwords).
@Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationEntryPoint authEntryPoint; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() .anyRequest().authenticated() .and().httpBasic() .authenticationEntryPoint(authEntryPoint); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("john123").password("password").roles("USER"); } }
Digest Authentication :
- This authentication method makes use of a hashing algorithms to encrypt the password (called password hash) entered by the user before sending it to the server. This, obviously, makes it much safer than the basic authentication method, in which the user’s password travels in plain text (or base64 encoded) that can be easily read by whoever intercepts it.
- There are many such hashing algorithms in java also, which can prove really effective for password security such as MD5, SHA, BCrypt, SCrypt and PBKDF2WithHmacSHA1 algorithms.
- Each time user login into application, you have to regenerate password hash again, and match with hash stored in database – At the Server end
-
<http create-session="stateless" entry-point-ref="digestEntryPoint"> <intercept-url pattern="/api/admin/**" access="ROLE_ADMIN" /> <http-basic /> <custom-filter ref="digestFilter" after="BASIC_AUTH_FILTER" /> </http> <beans:bean id="digestFilter" class= "org.springframework.security.web.authentication.www.DigestAuthenticationFilter"> <beans:property name="userDetailsService" ref="userService" /> <beans:property name="authenticationEntryPoint" ref="digestEntryPoint" /> </beans:bean> <beans:bean id="digestEntryPoint" class= "org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint"> <beans:property name="realmName" value="Contacts Realm via Digest Authentication"/> <beans:property name="key" value="acegi" /> </beans:bean> <authentication-manager> <authentication-provider> <user-service id="userService"> <user name="eparaschiv" password="eparaschiv" authorities="ROLE_ADMIN" /> <user name="user" password="user" authorities="ROLE_USER" /> </user-service> </authentication-provider> </authentication-manager>