s3通过cloudfront签名下载,偶尔报403问题

昨天在弄s3下载的时候,遇到一个问题就是通过Ajax去请求接口返回一个签名的cloudfront的下载url.会随机的报403,
就是说这个文件没有权限,导致下载不了。因为前端下载用的是iframe,起初怀疑是iframe的加载问题,我印象中iframe这货凡正是经常冒出好多问题,
然后让前端同事改成使用A标签后,问题依旧。但是有点奇怪的是,这个url你直接在浏览器里面是能访问和下载的。这也一直就是老板怀疑我代码有问题的原因。
为什么浏览器可以?点按钮请求这个url不行。说实在的,我最开始也一直纠结在这个点上,要说我返回的url有问题,为什么直接输入可以呢?
前端同事后面又试了下,如果url一直不变。那么是能成功下载的。所以,他也确认他的代码没问题,问题陷入僵局了。哎,虽说这样,问题还是要解决呀,只能一层层分析了。

既然动态请求接口的url有问题,那我用程序访问接口拿到url后,再访问这个url看看状态码,于是用go实现了下。

package main

import (
    "net/http"
    "bytes"
    "io/ioutil"
    "fmt"
    "encoding/json"
    "github.com/astaxie/beego/logs"
)

const (
    API_URL = "http://ap"
)

type ApiResponse struct {
    Code int32 `json:"code"`
    Msg string `json:"msg"`
    //Data  map[string]interface{} `json data`
    Data  ResultList `json:"data"`
}

type ResultList struct {
    Result UrlList `json:"result"`
}
type UrlList struct {
    Url string `json:"url"`
}


func getDownUrl() (string,error){

    //resp,error := http.Post(API_URL + "fi/dff",)
    postJson := `{"req_body":{"fd_lt":[{"fd":"12lvrmtp62q","fn":"contraeeeect.txt"}]}}`
    req, error := http.NewRequest("POST",API_URL + "/fi/dff",bytes.NewBuffer([]byte(postJson)))
    if error != nil {
        return "",error
    }
    req.Header.Set("Content-Type","application/json;charset=UTF-8")


    client := http.Client{}
    resp, error :=client.Do(req)
    if error != nil {
        panic(error)
    }

    defer resp.Body.Close()
    body,_ := ioutil.ReadAll(resp.Body);
    //fmt.Println(string(body))

    response := &ApiResponse{}
    json.Unmarshal(body, response)


    fmt.Println(response.Data.Result.Url)
    //fmt.Println(response.Data.Result.Url)

    return response.Data.Result.Url,nil
}

func getUrl(url string) {
    resp, _ := http.Get(url)
    defer resp.Body.Close()
    if resp.StatusCode != 200{
        fmt.Println("error get url",resp.StatusCode)
    }
    //fmt.Println("status:" + strconv.Itoa(resp.StatusCode))
}

func main(){
    urlist := make(map[int]string)
    /*for i:=0 ; i< 3 ;i++ {
        r, _ := http.Get("https://s3-us-west-1.amazonaws.com/us-west-1.hub.page/1/1a/l1do9rlv/11al1do9rlv")
        defer r.Body.Close()
        fmt.Println(r.StatusCode)
    }
     return*/
     for i:=0 ; i< 100 ;i++ {

         //logs.Info(fmt.Sprintf("第%d次请求",i))
         url, _ := getDownUrl()
         if url != "" {
             urlist[i] = url
             getUrl(url)            
         }
     }

     logs.Debug("继续访问")
     for i,url := range urlist {
         //logs.Info(fmt.Sprintf("第%d次请求",i))
         if url != "" {
             urlist[i] = url
             getUrl(url)
         }
     }

}

以上代码,我是先请求接口返回url,再发get请求,获取状态码。再把url保存起来。
通过上面的输出,发现第一次请求会出现403的情况,第二次请求都是200,这就说明问题出在服务端接口上了。
于是查看代码后,发现在签名的时候有一个时间参数,DateGreaterThan这个去掉就好了。
查了下这个参数表示对象不能在这个时间之前访问。

//从全局变量查找过期时间
$cacheInstance  = CacheManage::getInstance();
$expireTime = $cacheInstance->HGet(CacheConst::$cacheList['globalsetting']['globalsetting_s3_download_expired_time'],'value');
if (empty($expireTime)) {
    $expireTime =  120; //单位:分钟
}
$expires     = time() + $expireTime * 60;
//自定义策略
$policys =   json_encode([
    'Statement' => [
        [
            'Resource' => $bucketInfo['url'].'/*',
            'Condition' => [
               // 'DateGreaterThan' => ['AWS:EpochTime' => time() ],
                'DateLessThan' => ['AWS:EpochTime'    => $expires],
              //  'IpAddress' => ['AWS:SourceIp' => \app\util\Tools::getIp()],
            ],
        ],
    ],
],JSON_UNESCAPED_SLASHES);
posted @ 2018-11-29 13:24  随彦心MO  阅读(779)  评论(0)    收藏  举报