在完成中间件的介绍和日志中间件的代码后,我们的程序已经基本能正常跑通了,但如果要上生产,还少了一些必要的功能,例如鉴权、异常捕捉等。本章我们介绍如何编写鉴权中间件。

鉴权访问,说白了就是给用户的请求增加一些限制条件,过滤掉不符合要求的请求。完善的鉴权模块可以让我们的服务跑得更加安全,特别是面向公共的服务。

常用的无状态鉴权方式

  • 网络鉴权

通常有IP白名单方式,通过获取客户端的真实IP来对请求进行过滤

  • 用户鉴权

通过账号密码或者分配的密钥、Token等方式进行认证,常用的cookies、oauth2.0都是这种方式

  • 加密算法鉴权

客户端使用加密算法对用户的参数进行计算加密得到Token,并将参数和Token一起发送;服务端使用同样的加密算法对请求参数进行加密后比较Token的值是否一致。

Gin-IPs 鉴权访问

为了让我们的服务更加安全,我们通常是混合多种鉴权方式使用。本文采取“用户鉴权”和“加密算法鉴权”混合方式。

  • 鉴权算法介绍

通过某种方式生成或者手动指定分配公钥和私钥对给用户,用户使用公钥和请求参数组成消息内容,使用私钥对消息内容进行哈希计算,得到固定长度的字符串。

服务器使用同样的方式对用户请求的参数进行哈希计算后比较。由于这里面的算法和密钥对都是私有的,所以安全性较高,适用于大多数场景。

  • 加密代码
  1. // 签名算法如下
  2. /*
  3. Signature = HMAC-SHA1('SecretKey', UTF-8-Encoding-Of( StringToSign ) ) );
  4. StringToSign = method + "\n" +
  5. URL + "\n" +
  6. Sort-UrlParams + "\n" +
  7. Content-MD5 + "\n" + // md5(params)
  8. Expires + "\n" +
  9. AccessKey;
  10. */
  11. func genSignature(accessKey, secretKey, uri, method, urlParams, params, nowTS string) (string, error) {
  12. if params != "" {
  13. md5Ctx := md5.New()
  14. _, _ = io.WriteString(md5Ctx, params)
  15. params = fmt.Sprintf("%x", md5Ctx.Sum(nil))
  16. }
  17. // HTTP-Verb + "\n" +URL + "\n" +Parameters + "\n" +Content-Type + "\n" +Content-MD5 + "\n" +Date + "\n" +AccessKey;
  18. strSign := method + "\n" + uri + "\n" + urlParams + "\n" + "\n" + params + "\n" + nowTS + "\n" + accessKey
  19. sign := hmacSHA1Encrypt(strSign, secretKey)
  20. return sign, nil
  21. }
  22. // hmacSHA1Encrypt encrypt the encryptText use encryptKey
  23. func hmacSHA1Encrypt(encryptText, encryptKey string) string {
  24. key := []byte(encryptKey)
  25. mac := hmac.New(sha1.New, key)
  26. mac.Write([]byte(encryptText))
  27. var str = hex.EncodeToString(mac.Sum(nil))
  28. return str
  29. }
  • Gin鉴权中间件使用
  1. func Validate() gin.HandlerFunc {
  2. return func(c *gin.Context) {
  3. response := route_response.Response{}
  4. response.Data.List = []interface{}{} // 初始化为空切片,而不是空引用
  5. uri := c.Request.URL.Path
  6. contentType := c.Request.Header.Get("Content-Type")
  7. accessKey := c.DefaultQuery("accesskey", "")
  8. expires := c.DefaultQuery("expires", "")
  9. signature := c.DefaultQuery("signature", "")
  10. secret, err := dao.FetchSecret(accessKey)
  11. if err != nil || "valid" != secret.State {
  12. c.Abort()
  13. response.Code, response.Message = configure.RequestKeyNotFound, "无效的Token"
  14. c.JSON(http.StatusUnauthorized, response)
  15. return
  16. }
  17. secretKey := secret.SecretKey
  18. if nowTs, err := strconv.ParseInt(expires, 10, 64); err != nil {
  19. c.Abort()
  20. response.Code, response.Message = configure.RequestParameterTypeError, "有效期参数类型错误"
  21. c.JSON(http.StatusUnauthorized, response)
  22. return
  23. } else {
  24. passTime := time.Now().Unix() - nowTs
  25. if passTime < 0 || passTime >= configure.GinConfigValue.Expires {
  26. c.Abort()
  27. response.Code, response.Message = configure.RequestExpired, "请求已过期"
  28. c.JSON(http.StatusUnauthorized, response)
  29. return
  30. }
  31. }
  32. method := strings.ToUpper(c.Request.Method)
  33. var urlParams, params string
  34. if "POST" == method || "PUT" == method {
  35. body, _ := ioutil.ReadAll(c.Request.Body)
  36. c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) // 重设body
  37. params = string(body)
  38. } else if "GET" == method || "DELETE" == method {
  39. queryParams := c.Request.URL.Query()
  40. allParams := make(map[string]string)
  41. for k, v := range queryParams {
  42. if k != "accesskey" && k != "expires" && k != "signature" {
  43. allParams[k] = v[0] // 如果某个key传入了2个,只用第一个的值
  44. }
  45. }
  46. keys := getMapKeysSorted(allParams)
  47. for _, k := range keys {
  48. urlParams += k + allParams[k]
  49. }
  50. }
  51. if signatureString, err := genSignature(accessKey, secretKey, uri, method, urlParams, params, expires); err != nil {
  52. c.Abort()
  53. response.Code, response.Message = configure.ApiGenSignatureError, "API内部错误"
  54. c.JSON(http.StatusUnauthorized, response)
  55. return
  56. } else {
  57. if signature != signatureString {
  58. c.Abort()
  59. response.Code, response.Message = configure.RequestAuthorizedFailed, "API认证失败"
  60. c.JSON(http.StatusUnauthorized, response)
  61. return
  62. }
  63. }
  64. c.Next()
  65. }
  66. }

