Friday, 9 October 2020

How to Use Spring Security with UserDetailService in SpringBoot to Secure Rest Service End Point



Spring Security is a powerful and highly customizable authentication and access-control framework. 
It is the de-facto standard for securing Spring-based applications.


UserDetailsService Interface

UserDetailsService import org.springframework.security.core.userdetails.UserDetailsService
is interface that one method 

public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException

which invoke during authentication of user for fetching user details from database.

UserDetailServiceImpl.java

package in.jk.springbootapplication.configuration;

import java.util.Arrays;
import org.springframework.stereotype.Service;
import in.jk.springbootapplication.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;


@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private AdminService adminService;
private UserDetails userDetails;

@Override
public UserDetails loadUserByUsername(String userName) 
        throws UsernameNotFoundException {
in.jk.springbootapplication.entity.User user= null;
                user=adminService.findUserByUserName(userName);
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getRole());
userDetails = new org.springframework.security.core.userdetails.User(user.getUserName()                       , user.getPassword(),Arrays.asList(grantedAuthority));
  System.out.println("grantedAuthorizeUser :: " +userDetails);
 
  return userDetails;
}

public UserDetails getUserDetails() {
return userDetails;
}

public void setUserDetails(UserDetails userDetails) {
this.userDetails = userDetails;
}

Spring provide  WebSecurityConfigurerAdapter cofigure the Spring Security.
WebSecurityConfigurerAdapter class provides a 

public void configure(HttpSecurity http){}

method that contains the following default configuration.
The following class enable Spring Web Security with in memory authentication with provided users details .

SpingAPISecurityConfig.java

package in.jk.springbootapplication.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpingAPISecurityConfig extends WebSecurityConfigurerAdapter 
{

@Autowired
private UserDetailsService userDetailsService;

protected void configure(HttpSecurity httpSecurity) throws Exception {

httpSecurity.csrf().disable()
                                            .authorizeRequests().antMatchers("/adminapi/employee/**")
                                            .hasAnyAuthority("ADMIN")
                                             .and().authorizeRequests().antMatchers("adminapi/product/**")
                                             .hasAnyAuthority("USER").and().httpBasic();

}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
       auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);

}

}


Dirctory Structure of Spring boot application



























Maven Dependency for Spring Security 

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


Controller 
 In this application controller class contain three type of URL one for user service to add user the service URL start with user and other two start with employee and product protected with spring security . they require authentication access them .


AdminController.java

package in.jk.springbootapplication.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import in.jk.springbootapplication.request.EmployeeRequest;
import in.jk.springbootapplication.request.ProductRequest;
import in.jk.springbootapplication.request.UserRequest;
import in.jk.springbootapplication.response.Response;
import in.jk.springbootapplication.service.AdminService;

@RestController
@RequestMapping("/adminapi")
public class AdminController {

@Autowired
private AdminService adminService;


@RequestMapping(value = "/user/findUserById/{userId}", method = RequestMethod.GET)
public Response findUserById(@PathVariable Integer userId) {
return adminService.findUserById(userId);

}

@RequestMapping(value = "/user/addUser", method = RequestMethod.POST)
public Response addProduct(@RequestBody UserRequest user) {
return adminService.addUser(user);

}

@RequestMapping(value = "/employee/findEmployeeById/{employeeId}", 
          method = RequestMethod.GET)
public Response findEmployeeById(@PathVariable Integer employeeId) {

return adminService.findEmployeeById(employeeId);

}

@RequestMapping(value = "/employee/addEmployee", method = RequestMethod.POST)
public Response addEmployee(@RequestBody EmployeeRequest employee) {

return adminService.addEmployee(employee);

}

@RequestMapping(value = "/product/findProductById/{productId}",
         method = RequestMethod.GET)
public Response findProductById(@PathVariable Integer productId) {

return adminService.findProductById(productId);

}

@RequestMapping(value = "/product/addProduct", method = RequestMethod.POST)
public Response addProduct(@RequestBody ProductRequest product) {

return adminService.addProduct(product);

}

}

