我有一个Spring + CXF应用程序,它使用传输API:Transmission RPC在另一台服务器上运行。
根据Transmission文档,您需要发送在第一个请求时生成的令牌。然后,服务器使用409 http代码以及包含令牌的标头进行响应。此令牌应在所有后续呼叫中发送:
2.3.1。 CSRF保护大多数传输RPC服务器都需要一个X-Transmission-Session-Id标头与请求一起发送,以防止 CSRF攻击。当您的请求有错误的ID时 - 例如当您 发送您的第一个请求,或者当服务器到期CSRF令牌时 - 传输RPC服务器将返回HTTP 409错误 正确的X-Transmission-Session-Id在它自己的标题中。所以,正确的 处理409响应的方法是更新你的 X-Transmission-Session-Id并重新发送先前的请求。
我正在寻找使用CXF过滤器或拦截器的解决方案,它基本上将处理409响应并重试添加令牌标头的初始请求。我认为客户可以保留此令牌并在以后的呼叫中发送它。
我对cxf不是很熟悉所以我想知道这是否可以实现以及如何实现。任何提示都会有所帮助。
谢谢!
答案 0 :(得分:2)
这里可以使用spring-retry,它现在是一个独立的项目,不再是春季批次的一部分。
正如解释here重试回调将有助于使用令牌标头更新另一个调用。
在这种情况下,伪代码/逻辑看起来像下面的
RetryTemplate template = new RetryTemplate();
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
/*
* 1. Check if RetryContext contains the token via hasAttribute. If available set the header else proceed
* 2. Call the transmission API
* 3.a. If API responds with 409, read the token
* 3.a.1. Store the token in RetryContext via setAttribute method
* 3.a.2. Throw a custom exception so that retry kicks in
* 3.b. If API response is non 409 handle according to business logic
* 4. Return result
*/
}
});
确保RetryTemplate
配置合理的重试&amp;退避政策,以避免任何资源争用/意外。
如果有任何疑问/包版,请在评论中说明。
N.B。:RetryContext
的实施RetryContextSupport
有hasAttribute
&amp;从Spring核心AttributeAccessor
继承的setAttribute
方法
答案 1 :(得分:1)
假设您正在使用Apache CXF JAX RS客户端,只需为其创建自定义运行时异常和ResponseExceptionMapper
即可轻松完成。因此,我们的想法是手动将409个结果转换为某个异常,然后正确处理它们(在您的情况下重试服务调用)。
请参阅以下代码,了解完整的工作示例。
@SpringBootApplication
@EnableJaxRsProxyClient
public class SpringBootClientApplication {
// This can e stored somewhere in db or elsewhere
private static String lastToken = "";
public static void main(String[] args) {
SpringApplication.run(SpringBootClientApplication.class, args);
}
@Bean
CommandLineRunner initWebClientRunner(final TransmissionService service) {
return new CommandLineRunner() {
@Override
public void run(String... runArgs) throws Exception {
try {
System.out.println(service.sayHello(1, lastToken));
// catch the TokenExpiredException get the new token and retry
} catch (TokenExpiredException ex) {
lastToken = ex.getNewToken();
System.out.println(service.sayHello(1, lastToken));
}
}
};
}
public static class TokenExpiredException extends RuntimeException {
private String newToken;
public TokenExpiredException(String token) {
newToken = token;
}
public String getNewToken() {
return newToken;
}
}
/**
* This is where the magic is done !!!!
*/
@Provider
public static class TokenExpiredExceptionMapper implements ResponseExceptionMapper<TokenExpiredException> {
@Override
public TokenExpiredException fromResponse(Response r) {
if (r.getStatus() == 409) {
return new TokenExpiredException(r.getHeaderString("X-Transmission-Session-Id"));
}
return null;
}
}
@Path("/post")
public interface TransmissionService {
@GET
@Path("/{a}")
@Produces(MediaType.APPLICATION_JSON_VALUE)
String sayHello(@PathParam("a") Integer a, @HeaderParam("X-Transmission-Session-Id") String sessionId)
throws TokenExpiredException;
}
}