Keyholesoftware.com/2016/06/20/…

By THOMAS KENDALL

From stackGC

Microservices security is an important part of the architecture. Specifically, the authentication and authorization mode.

There are several ways to handle microservices authentication and authorization, but this article only covers the use of JSON Web Tokens.

JSON Web Token

A JSON Web Token (JWT) is essentially a separate authentication Token that can contain information such as the user identity, user role, and permissions, as well as any other information you can store. Anyone can easily read and parse, and use the key to verify authenticity. For a brief introduction to JSON Web tokens, see this page (jwt. IO /introductio…

One advantage of microservices using JSON Web tokens is that it can be configured to include any permissions the user has. This means that each service does not need to interact with the authorization service to authorize users.

Another advantage of JWT is that they are serializable, short enough to be placed in the request header.

The working principle of

JWT’s workflow is fairly simple. The first request is a POST to an unauthenticated endpoint with a username and password.

After successful authentication, the response will contain JWT. All subsequent requests come with an HTTP header that contains the JWT token: Authorization: XXXXX.YYyyy.zzzzz.

All requests between services go through this header so that other services can apply authorization.

Start coding

The first thing we need to do is figure out how to generate JWT. Fortunately, we’re not the first to step into the hole, and there are several libraries of JWT classes available.

I chose Java JWT (github.com/jwtk/jjwt). …

public class JsonWebTokenUtility {
 
    private SignatureAlgorithm signatureAlgorithm;
    private Key secretKey;
 
    public JsonWebTokenUtility(a) {
 
        // This is not a safe practice
        // For simplicity, I store a static key here
        // In fact, in a microservice environment, keys are held by the configuration server
        signatureAlgorithm = SignatureAlgorithm.HS512;
        String encodedKey = "L7A/6zARSkK1j7Vd5SDD9pSSqZlqF7mAhiOgRbgv9Smce6tf4cJnvKOjtKPxNNnWQj+2lQEScm3XIUjhW+YVZg==";
        secretKey = deserializeKey(encodedKey);
    }
 
    public String createJsonWebToken(AuthTokenDetailsDTO authTokenDetailsDTO) {
        String token = Jwts.builder().setSubject(authTokenDetailsDTO.userId).claim("email", authTokenDetailsDTO.email)
                .claim("roles", authTokenDetailsDTO.roleNames).setExpiration(authTokenDetailsDTO.expirationDate)
                .signWith(getSignatureAlgorithm(), getSecretKey()).compact();
        return token;
    }
 
    private Key deserializeKey(String encodedKey) {
        byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
        Key key = new SecretKeySpec(decodedKey, getSignatureAlgorithm().getJcaName());
        return key;
    }
 
    private Key getSecretKey(a) {
        return secretKey;
    }
 
    public SignatureAlgorithm getSignatureAlgorithm(a) {
        return signatureAlgorithm;
    }
 
    public AuthTokenDetailsDTO parseAndValidate(String token) {
        AuthTokenDetailsDTO authTokenDetailsDTO = null;
        try {
            Claims claims = Jwts.parser().setSigningKey(getSecretKey()).parseClaimsJws(token).getBody();
            String userId = claims.getSubject();
            String email = (String) claims.get("email");
            List roleNames = (List) claims.get("roles");
            Date expirationDate = claims.getExpiration();
 
            authTokenDetailsDTO = new AuthTokenDetailsDTO();
            authTokenDetailsDTO.userId = userId;
            authTokenDetailsDTO.email = email;
            authTokenDetailsDTO.roleNames = roleNames;
            authTokenDetailsDTO.expirationDate = expirationDate;
        } catch (JwtException ex) {
            System.out.println(ex);
        }
        return authTokenDetailsDTO;
    }
 
    private String serializeKey(Key key) {
        String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded());
        returnencodedKey; }}Copy the code

Now that we have this utility class, we need to configure Spring Security in each microservice.

To do this, we need to customize a validation filter to read the request header if it exists. Spring has a certification filter RequestHeaderAuthenticationFilter, we can inherit it.

public class JsonWebTokenAuthenticationFilter extends RequestHeaderAuthenticationFilter {
 
    public JsonWebTokenAuthenticationFilter(a) {
        // Don't throw exceptions if the header is missing
        this.setExceptionIfHeaderMissing(false);
 
        // This is the request header it will look for
        this.setPrincipalRequestHeader("Authorization");
    }
 
    @Override
    @Autowired
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager); }}Copy the code

At this point, the head is in the form of PreAuthenticatedAuthenticationToken into Spring Authentication object.

We now need an AuthenticationProvider to read the token, authenticate it, and convert it to our own custom Authentication object.