EmployeeRequest.java

package in.jk.springbootapplication.request;

public class EmployeeRequest {
private int employeeId;
private String name;
private String company;
        //Getters and Setters 
@Override
public String toString() {
return "Employee [employeeId=" + employeeId + ", 
               name=" + name + ", comapny=" + company + "]";
}
}

ProductRequest.java

package in.jk.springbootapplication.request;

public class ProductRequest {
private int productId;
private String productName;
private int price;
private String company;
       //Getters and Setters
@Override
public String toString() {
return "ProductRequest [productId=" + productId + ", 
                productName=" + productName + ", price=" + price
+ ", company=" + company + "]";
}
}

UserRequest.java

package in.jk.springbootapplication.request;
public class UserRequest {
private Integer userId;
private String userName;
private String name;
private String password;
private String emailId;
private String  role;
       //Getters and Setters
@Override
public String toString() {
return "UserRequest [userId=" + userId + ", userName=" + userName + ", 
                name=" + name + ", password=" + password
+ ", emailId=" + emailId + ", role=" + role + "]";
}
}

Spring Security application entity classes 

package in.jk.springbootapplication.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name="springboot.employee")
@NamedQuery(name="findEmployeeByName",query = "FROM Employee employee where employee.name=:name")
public class Employee {

@Id
@Column(name="employee_id")
private Integer employeeId;
@Column(name="name")
private String name;
@Column(name="company")
private String company;

// Getters and Setters

@Override
public String toString() {
return "Employee [employeeId=" + employeeId + ", name=" + name + ", 
       comapny=" + company + "]";
}
}


package in.jk.springbootapplication.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="springboot_product")
public class Product {
@Id
@Column(name="product_id")
private int productId;
@Column(name="product_name")
private String productName;
@Column(name="price")
private int price;
@Column(name="company")
private String company;
        //Getters and Setters
@Override
public String toString() {
return "ProductRequest [productId=" + productId + ", productName=" + 
                productName + ", price=" + price+ ", company=" + company + "]";
}

}

User.java

package in.jk.springbootapplication.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="springboot_user")
public class User {
@Id
@Column(name="user_id")
private Integer userId;
@Column(name="user_name")
private String userName;
@Column(name="name")
private String name;
@Column(name="password")
private String password;
@Column(name="email_id")
private String emailId;
@Column(name="role")
private String  role;
        //Getters and Setters
@Override
public String toString() {
return "UserRequest [userId=" + userId + ", userName=" + userName + ", 
                name=" + name + ", password=" + password
+ ", emailId=" + emailId + ", role=" + role + "]";
}
}

Spring Security application Repository classes 
EmployeeRepositry.java

package in.jk.springbootapplication.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import in.jk.springbootapplication.entity.Employee;

public interface EmployeeRepositry extends JpaRepository<Employee, Integer> {

}

ProductRepository.java
package in.jk.springbootapplication.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import in.jk.springbootapplication.entity.Product;

public interface ProductRepository  extends JpaRepository<Product, Integer>{
}

UserRepository.java

package in.jk.springbootapplication.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import in.jk.springbootapplication.entity.User;

public interface UserRepository extends JpaRepository<User, Integer> {
}

Spring Security Application class 
Response.java

package in.jk.springbootapplication.response;