本文关于鉴权访问的介绍和使用到此为止,下一章我们将使用异常捕捉中间件来完善我们的程序。

Github 代码

请访问 Gin-IPs 或者搜索 Gin-IPs

【Gin-API系列】Gin中间件之鉴权访问(五)的更多相关文章

  1. 微服务从代码到k8s部署应有尽有系列(三、鉴权)

    我们用一个系列来讲解从需求到上线.从代码到k8s部署.从日志到监控等各个方面的微服务完整实践. 整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中 ...

  2. SpringCloud 2020.0.4 系列之 JWT用户鉴权

    1. 概述 老话说的好:善待他人就是善待自己,虽然可能有所付出,但也能得到应有的收获. 言归正传,之前我们聊了 Gateway 组件,今天来聊一下如何使用 JWT 技术给用户授权,以及如果在 Gate ...

  3. Go+gRPC-Gateway(V2) 微服务实战,小程序登录鉴权服务(五):鉴权 gRPC-Interceptor 拦截器实战

    拦截器(gRPC-Interceptor)类似于 Gin 中间件(Middleware),让你在真正调用 RPC 服务前,进行身份认证.参数校验.限流等通用操作. 系列 云原生 API 网关,gRPC ...

  4. 自定义分布式RESTful API鉴权机制

    微软利用OAuth2为RESTful API提供了完整的鉴权机制,但是可能微软保姆做的太完整了,在这个机制中指定了数据持久化的方法是用EF,而且对于用户.权限等已经进行了封装,对于系统中已经有了自己的 ...

  5. SpringBoot系列: Web应用鉴权思路

    ==============================web 项目鉴权============================== 主要的鉴权方式有:1. 用户名/密码鉴权, 然后通过 Sess ...

  6. Go+gRPC-Gateway(V2) 微服务实战,小程序登录鉴权服务(六):客户端基础库 TS 实战

    小程序登录鉴权服务,客户端底层 SDK,登录鉴权.业务请求.鉴权重试模块 Typescript 实战. 系列 云原生 API 网关,gRPC-Gateway V2 初探 Go + gRPC-Gatew ...

  7. # RESTful登录(基于token鉴权)的设计实例

    使用场景 现在很多基于restful的api接口都有个登录的设计,也就是在发起正式的请求之前先通过一个登录的请求接口,申请一个叫做token的东西.申请成功后,后面其他的支付请求都要带上这个token ...

  8. 深入理解k8s中的访问控制(认证、鉴权、审计)流程

    Kubernetes自身并没有用户管理能力,无法像操作Pod一样,通过API的方式创建/删除一个用户实例,也无法在etcd中找到用户对应的存储对象. 在Kubernetes的访问控制流程中,用户模型是 ...

  9. 👮 Golang Gin/Ace/Iris/Echo RBAC 鉴权库

    GRBAC 项目地址: https://github.com/storyicon/grbac Grbac是一个快速,优雅和简洁的RBAC框架.它支持增强的通配符并使用Radix树匹配HTTP请求.令人 ...

随机推荐

  1. 每日一道 LeetCode (5):最长公共前缀

    前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee: https://gitee.com ...

  2. jsp应用-实现用户登陆功能

    项目结构 1.login.jsp 这个没什么好说的,把表单提交到校验页面进行校验 2.首先获取request域中user,password,然后进行校验,校验成功把信息存入session域,然后转发到 ...

  3. 关于json 是字符串还是对象的问题

    是用ajax提交的时候,json应该是字符串形式: 响应的内容,根据设置处理不同,可能是对象形式:也可能是字符串形式. 如果是字符串形式,可转化成对象形式 再进行处理. 以下常用的几个转换函数:看名字 ...

  4. 极简 Node.js 入门 - Node.js 是什么、性能有优势?

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  5. Git本地仓库基本操作

    目录 设置姓名和邮箱 创建仓库 提交本地代码 .gitignore git add git commit git status git diff 查看提交记录 撤销未提交的修改 版本回退 设置姓名和邮 ...

  6. LinuX操作系统基础------>软件的安装方式,chmod权限,vi编辑器的使用

    RPM包管理 -rpm命令管理 RPM包管理 -yum在线管理 文件权限管理 vi编辑器的使用和常用的快捷键 1.RPM包管理: 一种用于互联网下载包的打包及安装工具,RPM包管理分为rpm命令管理和 ...

  7. jquery 事件对象笔记

    jQuery元素操作 设置或获取元素固有属性   获取               prop(属性名)    修改               prop(属性名,值) 获取自定义属性          ...

  8. C++socket编程write()、read()简介及与send()、recv()的区别

    1. write 函数原型:ssize_t write(int fd, const void*buf,size_t nbytes)write函数将buf中的nbytes字节内容写入文件描述符fd.成功 ...

  9. 2020-08-02:输入ping IP 后敲回车,发包前会发生什么?

    福哥答案2020-08-02: 首先根据目的IP和路由表决定走哪个网卡,再根据网卡的子网掩码地址判断目的IP是否在子网内.如果不在则会通过arp缓存查询IP的网卡地址,不存在的话会通过广播询问目的IP ...

  10. C#LeetCode刷题之#122-买卖股票的最佳时机 II(Best Time to Buy and Sell Stock II)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4032 访问. 给定一个数组,它的第 i 个元素是一支给定股票第  ...