Mocking Dependency Injection Objects
There may be times where the service would be injected into the component using constructor by DI. In such a case while creating a component instance for test class we could end up with a problem. To answer this we pass mock objects as alternate to constructors

add.employee.component.ts

export class EmployeeComponent implements OnInit {
 .
 .
  constructor(private objEmpserviceService : EmpserviceService) {
  }

  checkForAuthentication(): string {
    if(this.objEmpserviceService.authenticateEmp('Admin')){
      return "You are allowed to Edit";
    }
    return "You are Not allowed to Edit";
  }
 .
 .

Two Methods to Answer this Problem
Method 1 : If you are not going to call the mockedObject methods

To address this the injected object using dependency injection we use providers. Providers provide mockedobject to constructor instead of real object like below

add.employee.component.spec.ts

let mockEmpserviceService:EmpserviceService;

beforeEach(async(() => {
    mockEmpserviceService = jasmine.createSpyObj(['authenticateEmp', 'addEmployee', 'deleteEmployee']); 

    TestBed.configureTestingModule({
      .
      declarations: [ EmployeeComponent ],
      providers : [ 
                   {provide:EmpserviceService, useValue:mockEmpserviceService}
                  ]
      .
      .
    }).compileComponents();
  }));
  • Now in the above code we have mocked the service using mockEmpserviceService and providing a list of methods. But the returnType is still missing. So this could be used only to check if the actual method under test is called without using callThrough which in turn calls the method in mockedObject.
     .
     it('Check for Service Method', ()=>{
        spyOn(component, 'checkForAuthentication');
       
        let auth = component.checkForAuthentication();
        (or)
        const btnAddEmp = fixture.debugElement.query(By.css("#btnAddEmp"));
        btnAddEmp.triggerEventHandler('click', {});
        fixture.detectChanges();
         
        expect(auth).toHaveCalled();
     });
     .
    

    Method 2 : If you are going to call the mocked object methods then in such case the return type of the method should be defined.The same code can be done using actual EmpserviceService in provider and using spyOn inside its block to define the returnType of service method.

    let empService: EmpserviceService;
    
    beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [ EmployeeComponent],
          providers : [ EmpserviceService],
          imports: [HttpClientTestingModule]
        })
        .compileComponents();
    
        empService = Testbed.inject(EmpserviceService);
      }));
    
      fit('Check for Service Method', ()=>{  
        //Mocking authenticateEmp to return true as if it is called    
        spyOn(empService, 'authenticateEmp').withArgs('Admin').and.returnValue(true);      
        spyOn(component, 'checkForAuthentication').and.callThrough();
        let auth = component.checkForAuthentication();
        expect(auth).toBe('You are allowed to Edit');
      })
    

    In the above code, checkForAuthentication calls authenticateEmp in empService since it does callThrough.

    Note:Even now the control doesnt goes through empService. It would be called and returnvalue would be sent back.

  • Parts of OpenAPI 3.0 Specification
    Info and Components are important parts of API Definition

    1. Info – Provides Name, Description and Developers information of API
    2. Servers – Provides server information and various environment URL of API
    3. Security – Provides details about how API is secured, API keys, Authorization information
    4. Paths – Details about API Endpoints
    5. Tags and extenaldocs – Tags that could be grouped for API operations
    6. Components – Set of reusable objects which could be used during API definition

    Below is a simple class which uses stub for Testing

    1. Below is a simple business implementation class which EmployeeDetailsImpl which uses EmployeeService API methods
    2. Now to test EmployeeDetailsImpl we need to substitude the methods in service class with stub methods for which we create EmployeeServiceStub
    3. EmployeeServiceStub is a stub class which implements the EmployeeService and provide method definitions
    4. EmployeeDetailsImplTest is the test class for EmployeeDetailsImpl which performs the unittest for methods in EmployeeDetailsImpl

    EmployeeService.java

     
    import java.util.List;
    
    public interface EmployeeService {
        public List<String> GetEmployeeDetails();
    }
    

    EmployeeDetailsImpl.java

     
    import com.mugil.org.api.EmployeeService;
    
    import java.util.List;
    
    public class EmployeeDetailsImpl {
        EmployeeService employeeService;
    
        public EmployeeDetailsImpl(EmployeeService pEmployeeService){
            this.employeeService = pEmployeeService;
        }
    
        public List<String> getEmployeeList(){
            List<String> arrEmp = this.employeeService.GetEmployeeDetails();
            return arrEmp;
        }
    }
    

    EmployeeServiceStub.java

     
    import java.util.Arrays;
    import java.util.List;
    
    public class EmployeeServiceStub  implements  EmployeeService{
    
        @Override
        public List<String> GetEmployeeDetails() {
            return Arrays.asList("Mugil", "Mani", "Vinu");
        }
    }
    

    EmployeeDetailsImplTest.java

     
    import com.mugil.org.api.EmployeeServiceStub;
    import org.junit.Test;
    
    import static org.junit.Assert.assertEquals;
    
    public class EmployeeDetailsImplTest {
        @Test
        public void getEmployeeList_success(){
            EmployeeServiceStub employeeService = new  EmployeeServiceStub();
            EmployeeDetailsImpl employeeDetailsImpl = new EmployeeDetailsImpl(employeeService);
            assertEquals(3, employeeDetailsImpl.getEmployeeList().size());
        }
    }
    

    Dis-advantage of the above implementation

    1. In the above implementation when ever new method is added to the API interface, it should be added to the EmployeeServiceStub which implements EmployeeService
    2. EmployeeServiceStub is extra java file which should be taken care, incase there are multiple services used in class multiple service stub needs to be created

    Replacing stub with mocks
    In the below code, the same stub service(EmployeeServiceStub.java) is replaced with mock class and its methods are replaced using when and return.This prevents the need for another class

    EmployeeDetailsImplTest.java

     
    import static org.junit.Assert.assertEquals;
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.when;
    
    public class EmployeeDetailsImplTest {
        @Test
        public void getEmployeeList_success(){
         
            //Replacement code for Stub class
            EmployeeService employeeService = mock(EmployeeService.class);
            when(employeeService.GetEmployeeDetails()).thenReturn(Arrays.asList("Mugil", "Mani", "Vinu"));
    
            EmployeeDetailsImpl employeeDetailsImpl = new EmployeeDetailsImpl(employeeService);
            assertEquals(3, employeeDetailsImpl.getEmployeeList().size());
        }
    }
    

    In the below code we are going to mock the List Interface and override the method behaviour of List Methods
    Methods mocked

    1. get(index)
    2. size()
    3. exception

    ListTest.java

    package com.mugil.org;
    
    import org.junit.Before;
    import org.junit.Test;
    import java.util.List;
    import static org.junit.Assert.assertNotEquals;
    import static org.mockito.Matchers.anyInt;
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.when;
    import static org.junit.Assert.assertEquals;
    
    public class ListTest {
        List arrEmployee;
    
        @Before
        public void init(){
            arrEmployee = mock(List.class);
        }
    
        @Test
        public void ListMock_SizeMethod(){
            when(arrEmployee.size()).thenReturn(3);
            assertEquals(3, arrEmployee.size());
        }
    
        @Test
        public void ListMock_GetMethod(){
            when(arrEmployee.get(0)).thenReturn("Employee1");
            when(arrEmployee.get(1)).thenReturn("Employee2");
            when(arrEmployee.get(2)).thenReturn("Employee3");
    
            assertEquals("Employee2", arrEmployee.get(1));
            assertNotEquals(null, arrEmployee.get(2));
        }
    
        @Test(expected = RuntimeException.class)
        public void ListMock_ThrowException(){
            when(arrEmployee.get(anyInt())).thenThrow(new RuntimeException());
            arrEmployee.get(1);
        }
    }
    
    

    5 Core Concepts of Spring Security

    1. Authentication and Authorization
      – Authentication – Who are you – Answer by showing ID(Facebook, LinkedIn for ID which uniquely identifies you)
      – Authorization – What you want – State what you want

      Knowledge Based Authentication – Providing details you know about you to prove its you. Downside is details can be stolen.
      Possession Based Authentication – Key Cards for accessing Building Doors, Phone OTP. Authenticates by checking the user posses something which
      realuser should posses.

      Multi Factor Authentication – Enter password and enter OTP(KBA + PBA)

    2. Authorization – Checks whether the person is allowed to do something. For Authorization, Authentication is needed at first place.
    3. Principal
      – Person identified through process of Authentication
      – Person who has logged in. Currently logged in user (or) account.
      – App remembers the principal in context as currently loggedin user.
    4. Granted Authority
      – Authority includes whether the user is allowed to Read, Write, Update and Delete at permission level
    5. Role
      – Group of Authorities assigned together forms a role

    Formbased Authentication
    pom.xml

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

    Basic Auth
    null

    1. Client Sends a request without username and password and gets 401 Unauthorized as Response
    2. Now Client Sends a request with username and password with Base64 Encoding
    3. Server validates whether user exists in DB
    4. Server replies with 200 Ok if user authentication is successful
    5. Basic ENCODED-BASE64-USERIDPASSWORD is the one sent in header to server from client
    6. In postman basic auth can be done by adding Authorization and base64 encoded user and password to header
      Header : Authorization
      Value : Basic base64('YourOrgName:YourAPIKEY');
      
    7. Base64 encoded text can be got from JS Console in browser as below

      "username:password!" // Here I used basic Auth string format
      
      // Encode the plain string to base64
      btoa("username:password!"); // output: "dXNlcm5hbWU6cGFzc3dvcmQh"
      
      
      // Decode the base64 to plain string
      atob("dXNlcm5hbWU6cGFzc3dvcmQh"); // output: "username:password!"
      
    8. Using Authorization Tab in post man does the same thing of adding base64 encoded UserName and Password to Header prepending Basic

    The Difference between FormAuth and BasicAuth is in BasicAuth UserName and Password would be sent everytime when making a request to the server in the header as base64 encoded character.

    Form-based authentication
    Form-based authentication is not formalized by any RFC.They don’t use the formal HTTP authentication techniques.They use the standard HTML form fields to pass the username and password values to the server.The server validates the credentials and then creates a “session” that is tied to a unique key that is passed between the client and server on each http put and get request.When the user clicks “log off” or the server logs the user off (for example after certain idle time), the server will invalidate the session key, which makes any subsequent communication between the client and server require re-validation

    null

    Basic Auth
    null

    Basic Auth with Authorization in Headers as seen in DevTool
    null

    Creating the below class in Spring Boot project would enable the Basic auth(httpAuth) instead of default formbased auth which we get after adding spring security starter dependency to pom.xml

    ApplicationSecurityConfig.java
    Using Custom Username and Password for Inmemory Authentication

    @Configuration
    @EnableWebSecurity
    public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity.authorizeRequests()
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
        }
    }
    

    ApplicationSecurityConfig.java

    @Configuration
    @EnableWebSecurity
    public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity.authorizeRequests()
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
        }
    }
    

    Whitelisting some URLs(index, js and CSS files)

    @Configuration
    @EnableWebSecurity
    public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity.authorizeRequests()
                        //Whitelisting URLS
                        .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
        }
    }
    

    Authentication with password with no encryption

    @Override
    @Bean
     protected UserDetailsService userDetailsService() {
           UserDetails mugilUsrBuilder = User.builder()
                  .username("Mugil")
                  .password("{noop}password")
                  .roles("ADMIN")
                  .build();
    
          return new InMemoryUserDetailsManager(mugilUsrBuilder);
    }
    

    If {noop} is not used in password Spring security would throw an error asking to encode the password with password encoder as below.
    java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”

    Using Password Simple Encoder
    PasswordConfig.java

    @Configuration
    public class PasswordConfig {
        @Bean
        public PasswordEncoder passwordEncoder()
        {
            return new BCryptPasswordEncoder(10);
        }
    }
    

    ApplicationSecurityConfig.java

    1. Inject the passwordEncoder from PasswordConfig class to ApplicationSecurityConfig
    2. Encode the password using instance of injected encoder in ApplicationSecurityConfig
     @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        @Bean
        protected UserDetailsService userDetailsService() {
            UserDetails mugilUsrBuilder = User.builder()
                    .username("Mugil")
                    .password(this.passwordEncoder.encode("password"))
                    .roles("ADMIN")
                    .build();
    
            return new InMemoryUserDetailsManager(mugilUsrBuilder);
        }
    

    Allowing Access to API based on Role – Authorization
    ApplicationSecurityConfig.java

    @Configuration
    @EnableWebSecurity
    public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    .
    .
     @Override
        @Bean
        //Authentication
        protected UserDetailsService userDetailsService() {
            UserDetails adminUsrBuilder = User.builder()
                    .username("admin")
                    .password(this.passwordEncoder.encode("password"))
                    .roles("ADMIN")
                    .build();
    
            UserDetails regularUsrBuilder = User.builder()
                    .username("user")
                    .password(this.passwordEncoder.encode("password"))
                    .roles("USER")
                    .build();
    
            return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);  
        }
    
        @Override
        //Authorization
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity.authorizeRequests()
                         //Whitelisting URLS
                        .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                        .antMatchers("/api/**").hasRole("ADMIN")
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
        }
    .
    .
    
    1. In the above code we have added two roles – ADMIN and USER
    2. Both were authenticated to access the application.But to access the API the role should be ADMIN
        antMatchers("/api/**").hasRole("ADMIN")
      
    3. If the user with Role USER try to access API then it would end up in 403 – Forbidden Error

    Access Allowed

    Forbidden Access

    Allowing Access based on 2 Different Role
    ApplicationSecurityConfig.java

    @Configuration
    @EnableWebSecurity
    public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    .
    .
    
        @Override
        @Bean
        protected UserDetailsService userDetailsService() {
            UserDetails adminUsrBuilder = User.builder()
                    .username("admin")
                    .password(this.passwordEncoder.encode("password"))
                    .roles("ADMIN")
                    .build();
    
            UserDetails regularUsrBuilder = User.builder()
                    .username("user")
                    .password(this.passwordEncoder.encode("password"))
                    .roles("USER")
                    .build();
    
            return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);
        }
    
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity.csrf().disable()
                         .authorizeRequests()
                         //Whitelisting URLS
                        .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                        .antMatchers(HttpMethod.GET,"/api/**").permitAll()
                        .antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
                        .antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
                        .antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
        }
    }
    
    1. In the above piece of code we have defined two roles ADMIN and USER
    2. Those with USER role can access the API with HTTP Get Method. That means both ADMIN and USER role could access all the API using GET method
      .
      .antMatchers(HttpMethod.GET,"/api/**").permitAll()
      .
      
    3. Those with ADMIN role can access the API with HTTP POST, DELETE and PUT Method which corresponds to Create, Delete and Update as per Open API Specifiaction.

      .
      .antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
      .antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
      .antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
      .
      
    4. The above could be cross checked by changing postman call with HttpMethods and Credentials

    StudentsService.java

    @RestController
    @RequestMapping("/api/v1/students")
    public class StudentsService {
        @Autowired
        StudentRepo studentRepo;
    
        @GetMapping(path="{studentId}")
        public Student getStudentById(@PathVariable("studentId") String studentId){
            return studentRepo.getStudentById(studentId);
        }
    
        @GetMapping
        public List<Student> getStudentList(){
            return studentRepo.getStudentsList();
        }
    
        @PutMapping
        public String updateStudent(@RequestBody Student student){
            return studentRepo.updateStudent(student);
        }
    
        @PostMapping
        public String addStudent(@RequestBody Student student){
            if(studentRepo.addStudents(student))
                return  "Student with Id " + student.getStudentId() + " added successfully";
            else
                return  "Error:Unable to create Student";
        }
    
        @DeleteMapping(path="{studentId}")
        public String deleteStudent(@PathVariable("studentId") String studentId){
            studentRepo.deleteStudent(studentId);
            return "Student Deleted Successfully";
        }
    }
    

    Allowing Access based on 2 Different Authority(or)Permission

    1. In the below code instead of using ROLES to authorize users to do something we use AUTHORITIES to allow user
    2. There are two ways to do this. One is by using hasAuthority in configure(HttpSecurity httpSecurity) method as below
    3. .
      @EnableGlobalMethodSecurity(prePostEnabled = true)
      .
      .antMatchers(HttpMethod.POST,"/api/v1/students/").hasAuthority("WRITE")
      .antMatchers(HttpMethod.DELETE,"/api/v1/students/**").hasAuthority("WRITE")
      .antMatchers(HttpMethod.PUT,"/api/v1/students/").hasAuthority("WRITE")
      .
      
    4. Other is by using @preauthorize annotation to decide the methods
      which could be allowed access to

      .
      .
      @PreAuthorize("hasAuthority('READ')")
      public List getStudentList(){
      .
      
      @PreAuthorize("hasAuthority('WRITE')")
      public String updateStudent(@RequestBody Student student){
      .
      .
      

    ApplicationSecurityConfig.java
    Using hasAuthority

    @Configuration
    @EnableWebSecurity
    public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        @Bean
        protected UserDetailsService userDetailsService() {
            GrantedAuthority[] arrGrantedAuthAdmin = {new SimpleGrantedAuthority("READ"), new SimpleGrantedAuthority("WRITE")};
            GrantedAuthority[] arrGrantedAuthUser = {new SimpleGrantedAuthority("READ")};
    
            UserDetails adminUsrBuilder = User.builder()
                    .username("admin")
                    .password(this.passwordEncoder.encode("password"))
                    .authorities("READ", "WRITE")
                    .build();
    
            UserDetails regularUsrBuilder = User.builder()
                    .username("user")
                    .password(this.passwordEncoder.encode("password"))
                    .authorities("READ")
                    .build();
    
            return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);
        }
    
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity.csrf().disable()
                         .authorizeRequests()
                         .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                        .antMatchers(HttpMethod.POST,"/api/v1/students/").hasAuthority("WRITE")
                        .antMatchers(HttpMethod.DELETE,"/api/v1/students/**").hasAuthority("WRITE")
                        .antMatchers(HttpMethod.PUT,"/api/v1/students/").hasAuthority("WRITE")
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
        }
    }
    

    ApplicationSecurityConfig.java
    Using @PreAuthorize and EnableGlobalMethodSecurity

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        public ApplicationSecurityConfig(PasswordEncoder passwordEncoder) {
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        @Bean
        protected UserDetailsService userDetailsService() {
            GrantedAuthority[] arrGrantedAuthAdmin = {new SimpleGrantedAuthority("READ"), new SimpleGrantedAuthority("WRITE")};
            GrantedAuthority[] arrGrantedAuthUser = {new SimpleGrantedAuthority("READ")};
    
            UserDetails adminUsrBuilder = User.builder()
                    .username("admin")
                    .password(this.passwordEncoder.encode("password"))
                    .authorities("READ", "WRITE")
                    .build();
    
            UserDetails regularUsrBuilder = User.builder()
                    .username("user")
                    .password(this.passwordEncoder.encode("password"))
                    .authorities("READ")
                    .build();
    
            return new InMemoryUserDetailsManager(adminUsrBuilder, regularUsrBuilder);
        }
    
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity.csrf().disable()
                         .authorizeRequests()
                        .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
        }
    }
    

    StudentsService.java

    @RestController
    @RequestMapping("/api/v1/students")
    public class StudentsService {
        @Autowired
        StudentRepo studentRepo;
    
        @GetMapping(path="{studentId}")
        public Student getStudentById(@PathVariable("studentId") String studentId){
            return studentRepo.getStudentById(studentId);
        }
    
        @GetMapping
        @PreAuthorize("hasAuthority('READ')")
        public List<Student> getStudentList(){
            return studentRepo.getStudentsList();
        }
    
        @PutMapping
        @PreAuthorize("hasAuthority('WRITE')")
        public String updateStudent(@RequestBody Student student){
            return studentRepo.updateStudent(student);
        }
    
        @PostMapping
        @PreAuthorize("hasAuthority('WRITE')")
        public String addStudent(@RequestBody Student student){
            if(studentRepo.addStudents(student))
                return  "Student with Id " + student.getStudentId() + " added successfully";
            else
                return  "Error:Unable to create Student";
        }
    
        @DeleteMapping(path="{studentId}")
        @PreAuthorize("hasAuthority('WRITE')")
        public String deleteStudent(@PathVariable("studentId") String studentId){
            studentRepo.deleteStudent(studentId);
            return "Student Deleted Successfully";
        }
    }
    
    1. CSRF Refers to Cross Site Request Forgery. More on CSRF Here
    2. When the Client makes the first GET request to the Server, Server generates CSRF token and sends back to Client
    3. On the Subsequent PUT,POST and DELETE request from the Client this token would be used for Authentication
    4. In Postman for the Same reason GET request would work irrespect we disable the CSRF in protected void configure(HttpSecurity httpSecurity) method. But for the PUT,POST and DELETE we should disable the CSRF as below otherwise the server would expect CSRF token when the request to server is PUT,POST and DELETE.
      @Override
      protected void configure(HttpSecurity httpSecurity) throws Exception
      {
      
      httpSecurity.csrf().disable()
                  .authorizeRequests()
                  .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                  .
                  . 
      

    Using CSRF Token for Authorization

    1. We have set of API’s exposed to the users whose role is ADMIN
    2. The way CSRF works is first Token would be generated once the User Logins using UserID and Password. The Way it works in REST API is first time
      when you use GET method you should use Basic Auth in Authorization to Generate XSRF-TOKEN which would be set in Cookie along with JSessionID and also available in Response Headers
    3. By default CSRF is not applicable for GET unless it is pointed to resource API. Simple reason behind that is, when the user types URL of login thats the first page which take login. If you try to access http://localhost:8080/ in postman it works fine without CSRF but when you access http://localhost:8080/api/v1/students/ which again uses GET method but points to resource API then it asks for UserId and Password due to basic Auth
    4. Subsequent POST, PUT and DELETE request could be done by attaching X-XSRF-TOKEN to header. The Cookie which has the JSESSIONID and XSRF-TOKEN should not be deleted in Postman
    5. Though CSRF is enabled explicity in my Code it didnt worked until i added following lines in ApplicationSecurityConfig.java
      .
      .
              httpSecurity
                           .csrf()
                           .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
      .
      

    Accessing resource using GET (No Authentication or Authorization)

    Accessing resource using GET (Authentication needed for API Resource)

    Accessing resource using POST (X-XSRF-TOKEN) in request header

    XSRF-TOKEN received after GET with credentials and X-XSRF-TOKEN in haeder for sunsequent POST,PUT and DELETE calls

    ApplicationSecurityConfig.java
    Using CSRF Token for Authorization

    @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception{
            httpSecurity
                         .csrf()
                         .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                         .and()
                        .authorizeRequests()
                        .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                        .antMatchers("/api/**").hasRole("ADMIN")
                        .anyRequest()
                        .authenticated()
                        .and()
                        .httpBasic();
    }
    

    FAQ
    There are N number of possibilities. Try out in Postman.

    1. How GET method to API resource Works with out CSRF token?
      It works based on JSESSIONID. When the JSESSIONID in cookie is deleted then again it asks for Login and password
    2. What happens when I delete XSRF token in Cookie?
      New Token would be generated based on JSESSIONID in cookie
    3. What happens when I delete XSRF token and in Cookie and try POST, DELETE and PUT over API?
      New JSESSIONID would be generated and placed in cookie. For this X-XSRF-TOKEN should be passed in header.
    1. What is the difference between FormBased and BasicAuth?
      The Difference between FormAuth and BasicAuth is in BasicAuth UserName and Password would be sent everytime when making a request to the server in the header as base64 encoded character. BasicAuth happens at http Protocol level where as Formbased Auth happens at Framework level.
    2. What is the difference between JKS and PKCS12?
      JKS is the most common if you stay within the Java world. PKCS#12 isn’t Java-specific, it’s particularly convenient to use certificates (with private keys) backed up from a browser or coming from OpenSSL-based tools.JKS, Java Key Store. You can find this file at sun.security.provider.JavaKeyStore. This keystore is Java specific, it usually has an extension of jks. This type of keystore can contain private keys and certificates, but it cannot be used to store secret keys. Since it’s a Java specific keystore, so it cannot be used in other programming languages.PKCS12, this is a standard keystore type which can be used in Java and other languages. You can find this keystore implementation at sun.security.pkcs12.PKCS12KeyStore. It usually has an extension of p12 or pfx. You can store private keys, secret keys and certificates on this type.

    3. How safe is Userid and Password in Basic Auth?
      UserId and Password in BasicAuth would transferred using base64 encoded UserName and Password attached to Header prepending Basic

      Authorization : Basic base64(UserId:Password)
      
    4. How POST, PUT and DELETE works in Basic Auth?
      In Basic Auth with respect to Spring Security CSRF Token would be used incase CSRF is enabled. If CSRF is disabled Authorization in header would be used
      for access. csrf().disable() should be always used incase you are using other than GET method.
    5. What is JSESSIONID?
      JSESSIONID which is generated and Stored as Cookie in Postman helps the spring boot app to recognizane whether the User is Authenticated or not.
    6. How Spring Security handles CSRF in client Side when enabled?
      Spring Security generates JSESSIONID and XSRF-TOKEN. Both were Set in Cookie. On Subsequent request X-XSRF-TOKEN should be sent in Header for Authorization.
    7. What happens when I delete XSRF token in Cookie?
      New Token would be generated based on JSESSIONID in cookie
    8. What happens when I delete XSRF token and in Cookie and try POST, DELETE and PUT over API?
      New JSESSIONID would be generated and placed in cookie. For this X-XSRF-TOKEN should be passed in header.
    9. Where is JSESSION stored?
      JSESSION is session ID which is stored in Inmemory Database in Server and in a Cookie in Client Side.
    10. What is the default Expiration time of JSESSION Cookie?
      30 Minutes
    11. Spring Security Arcitecture?
    12. Encoding vs Encrytion vs Hashing?
      Encoding refers to any transformation of a given input. For example, if we have a function x that reverses a string, function x -> y applied to ABCD produces DCBA.

      x -> y
      

      Encryption is a particular type of encoding where, to obtain the output, you provide both the input value and a key. The key makes it possible for choosing afterward who should be able to reverse the function (obtain the input from the output). The simplest form of representing encryption as a function looks like this:

      (x, k) -> y
      

      where x is the input, k is the key, and y is the result of the encryption. This way, an individual knows the key can use a known function to obtain the input from the output (y, k) -> x. We call this reverse function decryption. If the key used for encryption is the same as the one used for decryption, we usually call it a symmetric key.If we have two different keys for encryption ((x, k1) -> y) and decryption ((y,k2) -> x), then we say that the encryption is done with asymmetric keys. Then (k1,k2) is called a key pair. The key used for encryption, k1, is also referred to as the public key, while k2 is known as the private one. This way, only the owner of the private key can decrypt the data.

      Hashing is a particular type of encoding, except the function is only one way. That is,from an output y of the hashing function, you cannot get back the input x. However,there should always be a way to check if an output y corresponds to an input x, so we can understand the hashing as a pair of functions for encoding and matching. If hashing is x -> y, then we should also have a matching function (x,y) ->boolean.

      (x,y) ->boolean
      

      Sometimes the hashing function could also use a random value added to the input:
      (x, k) -> y. We refer to this value as salt. The salt makes the function stronger, enforcing the difficulty of applying a reverse function to obtain the input from the
      result.

    13. Two ways of Configuring Spring Security?
      Method 1: Adding userservice and passwordEncoder in configure method(Not Recommended)

      @Configuration
      public class ProjectConfig extends WebSecurityConfigurerAdapter {
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.inMemoryAuthentication()
                      .withUser("john")
                      .password("12345")
                      .authorities("read")
                      .and()
                      .passwordEncoder(NoOpPasswordEncoder.getInstance());
          }
      }
      

      Method 2: Using Seperate Configuration class for Bean and injecting beans(Recommended Method)
      PasswordConfig.java

      @Configuration
      public class PasswordConfig {
          @Bean
          public PasswordEncoder passwordEncoder()
          {
              return new BCryptPasswordEncoder(10);
          }
      }
      

      ApplicationSecurityConfig.java

      @Configuration
      @EnableWebSecurity
      public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
         @Autowired
         private PasswordEncoder passwordEncoder; 
       
          @Override
          @Bean
          protected UserDetailsService userDetailsService() {
      	   UserDetails mugilUsrBuilder = User.builder()
      			   .username("Mugil")
      			   .password(this.passwordEncoder.encode("password"))
      			   .roles("ADMIN")
      			   .build();
       
      	   return new InMemoryUserDetailsManager(mugilUsrBuilder);
          }
      
       
          @Override
          protected void configure(HttpSecurity httpSecurity) throws Exception{
              httpSecurity.csrf().disable()
                           .authorizeRequests()
                           //Whitelisting URLS
                          .antMatchers("/", "index", "/css/*", "/js/*").permitAll()
                          .antMatchers(HttpMethod.GET,"/api/**").permitAll()
                          .antMatchers(HttpMethod.DELETE,"/api/**").hasRole("ADMIN")
                          .antMatchers(HttpMethod.PUT,"/api/**").hasRole("ADMIN")
                          .antMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN")
                          .anyRequest()
                          .authenticated()
                          .and()
                          .httpBasic();
          }
      }
      
    14. What are the interface methods available in Spring Security?
      UserDetailsManager
      public interface UserDetailsManager extends UserDetailsService {
      void createUser(UserDetails user);
      void updateUser(UserDetails user);
      void deleteUser(String username);
      void changePassword(String oldPassword, String newPassword);
      boolean userExists(String username);
      }
      UserDetailsService
      public interface UserDetailsService {
      UserDetails loadUserByUsername(String username)
      throws UsernameNotFoundException;
      }
      UserDetails
      public interface UserDetails extends Serializable {
      String getUsername();
      String getPassword();
      Collection<? extends GrantedAuthority>
      ➥getAuthorities();
      boolean isAccountNonExpired();
      boolean isAccountNonLocked();
      boolean isCredentialsNonExpired();
      boolean isEnabled();
      }
      AuthenticationProvider
      public interface AuthenticationProvider {
      Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
      boolean supports(Class<?> authentication);
      }
      Authentication
      public interface Authentication extends Principal, Serializable {
      Collection<? extends GrantedAuthority> getAuthorities();
      Object getCredentials();
      Object getDetails();
      Object getPrincipal();
      boolean isAuthenticated();
      void setAuthenticated(boolean isAuthenticated)
      throws IllegalArgumentException;
      }


    Note:The example is for CSRF disabled

    1. From the above diagram you can see JSESSIONID and HttpStatus.OK is send is response once the credentials are authenticated
    2. JSESSIONID would be used for Subsequent request
    3. In the below code we define URL for login and logout.
      .formLogin()
      .loginPage("/login").permitAll().usernameParameter("username").passwordParameter("password")
      .defaultSuccessUrl("/test", true)
      .and()
      .rememberMe()
      .and()
      .logout().logoutUrl("/logout").clearAuthentication(true).invalidateHttpSession(true).deleteCookies("JSESSIONID", "remember-me")
      .logoutSuccessUrl("/login")			
      
    4. usernameParameter and passwordParameter is the name of the input form element as given in html
    5. defaultSuccessUrl tells the default page after authentication
    6. rememberMe allows the User to remember the session in server. The default JSESSIONID time is 30 minutes of inactivity. remember-me session would be active for 2 weeks and allows user to access page for 2 weeks
    7. logout is similar to login with following
      .logout().logoutUrl("/logout")
      .clearAuthentication(true)
      .invalidateHttpSession(true)
      .deleteCookies("JSESSIONID", "remember-me")
      .logoutSuccessUrl("/login")			
      

    JSESSIONID and remember-me as seen in cookie in response after login button clicked

    JSESSIONID and remember-me cookie deleted in response after logout button clicked

    login.html

    <body>
    <div class="container">
        <form class="form-signin" method="post" action="/login">
            <table cellpadding="3" cellspacing="3" border="1px solid black" style="border-collapse: collapse">
                <tr>
                    <td><label for="username" class="sr-only">Username</label></td>
                    <td><input type="text" id="username" name="username" class="form-control" placeholder="Username" required=""
                               autofocus=""></td>
                </tr>
                <tr>
                    <td><label for="password" class="sr-only">Password</label></td>
                    <td><input type="password" id="password" name="password" class="form-control" placeholder="Password"
                               required=""></td>
                </tr>
                <tr>
                    <td><label for="remember-me" class="sr-only">Remember Me?</label></td>
                    <td><input type="checkbox" id="remember-me" name="remember-me" class="form-control"></td>
                </tr>
                <tr>
                    <td colspan="2" align="center"><button class="btn btn-lg btn-primary btn-block" type="submit">Login</button></td>
                </tr>
            </table>
        </form>
    </div>
    </body>
    

    test.html

    You have been Logged In
    
    <form class="form-signin" method="get" action="/logout">
        <button class="btn btn-lg btn-primary btn-block" type="submit">Logout</button>
    </form>
    
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
    	httpSecurity
    				.csrf().disable()
    				.authorizeRequests()
    				.antMatchers("/", "index", "/css/*", "/js/*").permitAll()
    				.antMatchers("/api/**").hasRole("ADMIN")
    				.anyRequest()
    				.authenticated()
    				.and()
    				.formLogin()
    				.loginPage("/login").permitAll().usernameParameter("username").passwordParameter("password")
    				.defaultSuccessUrl("/test", true)
    				.and()
    				.rememberMe()
    				.and()
    				.logout().logoutUrl("/logout").clearAuthentication(true).invalidateHttpSession(true).deleteCookies("JSESSIONID", "remember-me")
    				.logoutSuccessUrl("/login");
    }
    
    1. Advantage of JSON token is it could be used form multiple platforms like PC, Mobile for accessing secured pages
    2. User enters user credentials(Username and Password) and gets authenticated.
    3. User Credentails are validated and token is generated
    4. On Subsequent request from APIs tokens are used in header for authorization
    5. Tokens are generated and verified by filters.There would be line of filters on way from request to API
    6. JwtUsernameAndPasswordAuthenticationFilter generates the Token after authentication
    7. JwtTokenVerifierFilter authorizes the user in each and every request by interpreting the token in the request header

    JwtTokenVerifierFilter.java

    public class JwtTokenVerifierFilter extends OncePerRequestFilter {
        private final SecretKey secretKey;
        private final JwtConfig jwtConfig;
    
        public JwtTokenVerifierFilter(SecretKey secretKey,
                                      JwtConfig jwtConfig) {
            this.secretKey = secretKey;
            this.jwtConfig = jwtConfig;
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                        HttpServletResponse httpServletResponse,
                                        FilterChain filterChain) throws ServletException, IOException {
    
            String authorizationHeader = httpServletRequest.getHeader(jwtConfig.getAuthorizationHeader());
    
    
            if(Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith("Bearer ")){
                filterChain.doFilter(httpServletRequest, httpServletResponse);
                return;
            }
    
            String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");
    
            try{
                Jws<Claims> claimsJws = Jwts.parser()
                        .setSigningKey(secretKey)
                        .parseClaimsJws(token);
    
                Claims body     = claimsJws.getBody();
                String username = body.getSubject();
    
    
                List<Map<String, String>> authorities = (List<Map<String, String>>)body.get("authorities");
                Set<SimpleGrantedAuthority> simpleGrantedAuth = authorities.stream()
                        .map(m -> new SimpleGrantedAuthority(m.get("authority")))
                        .collect(Collectors.toSet());
    
                Authentication authentication = new UsernamePasswordAuthenticationToken(username,  null, simpleGrantedAuth);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }catch (JwtException e){
                throw new IllegalStateException(String.format("Token %s cannot be Trusted", token));
            }
    
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        }
    }
    

    JwtUsernameAndPasswordAuthenticationFilter.java

    public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        private final AuthenticationManager authenticationManager;
        private final JwtConfig jwtConfig;
        private final SecretKey secretKey;
    
    
        public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager,
                                                          JwtConfig jwtConfig,
                                                          SecretKey secretKey) {
            this.authenticationManager = authenticationManager;
            this.jwtConfig = jwtConfig;
            this.secretKey = secretKey;
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request,
                                                    HttpServletResponse response) throws AuthenticationException {
    
            try {
                UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
                        .readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);
    
                Authentication authentication = new UsernamePasswordAuthenticationToken(
                        authenticationRequest.getUsername(),
                        authenticationRequest.getPassword()
                );
    
                Authentication authenticate = authenticationManager.authenticate(authentication);
                return authenticate;
    
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        protected void successfulAuthentication(HttpServletRequest request,
                                                HttpServletResponse response,
                                                FilterChain chain,
                                                Authentication authResult) throws IOException, ServletException {
    
            String token = Jwts.builder()
                    .setSubject(authResult.getName())
                    .claim("authorities", authResult.getAuthorities())
                    .setIssuedAt(new Date())
                    .setExpiration(java.sql.Date.valueOf(LocalDate.now().plusWeeks(jwtConfig.getTokenExpirationAfterDays())))
                    .signWith(secretKey)
                    .compact();
    
            response.addHeader(jwtConfig.getAuthorizationHeader(), jwtConfig.getTokenPrefix() + token);
        }
    }
    
    

    Download Code