Как JWT ретранслирует токены при вызове OpenFeign

Java задняя часть Spring Cloud
Как JWT ретранслирует токены при вызове OpenFeign

Мало знаний, большой вызов! Эта статья участвует в "    Необходимые знания для программистов    «Творческая деятельность

Эта статья также участвует "Проект "Звезда раскопок""    , чтобы выиграть творческие подарочные пакеты и бросить вызов творческим поощрениям

Оставьте сообщение ниже, Наггетсы отправят ихГде плохой текст в сообщении в блоге? Добро пожаловать в область комментариев, чтобы указатьNuggets официально раздают 100 периферийных устройств

При использовании Feign в разработке микросервиса Spring Cloud необходимо решить проблему ретрансляции токенов, поскольку только ретрансляция токенов может обеспечить передачу информации об аутентификации пользователя в цепочке вызовов и реализовать неявную передачу информации об аутентификации пользователя в сервисе А через Притворяться, служить Б. Сегодня я расскажу, как реализовать ретрансляцию токенов в Feign.

жетон реле

Token Relay — это более формальный термин, Проще говоря, Token передается между службами, чтобы гарантировать, что сервер ресурсов может правильно аутентифицировать ресурс вызывающей стороны. Клиент получает доступ к службе A с помощью JWT через шлюз, а служба A проверяет и анализирует JWT.Когда служба A вызывает службу B, службе B также может потребоваться проверить и проанализировать JWT. Например, чтобы запросить мой заказ и информацию о логистике моего заказа, служба заказов может получить мой заказ через JWT.userId, если нет необходимости явно помещать токен ретрансляцииuserIdПри переходе к информационной службе логистики, а иногда и к нижестоящим службам возникают проблемы с разрешениями, поэтому ретрансляция токенов очень необходима.

Нельзя ли автоматически передать токен в Feign?

Если мы используем токен для доступа к службе A, служба A определенно сможет пройти аутентификацию, но служба A вызовет службу B через Feign. В настоящее время токен A не может быть напрямую передан службе B.

Вот краткое объяснение причин: Обращения между сервисами осуществляются через интерфейс Feign. На стороне вызывающей стороны мы обычно пишем интерфейс Feign, подобный следующему:

@FeignClient(name = "foo-service",fallback = FooClient.Fallback.class)
public interface FooClient {
    @GetMapping("/foo/bar")
    Rest<Map<String, String>> bar();

    @Component
    class Fallback implements FooClient {
        @Override
        public Rest<Map<String, String>> bar() {
            return RestBody.fallback();
        }
    }
}

Когда мы вызываем интерфейс Feign, прокси-класс интерфейса будет сгенерирован динамическим прокси-сервером для вызова. Если мы не включим автоматический выключатель, мы можем предоставить Spring SecuritySecurityContextОбъект проверки подлинности, извлеченный на сервер ресурсов из объектаJwtAuthenticationToken, который содержит токен JWT, а затем мы можем реализовать интерфейс перехватчика Feign с помощьюRequestInterceptorПоместите токен в заголовок запроса, псевдокод выглядит следующим образом:

/**
 * 需要注入Spring IoC
 **/
static class BearerTokenRequestInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate template) {
            final String authorization = HttpHeaders.AUTHORIZATION;
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            
            if (authentication instanceof JwtAuthenticationToken){
                JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
                String tokenValue = jwtAuthenticationToken.getToken().getTokenValue();
                template.header(authorization,"Bearer "+tokenValue);
            }
        }
    }

Если мы не включим фьюз, то это не большая проблема.Чтобы предотвратить фьюз службы цепочки вызовов Avalanche, включить его в принципе никак нельзя. С этого времениSecurityContextHolderне могу получитьAuthentication. Потому что в это время вызов Feign выполняется в подпотоке, открытом в вызывающем потоке вызывающей стороны. Поскольку компоненты предохранителей, которые я использую,Resilience4J, соответствующий исходный код выполнения потокаResilience4JCircuitBreakerФрагмент ниже:

	Supplier<Future<T>> futureSupplier = () -> executorService.submit(toRun::get);

