更新: 2020-09-03
阿里云官方已经修正了Golang的示例 https://connect.console.aliyun.com/connect/detail/162632
PHP相关的我给一些项目提了 PR https://github.com/iiDestiny/flysystem-oss/pull/52/commits/07d7adf2d7dc4b3352a1258bee34db9e371fd4b7
今天是 2020-08-15
比正确时间晚8个小时
阿里云官网给的例子是 https://help.aliyun.com/document_detail/91771.html#concept-nhs-ldt-2fb
里面有个计算过期时间的函数
1 2 3 4 5 6 7 8 |
function gmt_iso8601($time) { $dtStr = date("c", $time); $mydatetime = new DateTime($dtStr); $expiration = $mydatetime->format(DateTime::ISO8601); $pos = strpos($expiration, '+'); $expiration = substr($expiration, 0, $pos); return $expiration."Z"; } |
其输入参数是Unix时间戳。这是一个把Unix时间戳转换成iso8601格式的算法,但是稀奇的是,阿里云给的例子,是直接把ISO8601的格式结尾的时区裁剪掉,改成0时区(Z),而我们知道,如果你系统本身就是0时区,那么这个做法没什么问题,如果你系统是东8区呢?那么生成的时间字符串所代表的过期时间就比实际期望的时间晚8个小时。
比如现在是东八区2020-08-15 14:15:30我输入如下参数,期望这个签名在30秒后过期。
1 |
echo gmt_iso8601((new DateTime('2020-08-15T14:15:30+8'))->format('U')+30); |
实际输出的是 2020-08-15T14:16:00Z, 这是一个ISO8601的格式,表示是0时区的2020-08-15 14:16:00,换算成东八区就是 2020-08-15T22:16:00+8 比我期望的 2020-08-15T14:16:00+8 晚了整整八个小时。
这种偏差在正时区还好,不影响业务,顶多就是过期时间比预期的长很多,如果在负时区呢?
1 2 |
date_default_timezone_set('America/Los_Angeles') echo gmt_iso8601((new DateTime('2020-08-15T14:15:30-8'))->format('U')+30); |
输出的是Z,直接报错了。
就算把系统默认时间改成正时区,不报错,但是输出依然是有误差的。
再来看看其他语言的SDK,这是阿里云官网的Golang文档 https://help.aliyun.com/document_detail/91818.html#title-io0-22w-4f3,代码包下载 http://docs-aliyun.cn-hangzhou.oss.aliyun-inc.com/assets/attach/86983/APP_zh/1538029675267/aliyun-oss-appserver-go-master.zip?file=aliyun-oss-appserver-go-master.zip
我们来看看Golang这个示例怎么实现这个方法的:
1 2 3 4 |
func get_gmt_iso8601(expire_end int64) string { var tokenExpire = time.Unix(expire_end, 0).Format("2006-01-02T15:04:05Z") return tokenExpire } |
很明显,他这边实现就是把unix时间戳直接格式化为ISO8601格式并且丢掉了时区信息统一设置为0时区
写个完整的程序跑了一下一样的结果。
那么这个时间差是否被OSS网关承认呢
到底这个时间戳有效期是我们设定的30秒还是8个多小时呢?我特意用"正确"的实现(当前时间转换为0时区时间)和错误的实现(直接丢掉时区信息强制设置为0时区)
我认为的正确实现,以Carbon为例
1 |
Carbon::now('GMT')->addSeconds(60)->format('Y-m-d\TH:i:s\Z') |
Carbon会把当前时区用GMT时区表示(不是简单的抹掉时区,而是加减到当地时间)
测试结果是,用正确时间算法,生成的policy就正好在60秒后过期。说明OSS的网关也是按照认为的正确算法来验证时间的,而用官网自带Demo实现的60秒过期算法,在几个小时后,这个policy还能够正常上传,直到8小时以后。看来自家网关实现的时候也是按照我认为的正确逻辑来实现的。
阿里这个低级错误不改犯。
正确的实现
刚才上面已经用Carbon给出了正确的时间实现,所以对应正确的Golang实现应该是这样的:
1 2 3 4 |
func get_gmt_iso8601(expire_end int64) string { var tokenExpire = time.Unix(expire_end, 0).UTC().Format("2006-01-02T15:04:05Z") return tokenExpire } |
如果不用Carbon的实现 用PHP自带的实现是这样的
1 |
(new \DateTime(null, new \DateTimeZone('UTC')))->setTimestamp($time)->format('Y-m-d\TH:i:s\Z'); |
不知道阿里云什么时候会改过来
哈哈哈哈,博主很细心呐。时区问题是最常见的新手问题之一,妹想到= =阿里的程序员也会犯这样的错误~