NestJS配置阿里云短信,使用V1或V3签名验证
2021-09-18 本文已影响0人
Poppy11
V1验证算法
这里有一个坑就是,生成的签名,将PhoneNumberSet和SignName都URL解码了。
import { HttpException, Injectable } from '@nestjs/common'
import { commonJiraJSONConfig } from './commonFun';
import { proxyAxios } from './proxyAxios';
var urlencode = require('urlencode')
const crypto = require('crypto')
@Injectable()
export class commonSMSService {
private sort_params(params) {
let strParam = "";
let keys = Object.keys(params);
keys.sort();
for (let k in keys) {
//k = k.replace(/_/g, '.');
strParam += ("&" + keys[k] + "=" + params[keys[k]]);
}
return strParam
}
private formatSignString(reqMethod, endpoint, path, strParam) {
let strSign = reqMethod + endpoint + path + "?" + strParam.slice(1);
return strSign;
}
private sha1(secretKey, strsign) {
let signMethodMap = { 'HmacSHA1': "sha1" };
let hmac = crypto.createHmac(signMethodMap['HmacSHA1'], secretKey || "");
return hmac.update(Buffer.from(strsign, 'utf8')).digest('base64')
}
private get_req_url(params, endpoint) {
params['Signature'] = escape(params['Signature']);
params['PhoneNumberSet.0'] = urlencode(params['PhoneNumberSet.0']);
params['SignName'] = urlencode(params['SignName']);
const url_strParam = this.sort_params(params)
return "https://" + endpoint + "/?" + url_strParam.slice(1);
}
protected async tencentAutograph(phone: string, code: string, type: string) {
const jiraMessagesJSON = await commonJiraJSONConfig('Messages.json')
const jiraMessages = jiraMessagesJSON[type]
const SECRET_ID = "SECRET_ID "
const SECRET_KEY = "SECRET_KEY "
const endpoint = "sms.tencentcloudapi.com"
const Region = 'ap-guangzhou'
const Version = '2021-01-11'
const Action = 'SendSms'
const Timestamp = Math.round(new Date().getTime() / 1000)
const Nonce = code
const obj = {
'Action': Action,
'Language': 'zh-CN',
'Nonce': Nonce,
'PhoneNumberSet.0': `+86${phone}`,
'Region': Region,
'SecretId': SECRET_ID,
'SignName': jiraMessages?.signature?.content,
'SmsSdkAppId': '1400557301',
'TemplateId': jiraMessages?.body?.id,
'TemplateParamSet.0': code,
'Timestamp': Timestamp,
'Version': Version
}
// 1. 对参数排序,并拼接请求字符串
const strParam = this.sort_params(obj)
// 2. 拼接签名原文字符串
const reqMethod = "GET";
const path = "/";
const strSign = this.formatSignString(reqMethod, endpoint, path, strParam)
// 3. 生成签名串
obj['Signature'] = this.sha1(SECRET_KEY, strSign)
const req_url = this.get_req_url(obj, endpoint)
return req_url
}
public async axiosSMSUrl(phone: string, code: string, type: string) {
try {
const getUrl = await this.tencentAutograph(phone, code, type)
const sendSMSRes: any = await proxyAxios(getUrl,false,'GET')
if (sendSMSRes.Response?.SendStatusSet[0]?.Code != 'Ok') {
throw new HttpException("验证码发送失败", 500)
}
} catch {
throw new HttpException("验证码发送失败", 500)
}
}
}
V3验证算法
import { Injectable } from '@nestjs/common'
import { commonJiraJSONConfig } from './commonFun';
import { proxyAxios } from './proxyAxios';
const crypto = require('crypto')
@Injectable()
export class commonSMSService {
sha256(message, secret = '', encoding?) {
const hmac = crypto.createHmac('sha256', secret)
return hmac.update(message).digest(encoding)
}
getHash(message, encoding = 'hex') {
const hash = crypto.createHash('sha256')
return hash.update(message).digest(encoding)
}
getDate(timestamp) {
const date = new Date(timestamp * 1000)
const year = date.getUTCFullYear()
const month = ('0' + (date.getUTCMonth() + 1)).slice(-2)
const day = ('0' + date.getUTCDate()).slice(-2)
return `${year}-${month}-${day}`
}
async getBody(phone: string, code: string, type: string) {
let jiraMessagesJSON
if (type !== 'verify') {
jiraMessagesJSON = await commonJiraJSONConfig('Messages.json')
} else {
jiraMessagesJSON = await commonJiraJSONConfig('Messages.json', 573686)
}
const jiraMessages = jiraMessagesJSON[type]
return {
"PhoneNumberSet": [
`+86${phone}`
],
"SmsSdkAppId": "1400557301",
'SignName': jiraMessages?.signature?.content,
'TemplateId': jiraMessages?.body?.id,
"TemplateParamSet": [
`${code}`
]
}
}
async axiosSMSUrl(phone: string, code: string, type: string) {
// 密钥参数
const SECRET_ID = "AKIDObXDckbCWYJo1xby5uSijTw84j7D2bss"
const SECRET_KEY = "kDPKxKRHq89tLWR6UbT5RVIo31i8H9k5"
const endpoint = "sms.tencentcloudapi.com"
const service = "sms"
const region = "ap-guangzhou"
const action = "SendSms"
const version = "2021-01-11"
const timestamp = Math.round(Date.now() / 1000)
console.log(timestamp)
//时间处理, 获取世界时间日期
const date = this.getDate(timestamp)
// ************* 步骤 1:拼接规范请求串 *************
const signedHeaders = "content-type;host"
const payload = await this.getBody(phone, code, type)
const hashedRequestPayload = this.getHash(JSON.stringify(payload));
const httpRequestMethod = "POST"
const canonicalUri = "/"
const canonicalQueryString = ""
const canonicalHeaders = "content-type:application/json\n" + "host:" + endpoint + "\n"
const canonicalRequest = httpRequestMethod + "\n"
+ canonicalUri + "\n"
+ canonicalQueryString + "\n"
+ canonicalHeaders + "\n"
+ signedHeaders + "\n"
+ hashedRequestPayload
console.log(canonicalRequest)
// ************* 步骤 2:拼接待签名字符串 *************
const algorithm = "TC3-HMAC-SHA256"
const hashedCanonicalRequest = this.getHash(canonicalRequest);
const credentialScope = date + "/" + service + "/" + "tc3_request"
const stringToSign = algorithm + "\n" +
timestamp + "\n" +
credentialScope + "\n" +
hashedCanonicalRequest
console.log(stringToSign)
// ************* 步骤 3:计算签名 *************
const kDate = this.sha256(date, 'TC3' + SECRET_KEY)
const kService = this.sha256(service, kDate)
const kSigning = this.sha256('tc3_request', kService)
const signature = this.sha256(stringToSign, kSigning, 'hex')
console.log(signature)
// ************* 步骤 4:拼接 Authorization *************
const authorization = algorithm + " " +
"Credential=" + SECRET_ID + "/" + credentialScope + ", " +
"SignedHeaders=" + signedHeaders + ", " +
"Signature=" + signature
console.log(authorization)
try {
const sendSMSRes: any = await proxyAxios(`https://${endpoint}`, false, 'POST', payload,
{
"Authorization": authorization,
"Content-Type": "application/json",
"Host": endpoint,
"X-TC-Action": action,
"X-TC-Timestamp": timestamp.toString(),
"X-TC-Version": version,
"X-TC-Region": region
}
)
if (sendSMSRes.Response?.SendStatusSet?.[0]?.Code == 'Ok') {
return 'ok'
} else {
return "sendFail"
}
} catch {
return "sendFail"
}
}
}