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. 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;
    }
  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

  1. Till now we were sending requests over HTTP. Now lets make HTTP secured using https by installing certificate in our project
  2. To generate certificate we use Key Store Explorer and will generate PKCS#12 Certificate

  3. Generate a public and private key and save the keystore
  4. The Keystore should be copied to the resources folder and the credentials of the keystore should be added to application.properties as below.

    application.properties

    # The format used for the keystore. It could be set to JKS in case it is a JKS file
    server.ssl.key-store-type=PKCS12
    # The path to the keystore containing the certificate
    server.ssl.key-store=classpath:TestCert
    # The password used to generate the certificate
    server.ssl.key-store-password=password
    
  5. Since the certificare generated(keystore file) cannot be cross checked with CA(Certificate Authority) it would display message like one below for the authenticy of certificate, whether it should be accpeted or not. How ever you can continue further by clicking on advanced

Note: While performing CRUD the addition of user is a JSON request which should be carried out only from postman. For this enable interceptor plugin in Chrome so that cookies set in chrome would be available in Postman.

Further Reads:
How SSL Works