Stateless Authentication using JWT to secure a Spring Boot REST API

How we can implement step by step a stateless authentication using JWT to secure a REST API endpoints built with the help of Spring Boot and Spring Security.

In this tutorial, we will see how we can implement a stateless authentication using JWT to secure a REST API endpoints built with the help of Spring Boot and Spring Security. Are you ready to get started ?

Session Cookie Based Authentication:

When we talk about The session-based approach, it means that it's the server that is responsible for managing the authentication state of the user, and we have noticed that today, the most common usage of authentication is the session-based approach. In this approcah, a session id is generated by the server and stored in a cookie within the JSESSIONID paramter. This implies that the server stores the session key in itself thus once the server reboots or redirect requests to a different server using load balancers, your "state" of session key becomes useless.

Stateless authentication:

What Stateless authentication means is that we don’t want to store any information about user on the server. One of the reasons can be serving your application through a load balancer and we do not want to use sticky sessions. With this configuration in place the user can reach out different backend servers transparently without having the need to reauthenticate. For REST API also, this may sound a better approach because we don’t want to rely on Basic Auth.

The basic idea behind a stateless authentication is that the user authenticates against your application, if the operation is successful the server will respond with a token that the user should send on any request using an Http Header or a Cookie to prove his identity (for our article we will use Http Headers). On the other side the server also needs to validate our token and verify if still valid, not expired and nobody tampered with it.

Exactly this can be achieved by the use of JWT.

Requirements:

Note: This tutorial was built using Spring Boot 2.0.4.RELEASE.

Before we begin coding let’s first define our dependencies and what we will need to build our application. Above is our pom.xml with all needed dependencies.


<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-jpa</artifactId>
 </dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
 <groupId>com.h2database</groupId>
 <artifactId>h2</artifactId>
 <scope>runtime</scope>
</dependency>
<dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt</artifactId>
 <version>0.9.0</version>
</dependency>

Configure Security:

After defining the dependencies, let’s know enable security by adding a Configuration class that extends WebSecurityConfigurerAdapter.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  
    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(this.userDetailsService)
                .passwordEncoder(passwordEncoder());
    }


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

    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
        return new JwtAuthenticationTokenFilter();
    }

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

        httpSecurity
                // we don't need CSRF because we store token in header
                .csrf().disable()

                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()

                // don't create session 
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

                .authorizeRequests()

                // allow anonymous resource requests
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                ).permitAll()
                .antMatchers(
                        HttpMethod.OPTIONS
                ).permitAll()
                .antMatchers("/api/open/**").permitAll()
                .anyRequest().authenticated();

        // Custom JWT based security filter
        httpSecurity
                .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);

        // disable page caching
        httpSecurity.headers().cacheControl().disable();
    }
    
    @Bean
    @Override
     public AuthenticationManager authenticationManagerBean() throws Exception {
          return super.authenticationManagerBean();
    }
}

For our application to be exposed to third parties clients it should support CORS, this is why we also need to create a class where we define our CORS configuration by defining a CorsFilter Bean.


@Configuration
public class RestConfig {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:4200");
        config.addAllowedHeader("*");
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("DELETE");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

}

How to go Stateless?

As you can see in our Class WebSecurityConfig.java we have told Spring Security that it should not store any session information and to be stateless using the method sessionCreationPolicy().

We also disabled CSRF because we will store our token in the header not in an HttpOnly cookie.

Now let’s define our JwtAuthenticationTokenFilter, this filter intercept all user’s requests and look for the Token in Authorization request header, if it exist it will be checked for validity then decide to authorize the request or deny it.


