Spring boot ClientHttpRequestInterceptor переслать на 401 - PullRequest
0 голосов
/ 01 марта 2019

Таким образом, у меня есть сценарий ниже для реализации с использованием Spring boot rest template для использования REST-API (включает механизм аутентификации токена).Для выполнения теста я создал простой макет REST API при весенней загрузке.Вот процесс,

Из моего потребительского приложения API

  • отправляет запрос, используя rest-template для использования защищенного API, для этого API требуется заголовок Authorization: Bearer <token>, присутствующий в запросе.
  • если что-то не так с этим токеном (отсутствует заголовок, недействительный токен), защищенный API возвращает HTTP-Unauthorized (401).
  • , когда это происходит, потребительский API должен отправить другой запрос другому защищенному API, которыйвозвращает действительный токен доступа, этот защищенный API требует наличия заголовка Authorization: Basic <token>.Новый токен доступа будет храниться в статическом поле и использоваться во всех других запросах для проверки подлинности.

Это можно сделать, просто перехватив 401-HttpClientErrorException в RestTemplate пользовательских методах (postForObject), но идея состояла в том, чтобы отделить его от REST-API потребительских классов.Чтобы добиться этого, я попытался использовать ClientHttpRequestInterceptor

Вот код, который я пробовал до сих пор.

Класс перехватчика

public class AuthRequestInterceptor implements ClientHttpRequestInterceptor {

private static final Logger LOGGER = LoggerFactory.getLogger(AuthRequestInterceptor.class);
private static final String BASIC_AUTH_HEADER_PREFIX = "Basic ";
private static final String BEARER_AUTH_HEADER_PREFIX = "Bearer ";

//stores access token
private static String accessToken = null;

@Value("${app.mife.apiKey}")
private String apiKey;

@Autowired
private GenericResourceIntegration resourceIntegration; // contains methods of rest template

@Override
public ClientHttpResponse intercept(
        HttpRequest request,
        byte[] body,
        ClientHttpRequestExecution execution
) throws IOException {
    LOGGER.info("ReqOn|URI:[{}]{}, Headers|{}, Body|{}", request.getMethod(), request.getURI(), request.getHeaders(), new String(body));
    request.getHeaders().add(ACCEPT, APPLICATION_JSON_VALUE);
    request.getHeaders().add(CONTENT_TYPE, APPLICATION_JSON_VALUE);
    try {
        //URI is a token generate URI, request
        if (isBasicUri(request)) {
            request.getHeaders().remove(AUTHORIZATION);
            //sets BASIC auth header
            request.getHeaders().add(AUTHORIZATION, (BASIC_AUTH_HEADER_PREFIX + apiKey));
            ClientHttpResponse res = execution.execute(request, body);
            LOGGER.info("ClientResponse:[{}], status|{}", "BASIC", res.getStatusCode());
            return res;
        }

        //BEARER URI, protected API access
        ClientHttpResponse response = null;
        request.getHeaders().add(AUTHORIZATION, BEARER_AUTH_HEADER_PREFIX + getAccessToken());
        response = execution.execute(request, body);
        LOGGER.info("ClientResponse:[{}], status|{}", "BEARER", response.getStatusCode());

        if (unauthorized(response)) {
            LOGGER.info("GetToken Res|{}", response.getStatusCode());
            String newAccessToken = generateNewAccessCode();
            request.getHeaders().remove(AUTHORIZATION);
            request.getHeaders().add(AUTHORIZATION, (BEARER_AUTH_HEADER_PREFIX + newAccessToken));
            LOGGER.info("NewToken|{}", newAccessToken);
            return execution.execute(request, body);
        }

        if (isClientError(response) || isServerError(response)) {
            LOGGER.error("Error[Client]|statusCode|{}, body|{}", response.getStatusCode(), CommonUtills.streamToString(response.getBody()));
            throw new AccessException(response.getStatusText(),
                    ServiceMessage.error().code(90).payload(response.getRawStatusCode() + ":" + response.getStatusText()).build());
        }

        return response;
    } catch (IOException exception) {
        LOGGER.error("AccessError", exception);
        throw new AccessException("Internal service call error",
                ServiceMessage.error().code(90).payload("Internal service call error", exception.getMessage()).build()
        );
    } finally {
        LOGGER.info("ReqCompletedOn|{}", request.getURI());
    }
}

private String generateNewAccessCode() {
    Optional<String> accessToken = resourceIntegration.getAccessToken();
    setAccessToken(accessToken.get());
    return getAccessToken();
}

private static void setAccessToken(String token) {
    accessToken = token;
}

private static String getAccessToken() {
    return accessToken;
}

private boolean isClientError(ClientHttpResponse response) throws IOException {
    return (response.getRawStatusCode() / 100 == 4);
}

private boolean isServerError(ClientHttpResponse response) throws IOException {
    return (response.getRawStatusCode() / 100 == 5);
}

private boolean unauthorized(ClientHttpResponse response) throws IOException {
    return (response.getStatusCode().value() == HttpStatus.UNAUTHORIZED.value());
}

private boolean isBasicUri(HttpRequest request) {
    return Objects.equals(request.getURI().getRawPath(), "/apicall/token");
}

private boolean isMifeRequest(HttpRequest request) {
    return request.getURI().toString().startsWith("https://api.examplexx.com/");
}

}