SecurityContextHolderСохранение информации по умолчаниюThreadLocalРеализовано, мы все знаем, что это не может быть кросс-поточно, а перехватчик Фейна в это время находится как раз в дочернем потоке,Поэтому Feign с включенным CircuitBreaker не может напрямую ретранслировать токены..

Компоненты слияния включают устаревшие Hystrix, Resilience4J и Sentinel от Ali, и их механизмы могут немного отличаться.

Реализовать ретрансляцию токенов

Хотя ретрансляция токенов напрямую невозможна, я все же нашел из нее некоторую информацию. Обработчик проксирован на интерфейс FeignFeignCircuitBreakerInvocationHandlerСледующий код был найден в:

private Supplier<Object> asSupplier(final Method method, final Object[] args) {
		final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
		return () -> {
			try {
				RequestContextHolder.setRequestAttributes(requestAttributes);
				return this.dispatch.get(method).invoke(args);
			}
			catch (RuntimeException throwable) {
				throw throwable;
			}
			catch (Throwable throwable) {
				throw new RuntimeException(throwable);
			}
			finally {
				RequestContextHolder.resetRequestAttributes();
			}
		};
	}

Это код выполнения прокси-класса Feign, мы можем видеть его перед выполнением:

		final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

Здесь нужно получить информацию, запрошенную в вызывающем потоке, в том числеServletHttpRequest,ServletHttpResponseи другая информация. Затем я снова помещаю эту информацию в Setter в лямбда-коде:

	RequestContextHolder.setRequestAttributes(requestAttributes);

Если это делается в потоке, то он просто перекормлен, на самом делеSupplierВозвращаемое значение выполняется в другом потоке. Целью этого является сохранение некоторых запрошенных метаданных между потоками.

InheritableThreadLocal

RequestContextHolderКак передавать данные между потоками?

public abstract class RequestContextHolder  {
 
	private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
			new NamedThreadLocal<>("Request attributes");

	private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
			new NamedInheritableThreadLocal<>("Request context");
// 省略
}

RequestContextHolderПоддерживаются два контейнера, один не может быть перекрестнымThreadLocal, один реализованInheritableThreadLocalизNamedInheritableThreadLocal.InheritableThreadLocalМожно передавать данные родительского потока в дочерний поток, основываясь на этом принципе.RequestContextHolderИнформация о запросе вызывающего абонента передается в подпоток, и с помощью этого принципа может быть реализована ретрансляция токена.

Реализовать ретрансляцию токенов

Ретрансляция токена реализована путем изменения исходного кода перехватчика Feign:

    /**
     * 令牌中继
     */
    static class BearerTokenRequestInterceptor implements RequestInterceptor {
        private static final Pattern BEARER_TOKEN_HEADER_PATTERN = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$",
                Pattern.CASE_INSENSITIVE);

        @Override
        public void apply(RequestTemplate template) {
            final String authorization = HttpHeaders.AUTHORIZATION;
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (Objects.nonNull(requestAttributes)) {
                String authorizationHeader = requestAttributes.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
                Matcher matcher = BEARER_TOKEN_HEADER_PATTERN.matcher(authorizationHeader);
                if (matcher.matches()) {
                    // 清除token头 避免传染
                    template.header(authorization);
                    template.header(authorization, authorizationHeader);
                }
            }
        }
    }

поэтому, когда вы звонитеFooClient.bar()когда, вfoo-serviceСервер ресурсов OAuth2 также может получить токен вызывающей стороны, а затем получить информацию о пользователе для обработки разрешений на ресурсы и услуг.

Не забудьте внедрить этот перехватчик в Spring IoC.

Суммировать

Реле токенов микросервиса очень важно для обеспечения передачи пользовательского состояния в цепочке вызовов. И в этом тоже сложность микросервисов. Сегодня с помощью некоторых возможностей Feign иThreadLocalЭта функция реализует ретрансляцию токенов для справки. Оригинальность не так проста, пожалуйста, лайкайте и комментируйте больше.