In this part of the series, we’ll look at the most complex part of the project: Generating, reading, and validating JWT tokens. The backend server issues tokens and returns them to users who requested one. When a user tries to access a restricted resource, they have to submit a previously generated token which the server validates. If the token validation is successful, the user may access the requested resource.
Implement the JWT features
Let’s start with the AuthenticationTokenConfigurer class which will set up our custom security filter and link all the components:
public class AuthenticationTokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>
{
private AuthenticationTokenProvider authenticationTokenProvider;
public AuthenticationTokenConfigurer(AuthenticationTokenProvider authenticationTokenProvider)
{
this.authenticationTokenProvider = authenticationTokenProvider;
}
@Override
public void configure(HttpSecurity http)
{
AuthenticationTokenFilter customFilter = new AuthenticationTokenFilter(authenticationTokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Okay, so right now this might not make too much sense. However, after looking at the custom filter, it should:
@Component
public class AuthenticationTokenFilter extends GenericFilterBean
{
// The token provider is needed for token validation
private AuthenticationTokenProvider authenticationTokenProvider;
public AuthenticationTokenFilter(AuthenticationTokenProvider authenticationTokenProvider)
{
this.authenticationTokenProvider = authenticationTokenProvider;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException
{
// Get the token and try to parse it
String authenticationHeader = ((HttpServletRequest)req).getHeader(SecurityGlobals.HEADER_FIELD_NAME);
String token = authenticationTokenProvider.getTokenFromHTTPHeader(authenticationHeader);
// Check, whether the token was valid
if (token != null && authenticationTokenProvider.validateToken(token))
{
Authentication auth = authenticationTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
// Call the next filter when we're done
filterChain.doFilter(req, res);
}
}
The filter chain is exactly what the name suggests: A chain of filters. You can add custom filters to it and each filter can read and modify the requests that users send to the server or that the server sends out to clients.
In this particular case, we read the value of the authentication header of each HTTP request. After that, the program extracts the payload of that header field.
Once that’s done, the server validates the payload and if it’s valid, the program reads the user data of the client this token was issued for.
The last line of the custom filter calls the next filter in the chain.
All the token generation, extraction, and validation is done in the provider class, which is the biggest module of this project.
The JWT provider
Token generation
Now we can finally generate tokens for a user. To do that, I implemented the following method:
public String createToken(User userDetails, boolean futureToken)
{
Date now = new Date();
Date validity = new Date(now.getTime() + SecurityGlobals.TIME_TO_LIVE);
Date validFrom = now;
Claims claims = Jwts.claims()
.setSubject(userDetails.getUsername())
.setIssuer(SecurityGlobals.ISSUER)
.setIssuedAt(now)
.setNotBefore(validFrom)
.setExpiration(validity);
claims.put("administrator", userDetails.getAdmin());
claims.put("id", userDetails.getId());
return Jwts.builder()
.setClaims(claims)
.signWith(SecurityGlobals.SIGNING_KEY)
.compact();
}
So, first we get the current time. This allows the server to give each token an expiration date. Then, the server generates the claims. This is the second array that we saw earlier when decoding the JWT. As you can see, you can add as many custom claims as you wish. I added two. The isAdmin value and the ID of the user who requested the token.
Once that’s done, the claims are signed with a signing key. This was one aspect that many (if not almost all) other tutorials got wrong. The signing key is NOT supposed to be a simple string (many use “secret” or “key” which are the worst keys possible closely followed by “password”). The JWT library includes a key generation mechanism that should be used to create the correct length and type of key for the encryption algorithm in use. Everything else is prone to errors and a potential security risk.
Token validation
The following method validates a token and returns true if it’s valid
boolean validateToken(String token)
{
if(null == token)
return false;
try
{
Jws<Claims> claims = Jwts.parser().setSigningKey(SecurityGlobals.SIGNING_KEY).parseClaimsJws(token);
if (claims.getBody().getExpiration().before(new Date()))
return false;
}
catch (SignatureException e)
{
// The signature of the given token did not match the locally computed one.
// This error occurs when a user tries to use an old key or one,
// that didn't get created by this server.
}
return true;
}
In this simple case, the only thing checked is the expiration time of the token. If it’s not yet expired, the server accepts the token. In your implementation, you can add as many checks as you need.
Other features
The token provider has a few helper methods for parsing different fields of a JWT. However, I think it’s not necessary to discuss them in detail as they are pretty self-explanatory. You can download the code at the end of this series.
Last but not least, make sure that the TokenProvider class is annotated as a Component.
The authentication endpoint
Now that everything’s set up, we can almost start using the application. The last thing we need is an endpoint where unauthenticated users can enter their credentials and obtain a valid token from the backend which can be used to access a secured resource.
So create a new endpoint:
@RestController
@RequestMapping("/authentication")
@CrossOrigin
public class AuthenticationEndpoint
{
@Autowired
AuthenticationTokenProvider authenticationTokenProvider;
@Autowired
IUserDao users;
@PostMapping("/")
@CrossOrigin
public AuthenticationResponse createBearerToken(@RequestBody AuthenticationRequest data)
{
AuthenticationResponse response = new AuthenticationResponse();
try
{
User user = users.findOneByUsername(data.getUsername());
if(user == null)
{
// User not found in database
}
else if(!user.getPassword().equals(data.getPassword()))
{
// The supplied password didn't match the stored one
}
else
{
response.setToken(authenticationTokenProvider.createToken(user, false));
}
}
catch (Exception e)
{
// Proper exception handling needed
}
return response;
}
}
The two classes used in the endpoint (AuthenticationRequest and AuthenticationResponse) contain variables and getter/setter methods for them:
public class AuthenticationRequest
{
String username, password;
// Getters, Setters
}
public class AuthenticationResponse
{
private String token;
// Getters, Setters
}
Obtaining a key
Start the application and send a POST request to the user endpoint to create a new user:

Now send a GET request to the authentication endpoint:

Copy the token to the clipboard and access a secured area, for example GET /users/

As you can see, the server accepts the JWT and returns a list of users!
Access the authenticated user’s information in the backend
So far, all the security features were managed by Spring Security. It restricts access to certain resources and allows certain methods. But what if you want to allow all users to access a certain resource but only allow administrators to read certain values?
An example: All users should be able to access /users/x where x is a user ID. However, regular users may only access the resource if x corresponds to their own ID. Administrators should be able to get each user’s details. That’s the reason why we implemented our User entity and the UserService the way we did: It will allow us to access all fields of the user that sent the request.
So let’s extend the old findOneById method of the UserEndpoint so that it looks like this:
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@CrossOrigin
public User findOneById(@PathVariable("id") Long id)
{
User loggedInUser = (User)(SecurityContextHolder.getContext().getAuthentication().getPrincipal());
User found = userService.findOneById(id);
if(loggedInUser.getId().equals(found.getId()) || loggedInUser.getAdmin())
return found;
else
return null;
}
This snippet retrieves the information of the user who sent the request. Then, the code checks whether the requesting user is an administrator or the user requested their own account data. If either is the case, the server returns the user data. Otherwise, it returns null.
Note: In such a case it would be better to throw a 403 exception instead of returning null. But this method is sufficient for this tutorial.
What’s still left to do?
You’ll need to implement proper exception handling. I omitted all of it to keep the code short and easier to understand.
The tokens will expire after five minutes and a user needs to renew them when that happens. That will be addressed in the next part of this series.
If you use an Angular frontend, you’ll need to implement an authentication and renewal mechanism. I’ll show you how I did it in the last part of this series.
Table of contents
Part 1 – Basics of JWT and the project scaffolding
Part 2 – Configure Spring Security
Part 3 – JWT token generation, validation, and authentication (You are here)
Part 4 – JWT authentication in an Angular frontend
Part 5 – Token renewal

Thanks so much for this, mate! Implementing those security features was causing me so much trouble. Interesting, how hard it was to find a proper tutorial on that subject.
LikeLiked by 1 person