Мало знаний, большой вызов! Эта статья участвует в " Необходимые знания для программистов «Творческая деятельность
Эта статья также участвует "Проект "Звезда раскопок"" , чтобы выиграть творческие подарочные пакеты и бросить вызов творческим поощрениям
Оставьте сообщение ниже, Наггетсы отправят их
Где плохой текст в сообщении в блоге? Добро пожаловать в область комментариев, чтобы указать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
Эта функция реализует ретрансляцию токенов для справки. Оригинальность не так проста, пожалуйста, лайкайте и комментируйте больше.