public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

 private final Log logger = LogFactory.getLog(this.getClass());

 @Autowired
 @Qualifier(value = "jwtUtilWithoutDbCheckImpl")
 private JwtUtil jwtTokenUtil;
 
 @Value("${jwt.header}")
 private String tokenHeader;
 @Value("${jwt.refresh.header}")
 private String refreshTokenHeader;

 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
   throws ServletException, IOException {
  
  response.addHeader("Access-Control-Allow-Headers",
                "Access-Control-Allow-Origin, Origin, Accept, X-Requested-With, Authorization, refreshauthorization, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Allow-Credentials");
        if (response.getHeader("Access-Control-Allow-Origin") == null)
            response.addHeader("Access-Control-Allow-Origin", "http://localhost:4200");
        if(response.getHeader("Access-Control-Allow-Credentials") == null)
         response.addHeader("Access-Control-Allow-Credentials", "true");
        if(response.getHeader("Access-Control-Allow-Methods") == null)
         response.addHeader("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, DELETE");
        
        String token;
        
        if(!request.getMethod().equals("OPTIONS")){
         token = request.getHeader(this.tokenHeader).substring(6);
        }else{
         token = request.getHeader(this.tokenHeader);
        }
  
  if (token != null && !token.equals("")) {
   
   if(jwtTokenUtil.isTokenExpired(token)){
    response.setStatus(490);
    return;
   }
   
   if (jwtTokenUtil.validateToken(token)) {
    
    String username = jwtTokenUtil.getUsernameFromToken(token);
    List autorities = jwtTokenUtil.getRolesFromToken(token);
    logger.info("checking Validity of JWT for user ");

    logger.info("checking authentication for user " + username);

    if (SecurityContextHolder.getContext().getAuthentication() == null) {

     UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, autorities);
     authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
     logger.info("authenticated user " + username + ", setting security context");
     SecurityContextHolder.getContext().setAuthentication(authentication);
    }
   }
  }
  chain.doFilter(request, response);
  
 }

}

We also need to define the Class JwtAuthenticationEntryPoint that is invoked when a user tries to access a secure REST resource without supplying any credentials, in that particular case the server should respond with a 401 Unautorized message as there is no login page to redirect to.


@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        // This is invoked when user tries to access a secure REST resource without supplying any credentials
        // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

Next create the JwtUser Class and JwtUserFactory Class.


public class JwtUser implements UserDetails {

 private static final long serialVersionUID = 1L;
 private final Long id;
    private final String username;
    private final String password;
    private final Collection authorities;
    private final boolean enabled;

    public JwtUser(
          Long id,
          String username,
          String password, Collection authorities,
          boolean enabled
    ) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.authorities = authorities;
        this.enabled = enabled;
    }

    @JsonIgnore
    public Long getId() {
        return id;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public Collection getAuthorities() {
        return authorities;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

}


public final class JwtUserFactory {

    private JwtUserFactory() {
    }

    public static JwtUser create(User user) {
        return new JwtUser(
                user.getId(),
                user.getUsername(),
                user.getPassword(),
                mapToGrantedAuthorities(new ArrayList(user.getRoles())),
                user.isEnabled()
        );
    }

    private static List mapToGrantedAuthorities(List authorities) {
        return authorities.stream()
                .map(authority -> new SimpleGrantedAuthority("ROLE_"+authority.getRoleName().toUpperCase()))
                .collect(Collectors.toList());
    }
}

Now define the utility Class JwtUtilWithoutDbCheckImpl that implements JwtUtil interface, so we can manage our Token easily from creation to validation.


@Component
public class JwtUtilWithoutDbCheckImpl implements JwtUtil, Serializable {

 private static final long serialVersionUID = -3301605591108950415L;

    static final String CLAIM_KEY_ID = "jti";
    static final String CLAIM_KEY_USERID = "userId";
    static final String CLAIM_KEY_USERNAME = "sub";
    static final String CLAIM_KEY_CREATED = "created";
    static final String CLAIM_KEY_ROLES = "roles";

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

    @Value("${jwt.expiration}")
    private Long expiration;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    public String getIdFromToken(String token) {
        String id;
        try {
            final Claims claims = getClaimsFromToken(token);
            id = claims.getId();
        } catch (Exception e) {
         id = null;
        }
        return id;
    }
    
    @Override
    public Long getUserIdFromToken(String token){
     Long userId;
        try {
            final Claims claims = getClaimsFromToken(token);
            userId = Long.parseLong(claims.get(CLAIM_KEY_USERID).toString());
        } catch (Exception e) {
         userId = null;
        }
        return userId;
    }
    
    @Override
    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }
    
    @SuppressWarnings("unchecked")
    @Override
 public List getRolesFromToken(String token) {
     List roles;
        try {
            final Claims claims = getClaimsFromToken(token);
            roles = (List)claims.get(CLAIM_KEY_ROLES);
        } catch (Exception e) {
         roles = null;
        }
        return roles != null ? roles.stream().map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toList()) : null;
    }
    
    @Override
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        } catch (Exception e) {
            created = null;
        }
        return created;
    }
    
    @Override
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    @Override
    public Claims getClaimsFromToken(String token) throws ExpiredJwtException, UnsupportedJwtException, 
    MalformedJwtException, SignatureException, IllegalArgumentException
 {
        Claims claims;
        
        claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();

        return claims;
    }
    
    
    private Date generateExpirationDate(String type) {
     if(type.equals("token")){
      return new Date(System.currentTimeMillis() + expiration * 1000);
     }else{
      return new Date(System.currentTimeMillis() + expiration * 5 * 1000);
     }
    }
    
    @Override
    public Boolean isTokenExpired(String token) {
     try{
      getClaimsFromToken(token);
      return false;
     }catch(ExpiredJwtException ex){
      return true;
     }
     
    }
    
    @Override
    public Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }
    
    @Override
    public JwtAuthenticationResponse generateToken(String username) {
     
        Map claims = generateClaims(username);
        String token = doGenerateToken(claims);
        String refreshToken = doGenerateRefreshToken(claims);
        return new JwtAuthenticationResponse(token, refreshToken);
    }
    
    @Override
    public JwtAuthenticationResponse refreshToken(String username) {
     
     String newToken;
        String newRefreshToken;
        try {
            final Map claims = generateClaims(username);
            newToken = doGenerateToken(claims);
            newRefreshToken = doGenerateRefreshToken(claims);
        } catch (Exception e) {
         newToken = null;
         newRefreshToken = null;
        }
        return new JwtAuthenticationResponse(newToken, newRefreshToken);
    }
    
    private Map generateClaims(String username){
     final JwtUser userDetails = (JwtUser)userDetailsService.loadUserByUsername(username);
        Map claims = new HashMap<>();
        
        claims.put(CLAIM_KEY_ID, UUID.randomUUID().toString());
        claims.put(CLAIM_KEY_USERID, userDetails.getId());
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        claims.put(CLAIM_KEY_ROLES, AuthorityUtils.authorityListToSet(userDetails.getAuthorities()));
        
        return claims;
    }
    
    private String doGenerateToken(Map claims) {
        return Jwts.builder()
          .setClaims(claims)
                .setExpiration(generateExpirationDate("token"))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    
    private String doGenerateRefreshToken(Map claims) {
        return Jwts.builder()
          .setClaims(claims)
                .setExpiration(generateExpirationDate("refresh"))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    
    @Override
    public Boolean validateToken(String token) {
     try{
      getClaimsFromToken(token);
      return true;
     }catch(Exception ex){
      return false;
     }
    }
}


public interface JwtUtil {
 
 public String getIdFromToken(String token);
 
 public Long getUserIdFromToken(String token);
    
    public String getUsernameFromToken(String token);
    
 public List getRolesFromToken(String token);

    public Date getCreatedDateFromToken(String token);

    public Date getExpirationDateFromToken(String token);

    public Claims getClaimsFromToken(String token);

    public Boolean isTokenExpired(String token);

    public Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset);

    public JwtAuthenticationResponse generateToken(String username);

    public JwtAuthenticationResponse refreshToken(String token);

    public Boolean validateToken(String token);
}

