Implement jwt in Spring Boot & React

Steve Mu
4 min readMay 4, 2019

In this article, I will show some code example on how to implement both access token and refresh token with jwt in Spring Boot.

Here is my application structure:

First, we have two routes, one for signin, one for getting a new refresh token. The signin route will return both the accessToken and refreshToken. In the frontend, You can store both in localStorage, then configure your frontend to check the expiration date in the accessToken token whenever it is making a request with the access token, and when it is going to expire in a 1 day for example, you can exchange the refreshToken with a new access token.

Here is my code in front end:

export const getAuthed = async (url) => {
await renewAccessTokenIfGoingExpire();

let token = getLocalToken();
let res = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
}
})
let j = await res.json();
return j;
}
//
export const checkAccessTokenWillExpireInDay = (days) => {
// decode access token
// check exp
// if will exp in a day
let decoded = getDecodedAccessToken();
return moment(decoded.exp * 1000).subtract(days, "days").isBefore(moment());
};

export const getDecodedAccessToken = () => {
let accessToken = getLocalToken();
let decoded = jwt_decode(accessToken);
return decoded;
}

export const exchangeRefreshTokenWithNewAccessToken = async () => {
let refreshToken = localStorage.getItem("refreshToken");
let username = getDecodedAccessToken().username;
let res = await post("/api/auth/renew", {
username,
refreshToken
});
return res.token;
};

export const renewAccessTokenIfGoingExpire = async () => {
let willExpire = checkAccessTokenWillExpireInDay(1);
if (willExpire) {
let token = await exchangeRefreshTokenWithNewAccessToken();
localStorage.setItem("token", token);
}
};

and in Spring Boot:

package com.stevemu.rest;

import com.fasterxml.uuid.Generators;
import com.stevemu.security.jwt.JwtTokenProvider;
import com.stevemu.user.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import static org.springframework.http.ResponseEntity.ok;

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

// username: refreshToken
private HashMap<String, String> refreshTokens = new HashMap<String, String>();

@Autowired
AuthenticationManager authenticationManager;

@Autowired
JwtTokenProvider jwtTokenProvider;

@Autowired
UserRepository users;

@PostMapping("/renew")
public ResponseEntity renew(@RequestBody RefreshTokenRequest data) {

Map<Object, Object> model = new HashMap<>();

// get username
// for now, pretend it is s
String username = data.getUsername();
String refreshToken = data.getRefreshToken();

if (refreshTokens.get(refreshToken) == null) {
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}

// check if the refreshToken is for this username
if (refreshTokens.get(refreshToken).equals(username)) {
// generate a new access token for this user
String token = jwtTokenProvider.createToken(username, this.users.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("Username " + username + "not found")).getRoles());

model.put("token", token);
return ok(model);
}

return new ResponseEntity(HttpStatus.UNAUTHORIZED);



}

@PostMapping("/signin")
public ResponseEntity signin(@RequestBody AuthenticationRequest data) {

try {
String username = data.getUsername();
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, data.getPassword()));
String token = jwtTokenProvider.createToken(username, this.users.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("Username " + username + "not found")).getRoles());

// generate a refreshToken
UUID refreshToken = Generators.randomBasedGenerator().generate();

// store the refreshToken the username
refreshTokens.put(refreshToken.toString(), username);

Map<Object, Object> model = new HashMap<>();
model.put("username", username);
model.put("token", token);
model.put("refreshToken", refreshToken);

return ok(model);
} catch (AuthenticationException e) {
throw new BadCredentialsException("Invalid username/password supplied");
}
}
}

Next, you need to create those files for implemnting jwt:

InvalidJwtAuthenticationException.java:public class InvalidJwtAuthenticationException extends AuthenticationException {
public InvalidJwtAuthenticationException(String e) {
super(e);
}
}
JwtConfigurer.java:public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

private JwtTokenProvider jwtTokenProvider;

public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}

@Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
JwtTokenFilter:public class JwtTokenFilter extends GenericFilterBean {

private JwtTokenProvider jwtTokenProvider;

public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
throws IOException, ServletException {

String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);

if (auth != null) {
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(req, res);
}

}
JwtTokenProvider:@Component
public class JwtTokenProvider {

@Value("${security.jwt.token.secret-key:secret}")
private String secretKey = "secret";

@Value("${security.jwt.token.expire-length:172800000}")
private long validityInMilliseconds = 172800000; // 2 days

@Autowired
private UserDetailsService userDetailsService;

@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}

public String createToken(String username, List<String> roles) {

Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
claims.put("username", username);

Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);

return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}

public Authentication getAuthentication(String token) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}

public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}

public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}

public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);

if (claims.getBody().getExpiration().before(new Date())) {
return false;
}

return true;
} catch (JwtException | IllegalArgumentException e) {
throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
}
}

}
CustomUserDetailsService:@Component
public class CustomUserDetailsService implements UserDetailsService {

private UserRepository users;

public CustomUserDetailsService(UserRepository users) {
this.users = users;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.users.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Username: " + username + " not found"));
}
}
RefreshTokenRequest:@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RefreshTokenRequest {
private String refreshToken;
private String username;
}
AuthenticationRequest.java:@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthenticationRequest implements Serializable {
private String username;
private String password;
}
SecurityConfig.java:@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


@Autowired
JwtTokenProvider jwtTokenProvider;

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

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

http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/users/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.apply(new JwtConfigurer(jwtTokenProvider));
}


}

I also have those in my build.gradle:

compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
compile group: 'com.fasterxml.uuid', name: 'java-uuid-generator', version: '3.2.0'
compile group: 'org.projectlombok', name: 'lombok', version: '1.18.6'

Source code: https://github.com/stevemu/landscaping_business_dashboard

--

--