Spring Boot Microservices requires authentication of users, and one way is through JSON Web Token (JWT). JWT is an open standard (RFC 7519) that defines a compact mechanism for securely transmitting information between parties.
In this post, I will explain how to implement JWT authentication in Spring Microservices.
JWT Token Overview
JWT is of relatively small size. Therefore it can be sent through a URL, :
Through a POST parameter, or
Inside an HTTP header.
However, take note that sending tokens through HTTP headers is the most common approach.
A JWT token contains all the required information about an entity, which can be a user or a service.
This figure shows a typical use case of JWT authentication.
The Example Application
For this post, I have created two services:
AuthenticatorService
AuthenticatorService: Responsible for authenticating user name and password. On successful authentication, this service generates and returns a JWT token.
BlogService
BlogService : A protected service. This service contains a filter that verifies the JWT token that a client sends. On successful verification, this service returns a secured message.
This figure shows the interaction between the client and the preceding services.
The AuthenticatorService
Maven POM
To generate JWT token, you need the
jjwt
jjwt artifact as a dependency of your module.
The following code snippet shows the Maven POM dependency.
public User getUserByNameAndPassword(String name, String password)throws UserNotFoundException {
User user = userRepository.findByUserNameAndPassword(name, password);
if(user == null){
thrownewUserNotFoundException("Invalid id and password");
}
return user;
}
}
package com.stackroute.AuthenticatorService.service;
import com.stackroute.AuthenticatorService.exception.UserNotFoundException;
import com.stackroute.AuthenticatorService.model.User;
import com.stackroute.AuthenticatorService.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository){
this.userRepository=userRepository;
}
@Override
public void saveUser(User user) {
userRepository.save(user);
}
@Override
public User getUserByNameAndPassword(String name, String password) throws UserNotFoundException {
User user = userRepository.findByUserNameAndPassword(name, password);
if(user == null){
throw new UserNotFoundException("Invalid id and password");
}
return user;
}
}
package com.stackroute.AuthenticatorService.service;
import com.stackroute.AuthenticatorService.exception.UserNotFoundException;
import com.stackroute.AuthenticatorService.model.User;
import com.stackroute.AuthenticatorService.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository){
this.userRepository=userRepository;
}
@Override
public void saveUser(User user) {
userRepository.save(user);
}
@Override
public User getUserByNameAndPassword(String name, String password) throws UserNotFoundException {
User user = userRepository.findByUserNameAndPassword(name, password);
if(user == null){
throw new UserNotFoundException("Invalid id and password");
}
return user;
}
}
The Controller
The controller has two endpoints: /register and /login. The first one is responsible to save a new user. The latter endpoint authenticates a user. On successful authentication, the latter endpoint returns a JWT token.
returnnew ResponseEntity<>("Hai this is a normal message..", HttpStatus.OK);
}
@GetMapping("/restricted")
public ResponseEntity<?>getRestrictedMessage(){
returnnew ResponseEntity<>("This is a restricted message", HttpStatus.OK);
}
}
package guru.springframework.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api/v1/blog")
public class BlogController {
@GetMapping("/unrestricted")
public ResponseEntity<?> getMessage() {
return new ResponseEntity<>("Hai this is a normal message..", HttpStatus.OK);
}
@GetMapping("/restricted")
public ResponseEntity<?> getRestrictedMessage() {
return new ResponseEntity<>("This is a restricted message", HttpStatus.OK);
}
}
package guru.springframework.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api/v1/blog")
public class BlogController {
@GetMapping("/unrestricted")
public ResponseEntity<?> getMessage() {
return new ResponseEntity<>("Hai this is a normal message..", HttpStatus.OK);
}
@GetMapping("/restricted")
public ResponseEntity<?> getRestrictedMessage() {
return new ResponseEntity<>("This is a restricted message", HttpStatus.OK);
}
}
The Configuration
The configuration is responsible for registering the authentication filter.
// provide endpoints which needs to be restricted.
// All Endpoints would be restricted if unspecified
filter.addUrlPatterns("/api/v1/blog/restricted");
return filter;
}
}
package guru.springframework.config;
import guru.springframework.filter.JwtFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean jwtFilter() {
FilterRegistrationBean filter= new FilterRegistrationBean();
filter.setFilter(new JwtFilter());
// provide endpoints which needs to be restricted.
// All Endpoints would be restricted if unspecified
filter.addUrlPatterns("/api/v1/blog/restricted");
return filter;
}
}
package guru.springframework.config;
import guru.springframework.filter.JwtFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean jwtFilter() {
FilterRegistrationBean filter= new FilterRegistrationBean();
filter.setFilter(new JwtFilter());
// provide endpoints which needs to be restricted.
// All Endpoints would be restricted if unspecified
filter.addUrlPatterns("/api/v1/blog/restricted");
return filter;
}
}
Test the JWT Token in the Filter
The filter is responsible for verifying the JWT token. The filter class extends the
GenericFilter
GenericFilter class and overrides the
doFiter()
doFiter() method. This method accepts as input parameter:
Alice and pass123 as credentials in the request body.
Notice the JSON web token returned by the Authenticator service.
Next, start
BlogService
BlogService and make a
GET
GET request to the restricted endpoint. Ensure, you add the token in the
Authorization
Authorization header.
The value Authorization header value must be
Bearer
Bearer, separated by a space and followed by the token Once you send the request, you will receive a restricted response.
Summary
I have seen lots of developers verifying JWT tokens in their services. Microservices have a pattern called Gateway Offloading. This pattern enables each microservice to offload shared service functionality, such as the use of SSL certificates, Token verification, to an API gateway.
In addition, Microservices Gateways can tend to become Single Point of Failure. However, with Technology evolving fast and moving to the cloud. There are hardware load balancers, software load balancers, and cloud load balancers. And all of these have redundancy and various failover schemes to prevent a single point of failure.
You can access the AuthenicatorService here at Github, For the Blogservice, access here at Githuub.
6 comments on “JWT Token Authentication in Spring Boot Microservices”
Yasir Raza
October 13, 2022 at 11:38 am
I have a user service which contains login endpoint. The login endpoint generates jwt token. I want to authenticate the jwt token using api gateway. Also want to authorize role based authentication in api gateway filter. I am unable to implement spring security in api gateway.
Thanks for the article. It gives a good overview, but the code contributor did not write the code cleanly. Exceptions thrown from controllers. return new ResponseEntity(jwtGenerator.generateToken(user), HttpStatus.OK); ignoring User userData = userService.getUserByNameAndPassword(user.getUserName(), …
Yasir Raza
I have a user service which contains login endpoint. The login endpoint generates jwt token. I want to authenticate the jwt token using api gateway. Also want to authorize role based authentication in api gateway filter. I am unable to implement spring security in api gateway.
Hoang Trung
If I have more service, i must config jwt filter for them?
Arvind
what is this ? if you pass the auth token or not it will give you the “This is a restricted message” msg??
there is no matter you are passing or not
Manish
I like your explanation, Thank You so much Guru
Rahul
This example does not suits for microservice architecture.
Kris
Thanks for the article.
It gives a good overview, but the code contributor did not write the code cleanly. Exceptions thrown from controllers. return new ResponseEntity(jwtGenerator.generateToken(user), HttpStatus.OK); ignoring User userData = userService.getUserByNameAndPassword(user.getUserName(), …
Lots of code smells