# 验签中间件

在 `go-zero` 负责通过签名来验证请求是否合法的中间件：`contentsecurityhandler` 


## 密钥参数
| **params** | **参数说明** |
| --- | --- |
| fingerprint | 指纹字符串 |
| publicKey | 公钥 |
| privateKey | 私钥 |
| key | 私钥密码 |



## 如何使用


### example


```go
engine.AddRoute(rest.Route{
    Method:  http.MethodGet,
    Path:    "/",
    Handler: handle,
},rest.WithSignature(rest.SignatureConf{
    Strict:      true,      // 是否严格模式
    Expiry:      time.Hour, // 允许的最大时间跨度
    PrivateKeys: []rest.PrivateKeyConf{
        rest.PrivateKeyConf{
            Fingerprint: "", // your Fingerprint
            KeyFile:     "", // your PrivateKey
        },
    },
}))
```




### 自定义验签失败回调函数


```go
engine := rest.MustNewServer(c.RestConf,rest.WithUnsignedCallback(func(w http.ResponseWriter, r *http.Request, next http.Handler, strict bool, code int) {
		// your own custom callback function
}))
```


## 签名生成规则


![image.png](https://cdn.nlark.com/yuque/0/2020/png/112627/1603433428382-4e310d52-f07a-49f9-b676-e339b616baea.png#align=left&display=inline&height=906&margin=%5Bobject%20Object%5D&name=image.png&originHeight=906&originWidth=1274&size=112330&status=done&style=none&width=1274#align=left&display=inline&height=906&margin=%5Bobject%20Object%5D&originHeight=906&originWidth=1274&status=done&style=none&width=1274)


需要在http header中设置 X-Content-Security  值为 `key=yourFingerprint;secret=yourSecret;signature=yourSignature`


```go
r.Header.Set("X-Content-Security", strings.Join([]string{
    fmt.Sprintf("key=%s", fingerprint),
    fmt.Sprintf("secret=%s", secret),
    fmt.Sprintf("signature=%s", signature),
}, "; "))
```


- key 指纹字符字符串




取值为密钥参数中的 fingerprint 值


- secret 生成规则

   1. 将type=? ,key =base64(私钥密码),time=当前时间戳  以 ; 分割成字符串
      - type => 取值范围：0或1，
      - key => 私钥密码的base64值
      - time =>  当前时间戳
   2. 再用公钥对所得的字符串进行 rsa加密 得到base64字符串



```go
//1.将以上三个值以;分割
content := strings.Join([]string{
		fmt.Sprintf("type=%s", mode),
		fmt.Sprintf("key=%s", base64.StdEncoding.EncodeToString(key)), //对私钥密码做base64加密
		fmt.Sprintf("time=%s", strconv.FormatInt(time.Now().Unix(), 10)),
	}, "; ")
//用公钥对所得字符串进行rsa加密取base64值
encrypter, _ := codec.NewRsaEncrypter([]byte(pubKey))
output, _ := encrypter.Encrypt([]byte(content))
secret := base64.StdEncoding.EncodeToString(output)
```


- signature  生成规则

   1. 将 timestamp,method,path,query,bodySign 的顺序 以换行(\n)分割成字符串
      - timestamp  当前时间戳
      - method  http 请求方式（GET/POST/PUT/....）
      - path  请求路径；例如 [https://test.com/a/b?c=d&e=f](https://test.com/a/b?c=d&e=f)   path 为 /a/b
      - query url参数 如上 query 为 c=d&e=f
      - bodySign 对request body 进行sha256加密
   2. 再将此字符串用你的key 做 hmac 后 再base64加密



```go
//1.按timestamp,method,path,query,bodySign的顺序 以换行(\n)分割成字符串
str := strings.Join([]string{
  "1602745942",
  "POST",
  "/a/b",
  "c=d&e=f",
  "49adb43b1c44f36c046357da1ff548d739e89c8cc75cbe4536752c476dad0761"
}, "\n")

func Hmac(key []byte, body string) []byte {
	h := hmac.New(sha256.New, key)
	io.WriteString(h, body)
	return h.Sum(nil)
}

func HmacBase64(key []byte, body string) string {
	return base64.StdEncoding.EncodeToString(Hmac(key, body))
}
//2.然后再将此字符串用你的key做hmac后再base64加密
sign:= HmacBase64("yourKey",str)
```


## js示例


```javascript
/**
 * 签名
 * @param {*} url 相对路径的地址
 * @param {*} query url 参数
 * @param {*} method 请求方式
 * @param {*} data 请求体
 */
var httpSign = function (url, query, method, data) {
  	// 随机生成秘钥
    var key = uuid.v4(); 
  	// 请求体要是string
    var newData = typeof data === 'string' ? data : JSON.stringify(data);
		// 拿到当前时间戳
    var timestamp = Date.parse(new Date().toString()) / 1000;
  	// 对请求体做 sha256 加密
    var hexString = sha256(newData);
    var signatureData = [timestamp.toString(), method.toUpperCase(), url, query, hexString];
    var secretData = ["version=" + version, "type=0", "key=" + base64Encode(key), "time=" + timestamp];
  	// 使用公钥对 secretData 以;分割的字符串进行 rsa 加密
    var secret = rsaEncrypt(secretData.join(';'), PublicKey);
  	// 对signatureData 以 \n 分割的字符串先hmac在进行 sha256 加密
    var signature = hmacSHA256(signatureData.join('\n'), key);
  	// res 就是所得的放在请求头 X-Content-Security 中的签名字符串
    var res = "key=" + fingerprint + ";secret=" + secret + ";signature=" + signature;
    return res;
};
```

<Vssue title="验签中间件" />

