Deep Dive to OAuth2.0 and JWT (Part 4 JWT Use Case)


Up your Spring Security game!


Assume that you are building an application for a hypothetical store chain. Each user of this application is assigned a role, and each role has a defined set of activities that it can perform (technically the API that it can access). Let say this store has the following roles and activities. (Note: this is part our in a series on JWTs security best-practices, parts one, two, and three can be found here, here, and here, respectively.)

  • Admin
    • Can add new stores.
    • Can add new users and assign roles to them (store admin and store user).
  • Store Manager
    • Can add new products to the store.
    • Can remove products from the store.
    • Can update product details.
  • User
    • Can view his/her detail.
    • Can view all products.
    • Can view a product using product id.
    • Can get all products from a store.


We will be implementing authentication with the following tools:

  • Spring Boot 2: Web and Security.
  • JJWT: JWT library for Java and Android.
  • H2 : in-memory database for our application.

You may also like: Spring Security Authentication.


The need here is to implement access control based on roles. So, one of the possible solutions could be to group our API based on use and allow access only to that API group, which has the allowed role. We can manage the role in JWT, and for each request, we can validate the role against the API based on which user is trying to access it.

Authentication workflow

Authentication workflow


If we try to map these requirements to APIs, one of the possible solutions could be to group the APIs as follows:

  • The API that lets the user login, which is accessible to everyone, will return a JWT token in case of a successful login. It will return an HTTP status 401 (Unauthorized) in case of login failure.
  • The APIs where you can perform all the operations related to products should only be accessible to users having role the of MANAGER. Let us group them under the URL “/product“.
  • The APIs using that allows you to add users and stores should only be accessible to users having role ADMIN. Let us group them under the URL “/mgmt“.
  • Users who can view all products or by product id or by store id will be grouped under the URL,  “/user“.

Below could be the possible APIs that can be implemented for the above requirements.

API access by role

API access by role

Components Required

To add authorization, we need to have below components.

  • Add a filter that will extract the “Authorization” token from each request and set it to an instance of (Spring’s Security context). This filter should be configured to run before UsernamePasswordAuthenticationFilter.
  • An implementation of will act as the principal object and will be available in the Spring Security context for each validated request.
  • An implementation of will manage the logic to create and set the Principal of users.
  • A JWT token helper will perform operations related to JWT, such as create, validate, or extract information based on the given token.
  • Finally, we will need the security configurations to configure allow and deny rules.

Time to Code

You can find the full code on GitHub.

JWT Filter: AUTH token extraction and validation logic. This will be executed for each secured request.