Метод генерации токена - In resourceIntegration

public Optional<String> getAccessToken() {
    ResponseEntity<AccessTokenResponse> res = getRestTemplate().exchange(
            getAccessTokenGenUrl(),
            HttpMethod.POST,
            null,
            AccessTokenResponse.class
    );
    if (res.hasBody()) {
        LOGGER.info(res.getBody().toString());
        return Optional.of(res.getBody().getAccess_token());
    } else {
        return Optional.empty();
    }
}

Другой пример защищенного метода вызова API

public Optional<String> getMobileNumberState(String msisdn) {
    try {
        String jsonString = getRestTemplate().getForObject(
                getQueryMobileSimImeiDetailsUrl(),
                String.class,
                msisdn
        );
        ObjectNode node = new ObjectMapper().readValue(jsonString, ObjectNode.class);
        if (node.has("PRE_POST")) {
            return Optional.of(node.get("PRE_POST").asText());
        }
        LOGGER.debug(jsonString);
    } catch (IOException ex) {
        java.util.logging.Logger.getLogger(RestApiConsumerService.class.getName()).log(Level.SEVERE, null, ex);
    }
    return Optional.empty();
}

Проблема

Вот журнал фиктивного API,

//first time no Bearer token, this returns 401 for API /simulate/unauthorized
accept:text/plain, application/json, application/*+json, */*
authorization:Bearer null
/simulate/unauthorized


//then it sends Basic request to get a token, this is the log
accept:application/json, application/*+json
authorization:Basic M3ZLYmZQbE1ERGhJZWRHVFNiTEd2Vlh3RThnYTp4NjJIa0QzakZUcmFkRkVOSEhpWHNkTFhsZllh
Generated Token:: 57f21374-1188-4c59-b5a7-370eac0a0aed
/apicall/token


//finally consumer API sends the previous request to access protected API and it contains newly generated token in bearer header
accept:text/plain, application/json, application/*+json, */*
authorization:Bearer 57f21374-1188-4c59-b5a7-370eac0a0aed
/simulate/unauthorized

Проблема даже в том, что хотя фиктивный журнал API имел правильный поток, потребительский API не получает никакогоответ на третий вызов, вот его журнал (ненужные журналы опущены).

RequestInterceptor.intercept() - ReqOn|URI:[GET]http://localhost:8080/simulate/unauthorized?x=GlobGlob, Headers|{Accept=[text/plain, application/json, application/*+json, */*], Content-Length=[0]}, Body|
RequestInterceptor.intercept() - ClientResponse:[BEARER], status|401 UNAUTHORIZED

RequestInterceptor.intercept() - GetToken Res|401 UNAUTHORIZED
RequestInterceptor.intercept() - ReqOn|URI:[POST]http://localhost:8080/apicall/token?grant_type=client_credentials, Headers|{Accept=[application/json, application/*+json], Content-Length=[0]}, Body|
RequestInterceptor.intercept() - ClientResponse:[BASIC], status|200 OK
RequestInterceptor.intercept() - ReqCompletedOn|http://localhost:8080/apicall/token?grant_type=client_credentials

RestApiConsumerService.getAccessToken() - |access_token2163b0d4-8d00-4eba-92d0-7e0bb609b982,scopeam_application_scope default,token_typeBearer,expires_in34234|
RequestInterceptor.intercept() - NewToken|2163b0d4-8d00-4eba-92d0-7e0bb609b982
RequestInterceptor.intercept() - ReqCompletedOn|http://localhost:8080/simulate/unauthorized?x=GlobGlob

http://localhost:8080/simulate/unauthorized третий раз не возвращает никакого ответа, но фиктивный журнал API говорит, что он попал в запрос.Что я сделал не так? Возможно ли решить эту задачу с помощью этой техники?или есть другой альтернативный способ сделать это?любая помощь высоко ценится.

...