This is a continuation of Authentication between microservices - Part I.
Please read part I to know the idea behind this implementation.
We will use spring-boot to make two services and see how authentication can be implemented.
Let's build caller service A
- First, configure the keys in
application.yml
that will be used to create JWT token
service:
jwtAlgorithmKey: sampleJwtAlgorithmKey #will be used as key in JWT Algo
serviceBSecretKey: serviceB-SecretKey-To-Be-Provided-By-ServiceA #this is the key that called service (`service B`) will compare on decoding token
spring:
application:
name: service-a #application-name
- Make a config class which will generate jwt tokens at the time of service startup using the values provided in
application.yml
file
@Configuration
@ConfigurationProperties(prefix = "service")
public class ServiceAuthConfig {
@Setter @Getter private String jwtAlgorithmKey;
@Setter @Getter private String serviceBSecretKey;
@Value("${spring.application.name}")
private String applicationName;
@Getter private String serviceBAuthToken;
/*
Generate auth token which will be passed in header when calling
API of service B
*/
@PostConstruct
public void load() {
serviceBAuthToken = createJwtToken(serviceBSecretKey);
}
/*
Create JWT Token with the algorithm key (common for both the called and caller service)
Service Secret key is to be generate by caller service and then shared
and stored in called service yml
*/
private String createJwtToken(String serviceSecretKey) {
return JWT.create()
.withIssuer(applicationName)
.withSubject(serviceSecretKey)
.sign(Algorithm.HMAC512(jwtAlgorithmKey));
}
}
- Make a feign client to invoke service B's apis
@FeignClient(name = "serviceBClient", url = "http://127.0.0.1:8081/service-b")
public interface ServiceBFeignClient {
@GetMapping(value = "/get-greetings")
@ApiOperation("Get Greetings from service B")
String getGreetings(@RequestHeader("SERVICE-AUTH-TOKEN") String serviceAuthToken);
}
- Make an api which will call service B's getGreetings API
@RestController
@RequestMapping(value = "/service-a")
@Slf4j
public class ServiceAController {
@Autowired private ServiceAuthConfig serviceAuthConfig;
@Autowired private ServiceBFeignClient serviceBFeignClient;
@GetMapping(value = "/get-greeting-from-service-b")
public ResponseEntity getGreetingFromServiceB() {
try {
String serviceBGreetingResponse =
serviceBFeignClient.getGreetings(serviceAuthConfig.getServiceBAuthToken()); // passing jwt auth token for service B
return ResponseEntity.ok(serviceBGreetingResponse);
} catch (FeignException exception) {
/*
Extract the error thrown from service B
and if the error is `INVALID_TOKEN`, then return 401
*/
String error = new String(exception.responseBody().get().array());
log.error("Error {}", error);
if ("INVALID_TOKEN".equals(error)) {
return ResponseEntity.status(401).body(error);
}
throw exception;
}
}
}
Now, Let's create a called service B
- Configure the jwt algo key and the secret key provided by caller service A in
application.yml
service:
jwtAlgorithmKey: sampleJwtAlgorithmKey
registeredSecretKeys:
- serviceB-SecretKey-To-Be-Provided-By-ServiceA
spring:
application:
name: service-b
- Expose an api which will generate a greeting
@RestController
@RequestMapping(value = "/service-b")
public class ServiceBController {
@GetMapping(value = "/get-greetings")
public ResponseEntity sayGreeting() {
return ResponseEntity.ok("Hello There from Service B!!!");
}
}
- Create a config component to auto read the
application.yml
properties
@Configuration
@ConfigurationProperties(prefix = "service")
@Slf4j
@Setter
@Getter
public class ServiceAuthConfig {
private String jwtAlgorithmKey;
private List<String> registeredSecretKeys;
}
- Finally, create a ServiceAuthFilter which will read the token from
SERVICE-AUTH-TOKEN
header, decode and validate it against the keys stored in config
@Component
@Slf4j
public class ServiceAuthFilter implements Filter {
@Autowired private ServiceAuthConfig serviceAuthConfig;
@Override
public void doFilter(
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = ((HttpServletRequest) servletRequest);
String serviceAuthToken = httpServletRequest.getHeader("SERVICE-AUTH-TOKEN");
boolean isTokenValid = validateJwtToken(serviceAuthToken);
if (!isTokenValid) {
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
httpServletResponse.setStatus(401);
httpServletResponse.getWriter().write("INVALID_TOKEN");
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
/*
Validate the token by comparing the secret key in it against list
of secret keys registered in service B
*/
private boolean validateJwtToken(String jwtToken) {
if (StringUtils.isEmpty(jwtToken)) {
return false;
}
DecodedJWT decodedJWT =
JWT.require(Algorithm.HMAC512(serviceAuthConfig.getJwtAlgorithmKey()))
.build()
.verify(jwtToken);
log.info("Request Initiated from {}", decodedJWT.getIssuer());
return serviceAuthConfig.getRegisteredSecretKeys().contains(decodedJWT.getSubject());
}
}
Now, how to validate what has been built?
Build and start both,
service A
andservice B
.Invoke
get-greeting-from-service-b
api from caller service A and see the output by keeping the secret keys same in both services and then by changing it in one of them.
More on implementation 👇
s2agrahari / authentication-between-microservices
authentication-between-microservices
authentication-between-microservices
authentication-between-microservices
🍻 If you enjoyed this story, please click the ❤️ button and share it to help others find it! Feel free to leave a comment below.
Top comments (0)