gpt4 book ai didi

java - Spring boot ClientHttpRequestInterceptor 在 401 上重新发送

转载 作者:行者123 更新时间:2023-12-02 12:24:40 27 4
gpt4 key购买 nike

所以我有以下场景要使用 Spring boot rest template 来实现消耗 REST-API (涉及token认证机制)。为了执行测试,我在 Spring Boot 中创建了简单的模拟 REST API。流程如下,

从我的 API 消费者应用程序中,

  • 使用 rest-template 发送请求要使用 protected API,此 API 需要 Authorization: Bearer <token>请求中存在的 header 。
  • 如果此 token 出现问题(缺少 header 、无效 token ), protected API 将返回 HTTP-Unauthorized (401) .
  • 发生这种情况时,消费者 API 应向另一个 protected API 发送另一个请求,该请求返回有效的访问 token ,此 protected 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();
}
}

另一个示例 protected 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;
}

关于java - Spring boot ClientHttpRequestInterceptor 在 401 上重新发送,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54940350/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com