public class Response {
private Object responseData;
private String responseMessage;
private String responseCode;
public void setResponse(String responseCode,Object data, String responseMessage) {
this.responseCode=responseCode;
this.responseData =data;
this.responseMessage=responseMessage;
}
public Object getResponseData() {
return responseData;
}
public void setResponseData(Object responseData) {
this.responseData = responseData;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
public String getResponseCode() {
return responseCode;
}
public void setResponseCode(String responseCode) {
this.responseCode = responseCode;
}
@Override
public String toString() {
return "Response [responseData=" + responseData + ", responseMessage=" + 
                responseMessage + ", responseCode="
+ responseCode + "]";
}

}


Service Interface and class of Spring Security Application 
AdminService.java
package in.jk.springbootapplication.service;

import in.jk.springbootapplication.entity.User;
import in.jk.springbootapplication.request.EmployeeRequest;
import in.jk.springbootapplication.request.ProductRequest;
import in.jk.springbootapplication.request.UserRequest;
import in.jk.springbootapplication.response.Response;

public interface AdminService {

public Response findUserById(Integer userId);

public Response addUser(UserRequest user);
public User findUserByUserName(String userName);
        public Response findEmployeeById(Integer employeeId);

public Response addEmployee(EmployeeRequest employee);

public Response findProductById(Integer productId);

public Response addProduct(ProductRequest product);

}

AdminServiceImpl.java
 
package in.jk.springbootapplication.service.impl;

import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import in.jk.springbootapplication.entity.Employee;
import in.jk.springbootapplication.entity.Product;
import in.jk.springbootapplication.entity.User;
import in.jk.springbootapplication.repository.EmployeeRepositry;
import in.jk.springbootapplication.repository.ProductRepository;
import in.jk.springbootapplication.repository.UserRepository;
import in.jk.springbootapplication.request.EmployeeRequest;
import in.jk.springbootapplication.request.ProductRequest;
import in.jk.springbootapplication.request.UserRequest;
import in.jk.springbootapplication.response.Response;
import in.jk.springbootapplication.service.AdminService;

@Service
public class AdminServiceImpl implements AdminService {

@Autowired
private UserRepository userRepository;
@Autowired
private EmployeeRepositry employeeRepositry;
@Autowired
private ProductRepository productRepository;

@Override
public Response findUserById(Integer userId) {

Response response = null;
User entity = null;

response = new Response();
try {
Optional<User> user = userRepository.findById(userId);
if (user.isPresent()) {
entity = user.get();
response.setResponse("200", entity, "User Find Succussfully ");

} else {
response.setResponse("200", entity, "User Not Present ");

}

} catch (Exception e) {
response.setResponse("500", e.getMessage(), "Error in Finding User ");
}

return response;
}

@Override
public Response addUser(UserRequest userRequest) {

Response response = null;
User user = new User();
user.setUserId(userRequest.getUserId());
user.setName(userRequest.getName());
user.setUserName(userRequest.getUserName());
user.setEmailId(userRequest.getEmailId());
user.setRole(userRequest.getRole());

BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode(userRequest.getPassword());

user.setPassword(password);

response = new Response();
try {
userRepository.save(user);
response.setResponse("200", user, "User Added Succussfully ");
} catch (Exception e) {
response.setResponse("500", e.getMessage(), "Error in Adding User ");
}

return response;
}

