Spring Boot security with JWT

Project Structure:

SecurityConfig.java


package com.deepsingh44.springjwt_tutorial.config;
import com.deepsingh44.springjwt_tutorial.filter.JwtFilter;
import com.deepsingh44.springjwt_tutorial.service.
CustomUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.
AuthenticationManager;
import org.springframework.security.config.annotation.
authentication.builders.AuthenticationManagerBuilder;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.
BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.
UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailService customUserDetailService;

    @Autowired
    private JwtFilter jwtFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth)
    throws Exception {
        auth.userDetailsService(customUserDetailService).
        passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() 
    throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

                http.
                csrf().
                disable()
                .authorizeRequests().
                antMatchers("/authenticate", "/register").
                permitAll().
                anyRequest().
                authenticated().
                and().
                sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //Add filter to validate the tokens with every request.
        http.addFilterBefore(jwtFilter, 
        UsernamePasswordAuthenticationFilter.class);
    }
}
 

AuthenticationController.java


package com.deepsingh44.springjwt_tutorial.controller;
import com.deepsingh44.springjwt_tutorial.model.JwtRequest;
import com.deepsingh44.springjwt_tutorial.model.JwtResponse;
import com.deepsingh44.springjwt_tutorial.model.User;
import com.deepsingh44.springjwt_tutorial.service.
CustomUserDetailService;
import com.deepsingh44.springjwt_tutorial.utility.JwtUtility;
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.
UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AuthenticationController {

    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtUtility jwtUtility;
    
    @Autowired
    private CustomUserDetailService customUserDetailService;

    @PostMapping("/authenticate")
    public ResponseEntity<?> authenticate(@RequestBody JwtRequest jwtRequest)
            throws Exception {
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(jwtRequest.
                    getUsername(),
                       jwtRequest.getPassword()));
        } catch (DisabledException disabledException) {
            throw new Exception("USER_IS_DISABLE", disabledException);
        } catch (BadCredentialsException badCredentialsException) {
            throw new Exception("INVALID_USER_CREDENTIALS", 
            badCredentialsException);
        }
        final UserDetails userDetails = customUserDetailService.
                loadUserByUsername(jwtRequest.getUsername());
        final String token = jwtUtility.generateToken(userDetails);
        return ResponseEntity.ok(new JwtResponse(token));
    }

    @PostMapping("/register")
    public ResponseEntity<?> saveUser(@RequestBody User user)
            throws Exception {
        return ResponseEntity.ok(customUserDetailService.save(user));
    }
    
    @GetMapping("/home")
    public String home(){
        return "Welcome to home page";
    }

} 
 

JwtFilter.java


package com.deepsingh44.springjwt_tutorial.filter;
import com.deepsingh44.springjwt_tutorial.service.CustomUserDetailService;
import com.deepsingh44.springjwt_tutorial.utility.JwtUtility;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.
UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.
WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtFilter extends OncePerRequestFilter {
    @Autowired
    private CustomUserDetailService jwtUserDetailsService;

    @Autowired
    private JwtUtility jwtTokenUtility;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
    HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        final String requestTokenHeader = request.getHeader("Authorization");

        String username = null;
        String jwtToken = null;

        if (requestTokenHeader != null && requestTokenHeader.
        startsWith("Bearer ")) {
            jwtToken = requestTokenHeader.substring(7);
            try {
                username = jwtTokenUtility.getUsernameFromToken(jwtToken);
            } catch (IllegalArgumentException e) {
                System.out.println("Unable to get JWT Token");
            } catch (ExpiredJwtException e) {
                System.out.println("JWT Token has expired");
            }
        } else {
          System.out.println("JWT Token does not begin with Bearer String");
        }

 
        if (username != null && SecurityContextHolder.
        getContext().getAuthentication() == null) {

            UserDetails userDetails = this.
            jwtUserDetailsService.loadUserByUsername(username);

            if (jwtTokenUtility.validateToken(jwtToken, userDetails)) {

                UsernamePasswordAuthenticationToken 
                usernamePasswordAuthenticationToken 
                = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().
                        buildDetails(request));
                
                SecurityContextHolder.getContext().
                setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        
        chain.doFilter(request, response);
    }
}
  

JwtRequest.java


package com.deepsingh44.springjwt_tutorial.model;

public class JwtRequest {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
  

JwtResponse.java


package com.deepsingh44.springjwt_tutorial.model;

public class JwtResponse {
    private final String jwttoken;

    public JwtResponse(String jwttoken) {
        this.jwttoken = jwttoken;
    }

    public String getJwttoken() {
        return jwttoken;
    }

}
  

User.java


package com.deepsingh44.springjwt_tutorial.model;
import javax.persistence.*;

@Entity
@Table(name = "users")

public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String username;
    private String password;

    public long getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
  

UserRepository.java


package com.deepsingh44.springjwt_tutorial.repository;
import com.deepsingh44.springjwt_tutorial.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User,Long> {
    User findByUsername(String username);
}
  

CustomUserDetailService.java


package com.deepsingh44.springjwt_tutorial.service;
import com.deepsingh44.springjwt_tutorial.model.User;
import com.deepsingh44.springjwt_tutorial.repository.
UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.
UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
@Service
public class CustomUserDetailService implements UserDetailsService {
    @Autowired
    private UserRepository userDao;

    @Autowired
    private PasswordEncoder bcryptEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) 
    throws UsernameNotFoundException {
        User user = userDao.findByUsername(username);
        if (user == null) {
            throw new 
            UsernameNotFoundException("User not found : " + username);
        }
        return new 
        org.springframework.security.core.userdetails.User(user.
        getUsername(),user.getPassword(),
                new ArrayList());
    }

    public User save(User user) {
        User newUser = new User();
        newUser.setUsername(user.getUsername());
        newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
        return userDao.save(newUser);
    }
}
  

JwtUtility.java


package com.deepsingh44.springjwt_tutorial.utility;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class JwtUtility implements Serializable {
    public static final long JWT_TOKEN_VALIDITY = 2*60*60;

    @Value("${jwt.secret}")
    private String secret;

    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public Date getIssuedAtDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getIssuedAt);
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public <T> T getClaimFromToken(String token, 
    Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser().
        setSigningKey(secret).
        parseClaimsJws(token).getBody();
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.
        before(new Date());
    }

    private Boolean ignoreTokenExpiration(String token) {
        return false;
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap();
        return doGenerateToken(claims, 
        userDetails.getUsername());
    }

    private String doGenerateToken(Map<String, Object>
    claims, String subject) {

        return Jwts.builder().
        setClaims(claims).setSubject(subject).
        setIssuedAt(new Date(System.currentTimeMillis()))
           .setExpiration(new Date(System.currentTimeMillis() + 
           JWT_TOKEN_VALIDITY*1000)).
           signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    public Boolean canTokenBeRefreshed(String token) {
        return (!isTokenExpired(token) || ignoreTokenExpiration(token));
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) &&
        !isTokenExpired(token));
    }
}
  

SpringJwtTutorialApplication.java


package com.deepsingh44.springjwt_tutorial;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.
SpringBootApplication;

@SpringBootApplication
public class SpringJwtTutorialApplication {

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

}
  

application.properties


jwt.secret=thisisasecretkey

spring.datasource.url =jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username =root
spring.datasource.password =root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.jpa.properties.hibernate.dialect =org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto =update
  

Output Screens:





No comments: