在Jersey ClientRequestFilter中获取请求实体实现AWS SigV4认证
Jersey Client实现AWS SigV4签名的可行方案
直接在ClientRequestFilter中完成实体序列化与SigV4签名的全流程是最简洁的方案,核心思路是利用Jersey的Providers组件获取对应实体的MessageBodyWriter,手动完成序列化,同时处理无实体的场景(用空字符串作为签名内容)。
实现步骤与代码示例
- 注入Providers组件:通过
@Context注解注入Jersey的Providers,用于获取适配请求实体的MessageBodyWriter,保证手动序列化的结果与后续实际请求的序列化逻辑一致。 - 处理实体序列化:判断请求是否有实体,有则用对应的
MessageBodyWriter序列化;无实体则直接使用空字符串。 - 生成并添加SigV4签名头:利用AWS SDK提供的签名工具生成SigV4签名,将
Authorization等必要头信息添加到请求中。
import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestFilter; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.Providers; import jakarta.annotation.Priority; import jakarta.ws.rs.Priorities; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Date; import com.amazonaws.auth.AWS4Signer; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.http.HttpMethodName; @Priority(Priorities.AUTHENTICATION) public class AwsSigV4Filter implements ClientRequestFilter { private final AWSCredentialsProvider credentialsProvider; private final AWS4Signer signer; private final String serviceName; private final String region; @Context private Providers providers; public AwsSigV4Filter(String serviceName, String region) { this.credentialsProvider = DefaultAWSCredentialsProviderChain.getInstance(); this.signer = new AWS4Signer(); this.signer.setServiceName(serviceName); this.signer.setRegionName(region); } @Override public void filter(ClientRequestContext requestContext) throws IOException { // 处理请求实体,生成签名用的payload String payload; Object entity = requestContext.getEntity(); if (entity != null) { // 确定请求的媒体类型,无指定则默认JSON MediaType mediaType = requestContext.getMediaType() != null ? requestContext.getMediaType() : MediaType.APPLICATION_JSON_TYPE; ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 获取适配的MessageBodyWriter并序列化实体 providers.getMessageBodyWriter(entity.getClass(), entity.getClass(), null, mediaType).writeTo(entity, entity.getClass(), entity.getClass(), null, mediaType, requestContext.getHeaders(), baos); payload = baos.toString(StandardCharsets.UTF_8); } else { // 无实体时使用空字符串作为签名内容 payload = ""; } // 准备AWS签名所需的请求参数 HttpMethodName httpMethod = HttpMethodName.fromValue(requestContext.getMethod()); String endpoint = requestContext.getUri().toString(); MultivaluedMap<String, String> requestHeaders = requestContext.getHeaders(); // 添加X-Amz-Date头(若未存在) if (!requestHeaders.containsKey("X-Amz-Date")) { requestHeaders.add("X-Amz-Date", new Date().toInstant().toString()); } // 构建AWS HttpRequest对象用于签名 com.amazonaws.http.HttpRequest awsHttpRequest = new com.amazonaws.http.HttpRequest(httpMethod, endpoint); awsHttpRequest.setHeaders(requestHeaders); awsHttpRequest.setContent(payload.getBytes(StandardCharsets.UTF_8)); // 执行SigV4签名 signer.sign(awsHttpRequest, credentialsProvider.getCredentials()); // 将签名结果写入请求头 requestHeaders.putSingle("Authorization", awsHttpRequest.getHeaders().get("Authorization")); // 处理临时凭证的安全令牌(若存在) String securityToken = awsHttpRequest.getHeaders().get("X-Amz-Security-Token"); if (securityToken != null) { requestHeaders.putSingle("X-Amz-Security-Token", securityToken); } } }
使用方式
将该Filter注册到Jersey Client即可:
import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; public class ClientSetup { public static void main(String[] args) { Client client = ClientBuilder.newClient(); // 注册SigV4 Filter,指定服务名称(如neptune-db)和AWS区域 client.register(new AwsSigV4Filter("neptune-db", "us-east-1")); // 后续使用client发起请求即可自动添加SigV4签名 } }
关键注意事项
- AWS SDK版本:上述代码基于AWS SDK v1,若使用SDK v2,需替换为
software.amazon.awssdk.auth.signer.Aws4Signer等对应类,签名逻辑需做适配。 - 媒体类型适配:若你的应用使用非JSON的媒体类型(如XML),需确保Jersey已注册对应的
MessageBodyWriter,否则手动序列化会失败。 - Filter优先级:设置
@Priority(Priorities.AUTHENTICATION)确保签名Filter在其他可能修改请求实体或头的Filter之后执行,保证签名基于最终的请求内容生成。
内容的提问来源于stack exchange,提问作者Martynas Jusevičius
相关产品推荐
相关产品推荐

