王飞
2025-01-23 99365608dec6eade7d645a91fb0f2205a332d1f1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// 临时密钥服务例子
var bodyParser = require('body-parser');
var STS = require('qcloud-cos-sts');
var express = require('express');
var crypto = require('crypto');
 
// 配置参数
var config = {
    secretId: process.env.SecretId,
    secretKey: process.env.SecretKey,
    proxy: process.env.Proxy,
    durationSeconds: 1800,
    bucket: process.env.Bucket,
    region: process.env.Region,
    // 允许操作(上传)的对象前缀,可以根据自己网站的用户登录态判断允许上传的目录,例子: user1/* 或者 * 或者a.jpg
    // 请注意当使用 * 时,可能存在安全风险,详情请参阅:https://cloud.tencent.com/document/product/436/40265
    allowPrefix: '_ALLOW_DIR_/*',
    // 简单上传和分片,需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/14048
    allowActions: [
        // 所有 action 请看文档 https://cloud.tencent.com/document/product/436/31923
        // 简单上传
        'name/cos:PutObject',
        'name/cos:PostObject',
        // 分片上传
        'name/cos:InitiateMultipartUpload',
        'name/cos:ListMultipartUploads',
        'name/cos:ListParts',
        'name/cos:UploadPart',
        'name/cos:CompleteMultipartUpload'
    ],
};
 
 
// 创建临时密钥服务
var app = express();
app.use(bodyParser.json());
 
// 格式一:临时密钥接口
app.all('/sts', function (req, res, next) {
 
    // TODO 这里根据自己业务需要做好放行判断
    if (config.allowPrefix === '_ALLOW_DIR_/*') {
        res.send({error: '请修改 allowPrefix 配置项,指定允许上传的路径前缀'});
        return;
    }
 
    // 获取临时密钥
    var LongBucketName = config.bucket;
    var ShortBucketName = LongBucketName.substr(0, LongBucketName.lastIndexOf('-'));
    var AppId = LongBucketName.substr(LongBucketName.lastIndexOf('-') + 1);
    var policy = {
        'version': '2.0',
        'statement': [{
            'action': config.allowActions,
            'effect': 'allow',
            'resource': [
                'qcs::cos:' + config.region + ':uid/' + AppId + ':prefix//' + AppId + '/' + ShortBucketName + '/' + config.allowPrefix,
            ],
        }],
    };
    var startTime = Math.round(Date.now() / 1000);
    STS.getCredential({
        secretId: config.secretId,
        secretKey: config.secretKey,
        proxy: config.proxy,
        region: config.region,
        durationSeconds: config.durationSeconds,
        policy: policy,
    }, function (err, tempKeys) {
        if (tempKeys) tempKeys.startTime = startTime;
        res.send(err || tempKeys);
    });
});
 
 
// // 格式二:临时密钥接口,支持细粒度权限控制
// // 判断是否允许获取密钥
// var allowScope = function (scope) {
//     var allow = (scope || []).every(function (item) {
//         return config.allowActions.includes(item.action) &&
//             item.bucket === config.bucket &&
//             item.region === config.region &&
//             (item.prefix || '').startsWith(config.allowPrefix);
//     });
//     return allow;
// };
// app.all('/sts-scope', function (req, res, next) {
//     var scope = req.body;
//
//     // TODO 这里根据自己业务需要做好放行判断
//     if (config.allowPrefix === '_ALLOW_DIR_/*') {
//         res.send({error: '请修改 allowPrefix 配置项,指定允许上传的路径前缀'});
//         return;
//     }
//     // TODO 这里可以判断 scope 细粒度控制权限
//     if (!scope || !scope.length || !allowScope(scope)) return res.send({error: 'deny'});
//
//     // 获取临时密钥
//     var policy = STS.getPolicy(scope);
//     var startTime = Math.round(Date.now() / 1000);
//     STS.getCredential({
//         secretId: config.secretId,
//         secretKey: config.secretKey,
//         proxy: config.proxy,
//         durationSeconds: config.durationSeconds,
//         policy: policy,
//     }, function (err, tempKeys) {
//         if (tempKeys) tempKeys.startTime = startTime;
//         res.send(err || tempKeys);
//     });
// });
//
// 用于 PostObject 签名保护
app.all('/post-policy', function (req, res, next) {
    var query = req.query;
    var now = Math.round(Date.now() / 1000);
    var exp = now + 900;
    var qKeyTime = now + ';' + exp;
    var qSignAlgorithm = 'sha1';
    var policy = JSON.stringify({
        'expiration': new Date(exp * 1000).toISOString(),
        'conditions': [
            // {'acl': query.ACL},
            // ['starts-with', '$Content-Type', 'image/'],
            // ['starts-with', '$success_action_redirect', redirectUrl],
            // ['eq', '$x-cos-server-side-encryption', 'AES256'],
            {'q-sign-algorithm': qSignAlgorithm},
            {'q-ak': config.secretId},
            {'q-sign-time': qKeyTime},
            {'bucket': config.bucket},
            {'key': query.key},
        ]
    });
 
    // 签名算法说明文档:https://www.qcloud.com/document/product/436/7778
    // 步骤一:生成 SignKey
    var signKey = crypto.createHmac('sha1', config.secretKey).update(qKeyTime).digest('hex');
 
    // 步骤二:生成 StringToSign
    var stringToSign = crypto.createHash('sha1').update(policy).digest('hex');
 
    // 步骤三:生成 Signature
    var qSignature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex');
 
    console.log(policy);
    res.send({
        policyObj: JSON.parse(policy),
        policy: Buffer.from(policy).toString('base64'),
        qSignAlgorithm: qSignAlgorithm,
        qAk: config.secretId,
        qKeyTime: qKeyTime,
        qSignature: qSignature,
        // securityToken: securityToken, // 如果使用临时密钥,要返回在这个资源 sessionToken 的值
    });
});
 
app.all('*', function (req, res, next) {
    res.send({code: -1, message: '404 Not Found'});
});
 
// 启动签名服务
app.listen(3000);
console.log('app is listening at http://127.0.0.1:3000');