We also need to create the JwtUserDetailsServiceImpl Class that implements Spring Security’s interface UserDetailsService. This class overrides the loadUserByUsername method to check if the user exists or not.


@Service
@Transactional
public class JwtUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IUserRepository userRepo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepo.findByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
        } else {
            return JwtUserFactory.create(user);
        }
    }
}

Test It!

To test that everything works properly, we will create a REST Controller as bellow.


@RestController
@RequestMapping("/api/open/")
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    @Qualifier(value = "jwtUtilWithoutDbCheckImpl")
    private JwtUtil jwtTokenUtil;
    
    @RequestMapping(value = "${jwt.route.authentication.path}", method = RequestMethod.POST)
    public ResponseEntity createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest) throws AuthenticationException {

        // Perform the security
     try{
      final Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            authenticationRequest.getUsername(),
                            authenticationRequest.getPassword()
                    )
            );
      SecurityContextHolder.getContext().setAuthentication(authentication);
     }catch(AuthenticationException e){
      
      return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ServerResponse(e.getMessage()));
     }
        
        final JwtAuthenticationResponse token = jwtTokenUtil.generateToken(authenticationRequest.getUsername());
        // Return the token
        return ResponseEntity.ok(token);
    }
}

What this method does, is it receives the user’s credentials then check them using an implementation of the AuthenticationManager Interface, if all is correct the user get a token from the server elsewhere he will receive an unauthorized message.

You will also need to define JwtAuthenticationRequest and JwtAuthenticationResponse DTOs classes.


public class  JwtAuthenticationRequest implements Serializable {

    private static final long serialVersionUID = -8445943548965154778L;

    private String username;
    private String password;