package; import;
import; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpHeaders;
import org.springframework.web.filter.GenericFilterBean; /** * @author satish sharma * * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is * found. */
public class JWTFilter extends GenericFilterBean { private TokenService jwtTokenProvider; public JWTFilter(TokenService jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; } /** * Extract Authorization header and validate token. * IF token is valid the set the {@link Principal} */ @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException { String token = jwtTokenProvider.getTokenValue(((HttpServletRequest)req).getHeader(HttpHeaders.AUTHORIZATION)); if (token != null && jwtTokenProvider.validateToken(token)) { Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null; SecurityContextHolder.getContext().setAuthentication(auth); } filterChain.doFilter(req, res); }

JWT Configurer: Configure the JWTFilter to run before the other filters.

package; import;
/** * * @author satish sharma * * Configure {@link JWTFilter} in the security filters chain * */
public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { private TokenService jwtTokenProvider; public JwtConfigurer(TokenService jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; } @Override public void configure(HttpSecurity http) throws Exception { JWTFilter customFilter = new JWTFilter(jwtTokenProvider); http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); }

UserDetail Service: The logic to find out user details and set the Principal.

package; import; import;
import org.springframework.stereotype.Service;
/** * * @author satish sharma * * Find and configure user details to be available as {@link Principal} * */
public class UserDetailsServiceImpl implements UserDetailsService { private UserRepository userRepo; public UserDetailsServiceImpl(UserRepository userRepo) {
this.userRepo = userRepo;
} /** * User Principal finding logic. */
public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException { return this.userRepo.findByLoginId(loginId)
.orElseThrow(() -> new UsernameNotFoundException("LoginId: " + loginId + " not found"));

TokenService: The service class containing logic to generate and validate a token using the JWT library.

package; import;
import java.util.Base64;
import java.util.Date; import javax.annotation.PostConstruct; import;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.log4j.Log4j2;
/** * * @author satish sharma * * Help with JWT related operations * */
public class TokenService { @Value("${jwt.secret}")
private String secretKey;
private long tokenValidityInMinutes;
private UserDetailsService customUserDetailsService; @PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
} public String createToken(User usr) {
Date validity = new Date(new Date().getTime() + (tokenValidityInMinutes * 10000));
return Jwts.builder().setSubject(usr.getLoginId()).claim("ROLE", usr.getRole())
.claim("NAME", usr.getFirstName()).signWith(SignatureAlgorithm.HS512, secretKey).setExpiration(validity)
} public Authentication getAuthentication(String token) {
UserDetails userDetails = this.customUserDetailsService.loadUserByUsername(getLoginId(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
} public String getLoginId(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
} public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return (claims.getBody().getExpiration().before(new Date())) ? false : true;
} catch (JwtException | IllegalArgumentException e) {
return false;
} public String getTokenValue(String authHeaderValue) {
return (StringUtils.hasText(authHeaderValue) && authHeaderValue.startsWith("Bearer ")) ? authHeaderValue.substring(7, authHeaderValue.length()): null;

UserPrincipal: The principal object created from our User entity.

package; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import;
import; /** * * @author satish sharma * * UserPrincipal to be available in SecurityContext */
public class UserPrincipal implements UserDetails {
private final User user; public UserPrincipal(User user) {
this.user = user;
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(this.user.getRole()));
return authorities;
public String getPassword() {
return user.getPassword();
public String getUsername() {
return user.getFirstName();
public boolean isAccountNonExpired() {
return !user.isActive();
public boolean isAccountNonLocked() {
return !user.isActive();
} @Override
public boolean isCredentialsNonExpired() {
return !user.isActive();
} @Override
public boolean isEnabled() {
return user.isActive();

Security Config: The @Configuration to stitch all our logic and plug it with Spring Security. This will also contain our security rules to be executed on requests.

package; import static;
import static;
import static; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
/** * @author satish sharma * * Configure SpringSecurity to use our logic for Authorization. * Also confifure the API accessible to roles * */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired TokenService jwtTokenProvider; @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } // Configure resources to be accessible to all H2 console and swagger ui @Override public void configure(WebSecurity web) throws Exception { web.ignoring() .antMatchers(HttpMethod.OPTIONS, "/**") .antMatchers("/bower_components/**") .antMatchers("/swagger-ui.html") .antMatchers("/swagger-resources/**") .antMatchers("/v2/api-docs") .antMatchers("/webjars/**") .antMatchers("/") .antMatchers("/db-console/**.css") .antMatchers("/db-console/**") ; } /** * Configure security based on roles */ @Override protected void configure(HttpSecurity http) throws Exception { http .httpBasic().disable() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/authorize").permitAll()//allowed to all .antMatchers("/product/**").hasRole(ROLE_STORE_MANAGER)//allowed only to MANAGER .antMatchers("/mgmt/**").hasAnyRole(ROLE_ADMIN)//allowed only to ADMIN .antMatchers("/user/**").hasAnyRole(ROLE_STORE_USER) //allowed only to USER .anyRequest().authenticated()// Rest of the request must be authenticated .and() .apply(new JwtConfigurer(jwtTokenProvider)) //user this to configure filters ; }

Let’s Run Some Tests

  1. Try to view products without authorization.Trying to view product without authorizationTrying to view product without authorization
  2. Authenticate: Let us authenticate using with user manager which has MANAGER role.Logging in as a managerLogging in as a manager
  3. Get all products: Try to view all products.Viewing all products as managerViewing all products as manager
  4. Negative Test: Try to access an API which manager does not have access. In our case, we want to view all stores that are in the “mgmt” bucket and only accessible to the ADMIN role. Here, we get an HTTP 403 (Access Denied) response.Image title

It is clear from the above test that we were able to achieve the RBAC what we wanted. At first, this looks complicated, but once you understand the components and how those roles allow/deny access to the APIs, it becomes very easy to implement this.

You can find the full code at this Github repo. Please feel free to share your valuable views, suggestions, and questions (if any). I would be happy to help!

Further Reading

This UrIoTNews article is syndicated fromDzone