       @Override
public User findUserByUserName(String userName) {
User user = null;

try {
List<User> userList = userRepository.findUserByUserName(userName);
if (!userList.isEmpty()) {

user = userList.get(0);
}

} catch (Exception e) {

System.out.println("Invalid User and Password ");
}

return user;
}


@Override
public Response findEmployeeById(Integer employeeId) {

Response response = null;
Employee entity = null;

response = new Response();
try {
Optional<Employee> employee = employeeRepositry.findById(employeeId);
if (employee.isPresent()) {
entity = employee.get();
response.setResponse("200", entity, "Employee Find Succussfully ");

} else {
response.setResponse("200", entity, "Employee Not Present ");

}

} catch (Exception e) {
response.setResponse("500", null, "Error in Finding Employee ");
}

return response;
}

@Override
public Response addEmployee(EmployeeRequest employee) {
Response response = null;
Employee entity = new Employee();
entity.setEmployeeId(employee.getEmployeeId());
entity.setName(employee.getName());
entity.setCompany(employee.getCompany());

response = new Response();
try {
employeeRepositry.save(entity);
response.setResponse("200", entity, "Employee Added Succussfully ");
} catch (Exception e) {
response.setResponse("500", null, "Error in Adding Employee ");
}

return response;
}

@Override
public Response findProductById(Integer productId) {

Response response = null;
Product entity = null;

response = new Response();
try {
Optional<Product> product = productRepository.findById(productId);
if (product.isPresent()) {
entity = product.get();
response.setResponse("200", entity, "Product Find Succussfully ");

} else {
response.setResponse("200", entity, "Product Not Present ");

}

} catch (Exception e) {
response.setResponse("500", e.getMessage(), "Error in Finding Product ");
}

return response;
}

@Override
public Response addProduct(ProductRequest product) {

Response response = null;
Product entity = new Product();
entity.setProductId(product.getProductId());
entity.setProductName(product.getProductName());
entity.setPrice(product.getPrice());
entity.setCompany(product.getCompany());

response = new Response();
try {
productRepository.save(entity);
response.setResponse("200", entity, "Product Added Succussfully ");
} catch (Exception e) {
response.setResponse("500", e.getMessage(), "Error in Adding Product ");
}

return response;
}

}

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 https://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.4.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>in.jk</groupId>
<artifactId>springbootapplication</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springbootapplication</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--  Jackson Dependency for json conversion -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>     
</dependency>

<!-- Swagger Dependency .. -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

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

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

</project>

Spring Security Swagger Config 
SwaggerConfig.java

package in.jk.springbootapplication.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {                                    
    @Bean
    public Docket api() { 
        return new Docket(DocumentationType.SPRING_WEB)  
          .select()                                  
          .apis(RequestHandlerSelectors.any())              
          .paths(PathSelectors.any())                          
          .build();                                           
    }
}

SpringbootApplication.java

package in.jk.springbootapplication;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
System.out.println("Spring Boot Application started .....");
}

}


How to test Spring Security application 

1. Right click on SpringbootApplication and start application open new Browser type following URL 

http://localhost:8080/swagger-ui.html#










User Service URL is not protected with spring security so it does not require authentication .
Now add two User with using Swagger URL using add user Post URL

First User Details
   
 UserId = 1
 UserName = "jk123"
 Password = "jk123"
 Role     = "USER"


Second User Details 

   UserId = 1
   UserName = "john"
   Password = "john"
   Role     = "ADMIN"

After adding above two user open new Browser type following URL for user Service with userId 1

http://localhost:8080/adminapi/user/findUserById/1








2.Testing of Employee Service 

Employee Service is protected by Spring Security it require authentication for employee Service we have set following username ,password and role
 
username = john
password = john
role     = ADMIN

Open new Browser window and type following URL
http://localhost:8080/adminapi/employee/findEmployeeById/1

after entering above URL it open a login form like bellow









After the entering username and password click on login button it authenticate user and gives following response .











So So Until we didn't added any employee in our application so go to Swagger UI and add one employee with employeeId 1 and then add following URL

http://localhost:8080/adminapi/employee/findEmployeeById/1

above URL gives Response with Employee Id 1










Note :- IF you try access product service URL which protect by other user details then 
it gives following response with Product Service URL

http://localhost:8080/adminapi/product/findProductById/1












3.Testing of Product Service 

Product Service is protected by Spring Security it require authentication for Product Service we have set following username ,password and role
 
username = jk123
password = jk123
role     = USER

Open new Browser window and type following URL

http://localhost:8080/adminapi/product/findProductById/10
after entering above URL it open a login form like bellow







After the entering username and password click on login button it authenticate use and gives following response .




So until we didn't added any product in our application so go to Swagger UI and add one product with productId 1 and then add following URL

http://localhost:8080/adminapi/product/findProductById/1

above URL gives Response with productId 1






Note :- IF you try access employee service URL which employee by other user details then 
it gives following response with Employee Service URL

http://localhost:8080/adminapi/employee/findEmployeeById/1








 






This is  how we can implement Spring Security in Springboot application .

please comment if you find any mistake ....


  



  
















No comments:

Post a Comment