Spring Boot Jwt Authentication

By Dhiraj Ray, 21 October,2017  

In this post we will be securing our REST APIs with JWT(JSOn Web Token) authentication. We will be using spring boot maven based configuration to develop and secure our APIs. We will be extending OncePerRequestFilter class to define our custom authentication mechanism using JWT.The authentication mechanism can be applied to URLs as well as for methods. And atlast, we will be testing the implementation with google advanced REST client.

What is JWT

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.a stateless authentication mechanism as the user state is never saved in server memory.A JWT token consists of 3 parts seperated with a dot(.) i.e. Header.payload.signature

Header has 2 parts type of token and hashing algorithm used.The JSON structure comprising these two keys are Base64Encoded.

{ "alg": "HS256", "typ": "JWT" }

Payload contains the claims.Primarily, there are three types of claims: reserved, public, and private claims. Reserved claims are predefined claims such as iss (issuer), exp (expiration time), sub (subject), aud (audience).In private claims, we can create some custom claims such as subject, role, and others.

{ "sub": "Alex123", "scopes": [ { "authority": "ROLE_ADMIN" } ], "iss": "http://devglan.com", "iat": 1508607322, "exp": 1508625322 }

Signature ensures that the token is not changed on the way.For example if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

The server's protected routes will check for a valid JWT in the Authorization header, and if it's present, the user will be allowed to access protected resources.Whenever the user wants to access a protected route or resource, the user agent should send the JWT, typically in the Authorization header using the Bearer schema. The content of the header should look like the following:

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJBbGV4MTIzIiwic2N.v9A80eU1VDo2Mm9UqN2FyEpyT79IUmhg

Project Structure

Following will be the final project structure that we will be building for our spring boot JWT authentication.


Implementing JWT Authentication Mechanism

Following class extends OncePerRequestFilter that ensures a single execution per request dispatch. This class checks for the authorization header and authenticates the JWT token and sets the authentication in the context.

public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { String header = req.getHeader(HEADER_STRING); String username = null; String authToken = null; if (header != null && header.startsWith(TOKEN_PREFIX)) { authToken = header.replace(TOKEN_PREFIX,""); try { username = jwtTokenUtil.getUsernameFromToken(authToken); } catch (IllegalArgumentException e) { logger.error("an error occured during getting username from token", e); } catch (ExpiredJwtException e) { logger.warn("the token is expired and not valid anymore", e); } catch(SignatureException e){ logger.error("Authentication Failed. Username or Password not valid."); } } else { logger.warn("couldn't find bearer string, will ignore the header"); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"))); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req)); logger.info("authenticated user " + username + ", setting security context"); SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(req, res); } }

Following is the util class to generate the auth token as well as to extract username from the token.

@Component public class JwtTokenUtil implements Serializable { public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } public T getClaimFromToken(String token, Function claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } private Claims getAllClaimsFromToken(String token) { return Jwts.parser() .setSigningKey(SIGNING_KEY) .parseClaimsJws(token) .getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } public String generateToken(User user) { return doGenerateToken(user.getUsername()); } private String doGenerateToken(String subject) { Claims claims = Jwts.claims().setSubject(subject); claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"))); return Jwts.builder() .setClaims(claims) .setIssuer("http://devglan.com") .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000)) .signWith(SignatureAlgorithm.HS256, SIGNING_KEY) .compact(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return ( username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }

Following are the constants that we have used in above implementation.

public class Constants { public static final long ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60; public static final String SIGNING_KEY = "devglan123r"; public static final String TOKEN_PREFIX = "Bearer "; public static final String HEADER_STRING = "Authorization"; }

Now let us define our usual security configurations.We have userDetailsService injected to fetch user credentials from database.

@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource(name = "userService") private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Autowired public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(encoder()); } @Bean public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception { return new JwtAuthenticationFilter(); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable(). authorizeRequests() .antMatchers("/token/*").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); } @Bean public BCryptPasswordEncoder encoder(){ return new BCryptPasswordEncoder(); } }

Following is the controller that is exposed to create token on user behalf.

@RestController @RequestMapping("/token") public class AuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserService userService; @RequestMapping(value = "/generate-token", method = RequestMethod.POST) public ResponseEntity register(@RequestBody LoginUser loginUser) throws AuthenticationException { final Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginUser.getUsername(), loginUser.getPassword() ) ); SecurityContextHolder.getContext().setAuthentication(authentication); final User user = userService.findOne(loginUser.getUsername()); final String token = jwtTokenUtil.generateToken(user); return ResponseEntity.ok(new AuthToken(token)); } }

We have very simple REST Apis exposed for testing purpose.Following is the implementation.

@RestController public class UserController { @Autowired private UserService userService; @RequestMapping(value="/user", method = RequestMethod.GET) public List listUser(){ return userService.findAll(); } @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) public User getOne(@PathVariable(value = "id") Long id){ return userService.findById(id); } }

Following is our entity class.

@Entity public class User { @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; @Column private String username; @Column @JsonIgnore private String password; @Column private long salary; @Column private int age;

Default Scripts

Following are the insert statements that are inserted when application starts.

INSERT INTO User (id, username, password, salary, age) VALUES (1, 'Alex123', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 3456, 33); INSERT INTO User (id, username, password, salary, age) VALUES (2, 'Tom234', '$2a$04$PCIX2hYrve38M7eOcqAbCO9UqjYg7gfFNpKsinAxh99nms9e.8HwK', 7823, 23); INSERT INTO User (id, username, password, salary, age) VALUES (3, 'Adam', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 4234, 45);

Testing the Application

We will be using Advanced REST Client to test the spring boot jwt authentication.

Generate AuthToken generate-auth-token Accessing Resource Without Token unauthorised Accessing Resource With Token jwt-auth-success


I hope this article served you that you were looking for. If you have anything that you want to add or share then please share it below in the comment section.




jw authentication/

How to

Suggest more topics in suggestion section or write your own article and share with your colleagues.

Is this page helpful to you? Please give us your feedback below. We would love to hear your thoughts on these articles, it will help us improve further our learning process.

Further Reading: