网站虚拟空间更新缓存,哪个网站建设商招代理,建设工程有限公司起名,网站建设要后台吗目录 请思考一个问题什么是API签名认证为什么需要API签名认证如何在后端实现签名认证签名认证实现通过 http request header 头传递参数加密方式怎么知道这个签名对不对#xff1f;怎么防重放#xff1f; Go 代码实现sign.goservice.goclient.go 请思考一个问题
请思考一个重… 目录 请思考一个问题什么是API签名认证为什么需要API签名认证如何在后端实现签名认证签名认证实现通过 http request header 头传递参数加密方式怎么知道这个签名对不对怎么防重放 Go 代码实现sign.goservice.goclient.go 请思考一个问题
请思考一个重要的问题如果我们为开发者提供了一个接口却对调用者一无所知。假设我们的服务器只能允许100个人同时调用接口。如果有攻击者疯狂的请求这个接口那将极其危险。一方面这可能会损害我们的安全性另一方面也可能耗尽服务器性能影响正常用户的使用。
因此我们必须为接口设置保护措施。例如限制每个用户每秒只能调用10次接口即实施请求频次的限额控制。
现在我们设计一个方法来确定谁在调用调用 。在开发后端时我们会进行一些权限检查。比如当管理员执行删除操作时后端需要检查这个用户是否为管理员。那么是如何获取用户信息的呢是否直接从后端的session中获取但问题来了当前端调用接口的时候一定有session吗比如说是前端直接发起请求并没有登录操作没有输入用户名和密码那怎么去调用呢因此一般情况下会采用一个叫 API签名认证 的机制。
什么是API签名认证
简单的说如果你想来我家做客我不可能随便让任何陌生人进来。所以我会提前给你发一个类似请帖的东西作为授权或许可证。当你来访问我的时候你需要带上这个许可证。我可能并不认识你但我认识你的请帖。只要你有这个请帖我就允许你进来。
所以API签名认证主要包括两个过程一是签发签名二是使用签名或校验签名。
为什么需要API签名认证
保证安全性不能让任何人都能调用。适用于无需保存登录态的场景。只认签名不关注用户登录态。
如何在后端实现签名认证
需要两个东西accessKey 和 secretKey 这和用户名和密码类似不过每次调用接口都需要带上实现 无状态的请求 。这样即使你之前没有来过只要这次的状态正确你就可以调用接口。所以需要这两个东西来标识用户。
签名认证实现
在签发的过程中可以自己编写一个生成 accessKey 和 secretKey 的工具。一般来说accessKey 和 secretKey 需要尽可能复杂以防止黑客尝试破解特别是密码需要尽可能复杂无规律。
通过 http request header 头传递参数
参数1accessKey 调用的标识 userA、userB复杂、无序、无规律参数2secretKey秘钥复杂、无序、无规律该参数不能放到请求头中参数3用户请求参数参数4sign
⚠️注意千万不能把秘钥直接在服务器之间传递有可能会被拦截。
加密方式
用户参数 秘钥 签名生成算法SHA-256、SHA-3等 不可解密的值 比如abc abdedfgh sfdasidfhssdfh
怎么知道这个签名对不对
服务端用一模一样的参数和算法去生成签名只要和用户传的一致就表示一致。
怎么防重放 参数5加 nonce 随机数只能用一次服务端要保存用过的随机数 每次请求时发生一个随机数给后端。后端只接受并认可该随机数一次一旦随机数被使用过后端将不再接受相同的随机数。这种方式解决了请求重复的问题因为即使对方使用之前的时间和随机数进行请求后端会认识到该请求已经被处理过不会再次处理。 但是这种方法需要后端额外开发来保存已使用的随机数。并且如果接口的并发量很大每次请求都需要一个随机数那么可能会面临处理百万、千万甚至上亿级别请求的情况。因此除了使用随机数之外还需要其他机制来定期清理已使用的随机数。 参数6加 timestamp 时间戳检验时间戳是否过期。 每个请求在发生时携带一个时间戳并且后端会验证该时间戳是否在指定的时间范围内。例如不超过10分钟或5分钟。这可以防止对方使用昨天的请求在今天进行重放。 通过这种方式我们可以一定程度上控制随机数的过期时间。因为后端需要同时验证这两个参数只要时间戳过期 或者 随机数被使用过后端会拒绝该请求。因此时间戳可以在一定程度上减轻后端保存随机数的负担。通常情况下这两张方法可以相互配和使用。
因此在标准的签名认证算法中建议至少添加以下五个参数accessKey、secretKey、sign、nonce、timestamp。此外建议将用户请求的其他参数例如接口中的name参数也添加到签名中以增加安全性。
API签名认证是一个很灵活的设计具体要有哪些参数、参数名 一定要根据场景来。比如userId、appId、version、固定值等 类似于HTTPS协议签名认证的本质是确保密码不在服务器之间传输。因为任何在服务器之间传输的内容都有可能被拦截。所以请记住密码绝不能在服务器之间传输。
Go 代码实现
源码地址 GitHub-golang版本有对应的单元测试代码
sign.go
package utilsimport (crypto/md5encoding/hex
)// 计算API签名
func CalculateSignature(accessKey, secretKey, nonce, timestamp, requestBody string) string {// 将参数拼接成一个字符串concatenatedString : accessKey nonce timestamp requestBody secretKey// 计算 MD5 值signature : md5.Sum([]byte(concatenatedString))return hex.EncodeToString(signature[:])
}service.go
package mainimport (net/httpsimple/api-signature/service/utilsgithub.com/gin-gonic/gin
)var AccessKey, SecretKey string aaa, 123456func GetNameByGet(c *gin.Context) {name : c.Query(name)headers : c.Request.HeaderaccessKey : headers.Get(accessKey)nonce : headers.Get(nonce)timestamp : headers.Get(timestamp)sign : headers.Get(sign)if accessKey ! AccessKey {c.JSON(http.StatusForbidden, gin.H{error: 用户不存在})return}// 计算签名signature : utils.CalculateSignature(accessKey, SecretKey, nonce, timestamp, )// 验证签名if signature ! sign {c.JSON(http.StatusForbidden, gin.H{error: 签名验证失败})return}c.JSON(http.StatusOK, gin.H{data: GET 你的名字是 name})
}func main() {r : gin.New()r.GET(/api/name, GetNameByGet)r.Run(:8011)
}client.go
package clientimport (fmtiomath/randnet/httpnet/urlsimple/api-signature/client/utilsstrconvtime
)/** 生成包含N个随机数字的字符串*/
func GenetateRandomString(length int) string {// 设置随机数种子以确保每次运行生成的随机数都不同r : rand.New(rand.NewSource(time.Now().UnixNano()))// 定义一个包含数字字符的字符集charset : 0123456789charsetLength : len(charset)// 生成随机数字并拼接字符串randomString : make([]byte, length)for i : 0; i length; i {randomIndex : r.Intn(charsetLength)randomChar : charset[randomIndex]randomString[i] randomChar}return string(randomString)
}// 获得请求头
func GetRequestHeaders(accessKey, secretkey, requestBody string) http.Header {headers : make(http.Header)// 生成 nonce : 一个包含100个随机数字的字符串nonce : GenetateRandomString(100)// 当前时间戳秒级别timestamp : strconv.FormatInt(time.Now().Unix(), 10)// 计算签名signature : utils.CalculateSignature(accessKey, secretkey, nonce, timestamp, requestBody)// 设置请求头headers.Set(accessKey, accessKey)headers.Set(nonce, nonce)headers.Set(timestamp, timestamp)headers.Set(sign, signature)return headers
}func SendApi(name, accessKey, secretKey string) (statusCode int, contentType string, bodyBytes []byte, err error) {requestURL : http://localhost:8011/api/name// 构建查询字符串将其附加到URL上params : url.Values{}params.Set(name, name)// 构建包含查询参数的URLfullURL : fmt.Sprintf(%s?%s, requestURL, params.Encode())client : http.Client{}req, err : http.NewRequest(GET, fullURL, nil)if err ! nil {fmt.Println(Failed to create request, err, err)return}// 构建请求头headers : GetRequestHeaders(accessKey, secretKey, )req.Header headersresponse, err : client.Do(req)if err ! nil {fmt.Println(Failed to make request, err, err)return}defer response.Body.Close()// 读取响应体将响应体内容原封不动地返回给前端bodyBytes, err io.ReadAll(response.Body)if err ! nil {fmt.Println(Failed to read response, err, err)return}statusCode response.StatusCodecontentType response.Header.Get(Content-Type)return
}