Implementing Microservices with JSON Web Tokens and Spring

 JSON Web Token (JWT) is a self-authenticating token that can contain information such as user ID, role, and user authority, and can be easily parsed by anyone and verified using a secure key. For details, please refer to here .

Microservices combined with JWT can avoid session overhead and facilitate distributed systems across multiple machines, so that each service does not need to call a special authorization service to confirm the user's permission to operate the service. Another advantage is that the JWT is small enough to be serialized and attached to the request header.

The first request with username and password submits a POST to an unprotected login authorization REST endpoint. Once the user and password are authorized, the response will contain a JWT, and subsequent requests will include this JWT token in the HTTP header. in the form like: Authorization: xxxxx.yyyyy.zzzzz

Any service-to-service request will pass this request header along the way, so that authorization information can be applied to the services passed along the way, and these services can check this JWT to decide whether to accept access.

The following uses the Spring cloud code as an example, using the Java implementation of JWT:  Java JWT

public class JsonWebTokenUtility {

 

    private SignatureAlgorithm signatureAlgorithm;

    private Key secretKey;

 

    public JsonWebTokenUtility() {

 

       // This is not really safe practice

       // For simplicity, we store a static key here,

       // In a real microservice environment, this key will be kept in the config 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() {

       return secretKey;

    }

 

    public SignatureAlgorithm getSignatureAlgorithm() {

       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());

       return encodedKey;

    }

 

}

现在有了这个工具类,我就可以在每个微服务中设置Spring Security。

现在我们需要一个定制授权过滤器,将能读取请求头部信息,在Spring中已经有一个这样的授权Filter称为:RequestHeaderAuthenticationFilter,我们只要扩展继承即可:

public class JsonWebTokenAuthenticationFilter extends RequestHeaderAuthenticationFilter {

 

    public JsonWebTokenAuthenticationFilter() {

       // 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);

    }

}

在这里,头部信息将被转换为Spring Authentication 对象,名称为PreAuthenticatedAuthenticationToken

我们需要一个授权提供者读取这个记号,然偶验证它,然后转换为我们自己的定制授权对象:

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 =

           new JsonWebTokenAuthentication(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) {

       return

      authentication.isAssignableFrom(

        PreAuthenticatedAuthenticationToken.class)||

      authentication.isAssignableFrom(

        JsonWebTokenAuthentication.class);

    }

 

}

使用这些组件,我们现在就可以有使用JWT的标准Spring Securtiy安全机制,当我们进行服务对服务调用时,我们需要一路传输JWT。

这里虚构一个客户端,将JWT作为参数传递:

@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);
}

为了一路传递JWT,在我们自己的控制器中模仿Spring Security实现如下:

private String getAuthorizationToken() {

    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;

}

正如你看到,在一个分布式微服务环境中,JWT提供了灵活的授权。

项目源码:Github

JSON Web Tokens(JWT)教程

Spring专题

微服务专题

 

http://www.jdon.com/dl/best/json-web-tokens-spring-cloud-microservices.html

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326723418&siteId=291194637