[Перевод] Микросервисы JWT и Spring Cloud

Spring Boot Микросервисы Архитектура Spring Java EE Безопасность

keyholesoftware.com/2016/06/20/…

Автор: ТОМАС КЕНДАЛЛ

Перепечатано с общедоступного номера: stackgc

Безопасность микросервисов — одна из важных частей архитектуры. В частности, режим аутентификации и авторизации.

Существует несколько вариантов проверки подлинности и авторизации микросервиса, но в этой статье рассматривается только использование веб-токенов JSON.

JSON Web Token

Веб-токен JSON (JWT) — это, по сути, автономный токен проверки подлинности, который может содержать такую ​​информацию, как идентификатор пользователя, роли и разрешения пользователей, а также любую другую информацию, которую вы можете хранить. Любой может легко прочитать и проанализировать, а также использовать ключ для проверки подлинности. Для краткого ознакомления с веб-токенами JSON посетите эту страницу (Audition.IO/представить IO…

Одним из преимуществ использования веб-токенов JSON для микросервисов является то, что мы можем настроить его для включения любых разрешений, которые есть у пользователя. Это означает, что каждой службе не нужно взаимодействовать со службой авторизации, чтобы авторизовать пользователя.

Еще одним преимуществом JWT является то, что они сериализуемы, достаточно короткие, чтобы поместиться в заголовках запросов.

Принцип работы

Рабочий процесс JWT довольно прост. Первый запрос — это POST к неаутентифицированной конечной точке с именем пользователя и паролем.

После успешной аутентификации ответ будет содержать JWT. Все последующие запросы приходят с заголовком HTTP, содержащим токен JWT:Authorization: xxxxx.yyyyy.zzzzz.

Все запросы между службами передают этот заголовок, чтобы другие службы могли применять авторизацию.

начать кодирование

Первое, что нам нужно сделать, это выяснить, как генерировать JWT. К счастью, мы не первым наступили, есть несколько готовых библиотек JWT.

Я выбрал Java JWT (GitHub.com/чрезвычайно болезненно/проблемное решение). …

public class JsonWebTokenUtility {
 
    private SignatureAlgorithm signatureAlgorithm;
    private Key secretKey;
 
    public JsonWebTokenUtility() {
 
        // 这不是一个安全的实践
        // 为了简化,我存储了一个静态的 key 在这里
        // 实际上,在微服务环境中,key 是由配置服务器持有的
        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 имеет фильтр аутентификации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);
    }
}

В это время голова ужеPreAuthenticatedAuthenticationTokenФорма преобразуется в объект Spring Authentication.

Теперь нам нужен AuthenticationProvider для чтения токена, аутентификации и преобразования его в наш собственный объект Authentication.

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 Security. При межсервисном взаимодействии нам необходимо передать JWT.

Я использую клиент Feign, который принимает 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 хорошо вписывается в среду распределенных микросервисов и предоставляет множество функций. Если вы думаете о разработке архитектуры безопасности для своего следующего проекта микросервисов, рассмотрите возможность использования JSON Web Token.