public class JsonWebTokenAuthenticationProvider implements AuthenticationProvider {
 
    private JsonWebTokenUtility tokenService = new JsonWebTokenUtility();
 
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Authentication authenticatedUser = null;
        // Only process the PreAuthenticatedAuthenticationToken
        if(authentication.getClass().isAssignableFrom(PreAuthenticatedAuthenticationToken.class) && authentication.getPrincipal() ! =null) {
            String tokenHeader = (String) authentication.getPrincipal();
            UserDetails userDetails = parseToken(tokenHeader);
            if(userDetails ! =null) {
                authenticatedUser = newJsonWebTokenAuthentication(userDetails, tokenHeader); }}else {
            // It is already a JsonWebTokenAuthentication
            authenticatedUser = authentication;
        }
        return authenticatedUser;
    }
 
    private UserDetails parseToken(String tokenHeader) {
 
        UserDetails principal = null;
        AuthTokenDetailsDTO authTokenDetails = tokenService.parseAndValidate(tokenHeader);
 
        if(authTokenDetails ! =null) {
            List<GrantedAuthority> authorities = authTokenDetails.roleNames.stream()
                    .map(roleName -> new SimpleGrantedAuthority(roleName)).collect(Collectors.toList());
            principal = new User(authTokenDetails.email, "", authorities);
        }
 
        return principal;
    }
 
    @Override
    public boolean supports(Class
        authentication) {
        returnauthentication.isAssignableFrom(PreAuthenticatedAuthenticationToken.class) || authentication.isAssignableFrom(JsonWebTokenAuthentication.class); }}Copy the code

With these components, we are ready to use JWT in Spring Security. When communicating between services, we need to pass JWT.

I used a Feign client with JWT as a parameter.

@FeignClient("user-management-service")
public interface UserManagementServiceAPI {
 
    @RequestMapping(value = "/authenticate", method = RequestMethod.POST)
    AuthTokenDTO authenticateUser(@RequestBody AuthenticationDTO authenticationDTO);
 
    @RequestMapping(method = RequestMethod.POST, value = "/roles")
    RoleDTO createRole(@RequestHeader("Authorization") String authorizationToken, @RequestBody RoleDTO roleDTO);
 
    @RequestMapping(method = RequestMethod.POST, value = "/users")
    UserDTO createUser(@RequestHeader("Authorization") String authorizationToken, @RequestBody UserDTO userDTO);
 
    @RequestMapping(method = RequestMethod.DELETE, value = "/roles/{id}")
    void deleteRole(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id);
 
    @RequestMapping(method = RequestMethod.DELETE, value = "/users/{id}")
    void deleteUser(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id);
 
    @RequestMapping(method = RequestMethod.GET, value = "/roles")
    Collection<RoleDTO> findAllRoles(@RequestHeader("Authorization") String authorizationToken);
 
    @RequestMapping(method = RequestMethod.GET, value = "/users")
    Collection<UserDTO> findAllUsers(@RequestHeader("Authorization") String authorizationToken);
 
    @RequestMapping(method = RequestMethod.GET, value = "/roles/{id}", produces = "application/json", consumes = "application/json")
    RoleDTO findRoleById(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id);
 
    @RequestMapping(method = RequestMethod.GET, value = "/users/{id}", produces = "application/json", consumes = "application/json")
    UserDTO findUserById(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id);
 
    @RequestMapping(method = RequestMethod.GET, value = "/users/{id}/roles")
    Collection<RoleDTO> findUserRoles(@RequestHeader("Authorization") String authorizationToken,
            @PathVariable("id") int id);
 
    @RequestMapping(method = RequestMethod.PUT, value = "/roles/{id}")
    void updateRole(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id,
            @RequestBody RoleDTO roleDTO);
 
    @RequestMapping(method = RequestMethod.PUT, value = "/users/{id}")
    void updateUser(@RequestHeader("Authorization") String authorizationToken, @PathVariable("id") int id,
            @RequestBody UserDTO userDTO);
}
Copy the code

To pass the JWT, I fetch it in the controller’s Spring Security:

private String getAuthorizationToken(a) {
    String token = null;
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if(authentication ! =null && authentication.getClass().isAssignableFrom(JsonWebTokenAuthentication.class)) {
        JsonWebTokenAuthentication jwtAuthentication = (JsonWebTokenAuthentication) authentication;
        token = jwtAuthentication.getJsonWebToken();
    }
    return token;
}
Copy the code

JWT fits well into a distributed microservice environment and provides a great deal of functionality. If you are looking to design a security architecture for your next microservice project, consider using JSON Web Tokens.