У меня есть два микросервиса, назовем их A и B, за Spring Cloud Gateway. Также есть OAuthServer. Когда пользователи отправляют запрос в службу A, они должны сначала получить токен JWT от oAuthServer с помощью потока паролей.
Предостережение заключается в том, что службе A необходимо взаимодействовать со службой B, но для этого ей нужен доступ токен. Идея состоит в том, чтобы использовать JWT, предоставляемый пользователем, при доступе к службе A, ретранслируя его службе B, таким образом выполняя запрос.
В предыдущих версиях Spring Boot я бы использовал OAuth2RestTemplate
, но теперь мне нужно использовать Webclient
.
Я создал фильтр для извлечения токена-носителя из входящего запроса в сервисе A, а затем сохранил его в одноэлементном классе и вручную добавил к исходящему вызову.
@Component
public class JWTAuthorizationFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(JWTAuthorizationFilter.class);
private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
private TokenStore tokenStore;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
if (checkJWTToken(request, response)) {
String token = extractToken(request);
if (token != null) {
log.info("*****RECEIVED REQUEST WITH TOKEN {}. ", token);
log.info("***** Will store it for future requests.");
tokenStore = TokenStore.getInstance();
tokenStore.setToken(token);
// setUpSpringAuthentication(token);
} else {
SecurityContextHolder.clearContext();
}
}
chain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
return jwtToken;
}
private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) {
String authenticationHeader = request.getHeader(HEADER);
if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX))
return false;
return true;
}
TokenStore
public class TokenStore {
private static TokenStore instance;
private String token;
private TokenStore() {
}
public static synchronized TokenStore getInstance() {
if (instance == null) {
instance = new TokenStore();
}
return instance;
}
public String getToken() {
return token;
}
public synchronized void setToken(String token) {
this.token = token;
}
}
И, наконец, веб-клиент:
@Bean
@LoadBalanced
@Profile("!default")
public WebClient.Builder loadBalancedWebClientBuilder() {
final WebClient.Builder builder = WebClient.builder();
return builder;
}
Использование:
Mono<Map<Long, DrawDTO>> mono = getWebClient().get().uri(url)
.header("Authorization", "Bearer " + tokenStore.getToken()).retrieve().bodyToFlux(DTO.class)
.map(d -> Tuples.of(Long.parseLong(d.getUniqueId()), d))
.collectMap(tuple -> tuple.getT1(), tuple -> tuple.getT2()).log()
.onErrorMap(WebClientResponseException.class, ex -> handleException(ex)).timeout(Duration.ofSeconds(5));
Однако , Я почти уверен, что это ошибка. TokenStore является одноэлементным, если два запроса приходят от пользователей X и Y , при этом второй вызов фильтра переопределит токен первого. Таким образом, служба A будет использовать токен пользователя Y для выполнения запроса пользователя X . Таким образом, если уровень доступа пользователя X ниже, чем Y , это проблема безопасности.
Я ошибаюсь насчет вышеизложенного? Если да, что я могу сделать, чтобы это исправить?
Я нашел одно решение - использовать учетные данные клиента для внутрисервисной связи, но я не уверен на 100%, как это сделать с помощью Spring Boot 2.2.6 и Spring Security 5.
Есть ли правильный способ сделать то, что я задумал изначально?