1、背景
公司网络安全原因,私有部署s3服务的机器无法被直接访问,所以需要加一层代理,通过访问代理去访问s3服务器,这里使用nginx进行代理。使用s3服务的方式是在代码中使用官方的java s3 sdk(本文对于其他语言的官方sdk也适用)。
由于在配置过程中遇到了一些问题,如果不对s3协议的签名校验规则比较了解就难以解决此问题,故在此记录分享一下。
2、配置
官方sdk访问云存储提供了方便的接口,下载文件直接getObject(),上传文件直接putObject(),可以让我们不需要关心底层的逻辑,专注与我们的业务开发。sdk在被调用getObject()方法时,会把我们的获取文件动作封装成一个http请求发送出去,sdk封装的请求的过程中会在请求头中放入一些信息,以让s3服务端对请求进行验证。下面展示一个“获取云存储中的所有桶信息”的请求与响应的http报文(云存储地址为10.11.12.13),注意观察请求头中多了一些认证相关的信息:
请求
GET / HTTP/1.1
Host: 10.11.12.13
amz-sdk-invocation-id: *****
amz-sdk-request: attempt=1;max=4
amz-sdk-retry: 0/0/500
Authorization: AWS4-HMAC-SHA256 Credential=*****, SignedHeaders=*****, Signature=*****
Content-Type: application/octet-stream
User-Agent: aws-sdk-java/*****
x-amz-content-sha256: *****
X-Amz-Date: 20231123T015112Z
Content-Length: 0
Connection: Keep-Alive
响应
HTTP/1.1 200 OK
Content-Length: 785
Date: Thu, 23 Nov 2023 01:50:57 GMT
Vary: Origin
Connection: keep-alive
<?xml version="1.0" encoding="utf-8"?>
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
<Owner>
<ID>*****</ID>
<DisplayName>*****</DisplayName>
</Owner>
<Buckets>
<Bucket>
<Name>*****</Name>
<CreationDate>2023-09-28T08:49:10.000Z</CreationDate>
</Bucket>
</Buckets>
</ListAllMyBucketsResult>
重点: sdk在计算摘要时,会将请求的路径和请求Host参与摘到计算,所以一定要确保sdk访问nginx的uri和nginx访问云存储的uri保持一致(即请求头中的Host和请求路径要保持不变)。
这个怎么理解呢,直接看下面的两个报文,一个是sdk请求nginx的报文(nginx地址9.8.7.6),一个是nginx转发请求s3服务的报文,可以观察一下nginx转发后请求头中的哪些内容发生了变化,正是这些报文的变化导致了nginx转发到s3服务的请求失败(报错InvalidDigest,SignatureDoesNotMatch):
sdk请求nginx的报文
GET /xxx/x HTTP/1.1
Host: 9.8.7.6
amz-sdk-invocation-id: *****
amz-sdk-request: attempt=1;max=4
amz-sdk-retry: 0/0/500
Authorization: AWS4-HMAC-SHA256 Credential=*****, SignedHeaders=*****, Signature=*****
Content-Type: application/octet-stream
User-Agent: aws-sdk-java/*****
x-amz-content-sha256: *****
X-Amz-Date: 20231123T114100Z
Content-Length: 0
Connection: Keep-Alive
nginx转发请求s3服务的报文
GET / HTTP/1.0
Host: 10.11.12.13
Connection: close
Content-Length: 0
amz-sdk-invocation-id: *****
amz-sdk-request: attempt=1;max=4
amz-sdk-retry: 0/0/500
Authorization: AWS4-HMAC-SHA256 Credential=*****, SignedHeaders=*****, Signature=*****
Content-Type: application/octet-stream
User-Agent: aws-sdk-java/*****
x-amz-content-sha256: *****
X-Amz-Date: 20231123T114100Z
上面我们可以发现两处不同:
sdk请求到nginx的路径是配置nginx代理规则的时候配的,当请求路径以 /xxx/x开头时,将请求转发到云存储。但是nginx转发到s3服务就不需要这个路径了,所以发生了变化。sdk请求到nginx时host肯定时nginx的地址,nginx请求到云存储时host肯定是云存储的地址,这样没问题。
nginx的这两个在转发后的变化非常合理,但是却导致了云存储认证的时候报错。原因就是sdk在发送请求时将 9.8.7.6/xxx/x加入了摘要的计算,但是正确的云存储访问应该是10.11.12.13/,这样就导致了问题,s3服务端那边将这个host的值和路径的值取过来进行运算,发现了不匹配,于是就报错了。
那么怎样修改使得s3那边不报错呢?很简单,只需要保证Host的值不变、sdk到nginx的路径和nginx到云存储的路径不变(即uri不变),就可以了。这里需要修改nginx的代理配置,首先要求代理的匹配规则必须为’/',然后将Host的值赋值到header中,看下面的配置:
location / {
proxy_pass http://10.11.12.13:80;
proxy_set_header Host 9.8.7.6;
}
这里一定要注意,1、匹配的规则一定是 / ,不能是/x这种自定义匹配规则。2、转发后请求头的Host的值一定要和转发前请求头中的Host值相等,这里修改Host的值并不会导致请求转发不了。
经过如上配置后,使用sdk访问niginx就不会出错了,摘要认证就通过了。