Basic Authentication Application(JWT and Role based Authentication) — Spring Boot, MySQL

Requirements

  1. Basic Spring boot knowledge
  2. MySQL
  3. Hibernate
  4. IntelliJ

Lets proceed with EXAMPLE

Let’s say Spanish football league LaLiga wants to give user a streaming platform which will stream all the matches on their website. They want to give users functionality to chat as they want to collect data when the open stadiums to people they can convey fans about it.

LaLiga already has made a streaming platform and chat window, but now they want only registered users to chat, but unregistered user can stream the match. They also want to make some people moderators, who can perform some actions like block a user, change title and they want some users who could start the stream.

Now, LaLiga admin gives you a contract to make a service on top of the their streaming platform so that only authenticated users can perform some actions.

Let’s Get Started

In the Fig 1. we see two concepts Authentications and Authorizations. First we talk about authentication, it refers to the process of validating the user, this will tell if the user exists with our system or not. On the other hand, authorization is process to validate if the existed user has the permission to make changes that he has requested for.

Authentication with JWT

There are many authentication mechanism by which we can identify the user, JWT(Json Web Token) works best when we want to authenticate user without compromising their credentials.

Checkout more at https://jwt.io/introduction/ https://dzone.com/articles/spring-boot-security-json-web-tokenjwt-hello-world

Authorization

We will use role based authorization to check if the request coming from user is authorised to perform the action or not.

Jumping To Code

Project Structure

Entities

  1. UserEntity — id, userName(unique), email(unique), password, set of roles
  2. Role — id, name

A mapping table(user_roles) will be made to store all the user(id) mapped to role(id).

JWT

  1. We will add AuthTokenFilter.java to add a filter to requests that we want JWT authentication, internally the code will be overridden by doFilterInternal code.
public class AuthTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;

@Autowired
private UserDetailsServiceImpl userDetailsService;

private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);

UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: ", e);
}

filterChain.doFilter(request, response);
}

private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");

if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}

return null;
}
}

a. From token we are getting username and password(encrypted by key).
b. Then by that we make UserDetails which is then user to make authentication token to pass to security context for verification.

2. We will add JwtUtils which will contain some helper functions(comments mention below the use of the imp statements)

@Component
public class JwtUtils{
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
// this is hashing key(keep it secret)
private final String jwtSecret = "mySecret";
// timeout of token, the token will not work after this much millis
private final int jwtExpirationMs = 1000 * 60 * 60 * 10;

public String generateJwtToken(Authentication authentication) {

UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
// building and returning the processed jwt token with username and // password
return Jwts.builder()
.setSubject((userPrincipal.getUsername()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}

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

public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty: {}", e.getMessage());
}

return false;
}

3. Making UserDetailsImpl.java which will be implementation of UserDetails which contains the info about the user. We will just add a function to make UserDetails from UserEntity.

public static UserDetailsImpl build(UserEntity user) {
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName().name()))
.collect(Collectors.toList());

return new UserDetailsImpl(
user.getId(),
user.getUserName(),
user.getEmail(),
user.getPassword(),
authorities);
}

4. We have to override the loadUserByUsername function from spring boot UserDetailsService. So, we make the UserDetailsServiceImpl.java which will get the user details from our database.

@Override
@Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity user = userRepository.findFirstByUserName(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));

return UserDetailsImpl.build(user);
}

5. Now we have to configure WebSecurityConfig which is to add all the filters and encoder we made.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;

@Autowired
private AuthEntryPointJwt unauthorizedHandler;

@Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
// password encoder will be required in spring to run the
// authentication
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

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

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// /v1/auth contains login and signup and jwt doesn't make sense on // them and /api/test/** will contain our apis which requires jwt
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/v1/auth/**").permitAll()
.antMatchers("/api/test/**").permitAll()
.anyRequest().authenticated();
// for each api the filter has been added with above http request
// conditions
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}

6. Now create a login and signup apis with requirements needed(code available in github repository) and configure mysql accordingly.

#For roles please run these commands
INSERT INTO roles(name) VALUES('ROLE_USER');
INSERT INTO roles(name) VALUES('ROLE_MODERATOR');
INSERT INTO roles(name) VALUES('ROLE_ADMIN');

Hope you like the article, thank you very much for reading this.

REFERENCES

https://spring.io/guides/tutorials/rest/
https://blog.restcase.com/4-most-used-rest-api-authentication-methods/

https://spring.io/guides/tutorials/spring-boot-oauth2/

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/bcrypt/BCrypt.html

https://www.okta.com/identity-101/what-is-token-based-authentication/#:~:text=Token%2Dbased%20authentication%20is%20a,receive%20a%20unique%20access%20token.&text=Auth%20tokens%20work%20like%20a,app%2C%20the%20token%20is%20invalidated.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Mehul

Mehul

Software Development Engineer- Glance(Inmobi)