所以我有以下方案来实现使用Spring boot rest template来消耗REST-API(涉及 token 认证机制)。为了执行测试,我在 Spring 启动时创建了简单的模拟REST API。这是过程

在我的API消费者应用中,

  • 使用rest-template发送请求以使用受保护的API,此API要求Authorization: Bearer <token>标头出现在请求中。
  • 如果此 token 有问题(缺少标头,无效 token ),则受保护的API返回HTTP-Unauthorized (401)
  • 发生这种情况时,使用者API应该向另一个受保护的API发送另一个请求,该请求返回一个有效的访问 token ,此受保护的API要求存在Authorization: Basic <token>标头。新的访问 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/");
    }
    

    }

    token 生成方法-在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日志说它已达到请求。我做错了什么?可以使用这种技术来完成这项任务吗?还是有其他替代方法可以做到这一点?非常感谢您的帮助。

    最佳答案

    我已经试过了:

    添加一个拦截器ClientHttpRequestInterceptor

        import java.io.IOException;
    
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.data.redis.core.RedisTemplate;
        import org.springframework.http.HttpRequest;
        import org.springframework.http.HttpStatus;
        import org.springframework.http.client.ClientHttpRequestExecution;
        import org.springframework.http.client.ClientHttpRequestInterceptor;
        import org.springframework.http.client.ClientHttpResponse;
        import org.springframework.util.StringUtils;
    
    
    
    
    
    
        public class RequestResponseHandlerInterceptor implements ClientHttpRequestInterceptor {
    
    
            @Autowired
            private TokenService tokenService;
    
            @Autowired
            private RedisTemplate<String, String> redisTemplate;
    
            private static final String AUTHORIZATION = "Authorization";
    
            /**
             * This method will intercept every request and response and based on response status code if its 401 then will retry
             * once
             */
    
            @Override
            public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
                ClientHttpResponse response = execution.execute(request, body);
                if (HttpStatus.UNAUTHORIZED == response.getStatusCode()) {
                    String accessToken = tokenService.getAccessToken();
                    if (!StringUtils.isEmpty(accessToken)) {
                        request.getHeaders().remove(AUTHORIZATION);
                        request.getHeaders().add(AUTHORIZATION, accessToken);
    //retry
    response = execution.execute(request, body);
                    }
                }
                return response;
            }
    
        }
    

    除此之外,您还需要覆盖RestTemplate初始化。
    @Bean
        public RestTemplate restTemplate() {
            RestTemplate restTemplate = new RestTemplate();
            restTemplate.setInterceptors(Collections.singletonList(new RequestResponseHandlerInterceptor()));
            return restTemplate;
        }
    

    08-18 05:57