    public JwtAuthenticationRequest() {
        super();
    }

    public JwtAuthenticationRequest(String username, String password) {
        this.setUsername(username);
        this.setPassword(password);
    }

    public String getUsername() {
        return this.username;
    }

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

    public String getPassword() {
        return this.password;
    }

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


public class JwtAuthenticationResponse implements Serializable {

    private static final long serialVersionUID = 1250166508152483573L;

    private final String token;
    private final String refreshToken;

    public JwtAuthenticationResponse(String token, String refreshToken) {
        this.token = token;
        this.refreshToken = refreshToken;
    }

    public String getToken() {
        return this.token;
    }

 public String getRefreshToken() {
  return refreshToken;
 }
}

And here is another Controller to test things out.


@RestController
@RequestMapping("/api/auth/")
public class AuthenticatedUserController {

 
 @Value("${jwt.header}")
    private String tokenHeader;
 
    @Autowired
    @Qualifier(value = "jwtUtilWithoutDbCheckImpl")
    private JwtUtil jwtTokenUtil;
    @Autowired
    private IUserService userService;

    @RequestMapping(value = "me", method = RequestMethod.GET)
    public ResponseEntity getAuthenticatedUser(@RequestHeader(value= "${jwt.header}") String token) {
        
     Long userId = jwtTokenUtil.getUserIdFromToken(token);
     User user = userService.read(userId);
        return ResponseEntity.ok(user.getUsername());
    }

}

Finally we need to implement the CommandLineRunner in our Application or Main class JwtAuthenticationApplication, so we can insert some users with their respective roles into database at runtime.


@SpringBootApplication
public class JwtAuthenticationApplication implements CommandLineRunner{

 @Autowired
 IUserService userService;
 @Autowired
 IRoleService roleService;
 
 public static void main(String[] args) {
  SpringApplication.run(JwtAuthenticationApplication.class, args);
 }

 @Override
 @Transactional
 public void run(String... args) throws Exception {
  // TODO Auto-generated method stub
  Role role_admin = new Role("ADMIN");
  Role role_user = new Role("USER");
  roleService.create(role_admin);
  roleService.create(role_user);
  
  User admin = new User("admin", "pass", true);
  admin.addRole(role_admin);
  admin.addRole(role_user);
  
  User u = new User("user", "pass", true);
  u.addRole(role_user);
  
  userService.create(admin);
  userService.create(u);
 }
}

Conclusion:

In this article, we defined the two types or approachs of authentication, the session-based one and the stateless authentication and we have learned step by step the way to implement a stateless authentication system in our Spring Boot application using the JWT approach from scratch. Hope that this post was a great help for you. The full source code of this tutorial can be found on GitHub .

If you liked the content, share and please let me know in the comments section if you have any questions.

Name

Angular,7,Angular 8,1,Best Practices,1,Design,1,Firebase,1,Ionic,1,Java,5,Nodejs,2,Python,1,Restful API,1,Software Development,1,Spring,3,Spring Batch,1,Spring Boot 2,1,Web Development,1,
ltr
item
Programming Tutorials, News and Reviews: Stateless Authentication using JWT to secure a Spring Boot REST API
Stateless Authentication using JWT to secure a Spring Boot REST API
How we can implement step by step a stateless authentication using JWT to secure a REST API endpoints built with the help of Spring Boot and Spring Security.
Programming Tutorials, News and Reviews
https://www.ninjadevcorner.com/2018/09/stateless-authentication-jwt-secure-spring-boot-rest-api.html
https://www.ninjadevcorner.com/
https://www.ninjadevcorner.com/
https://www.ninjadevcorner.com/2018/09/stateless-authentication-jwt-secure-spring-boot-rest-api.html
true
493653397416713395
UTF-8
Loaded All Posts Not found any posts VIEW ALL Readmore Reply Cancel reply Delete By Home PAGES POSTS View All RECOMMENDED FOR YOU LABEL ARCHIVE SEARCH ALL POSTS Not found any post match with your request Back Home Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sun Mon Tue Wed Thu Fri Sat January February March April May June July August September October November December Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec just now 1 minute ago $$1$$ minutes ago 1 hour ago $$1$$ hours ago Yesterday $$1$$ days ago $$1$$ weeks ago more than 5 weeks ago Followers Follow THIS CONTENT IS PREMIUM Please share to unlock Copy All Code Select All Code All codes were copied to your clipboard Can not copy the codes / texts, please press [CTRL]+[C] (or CMD+C with Mac) to copy