本文是 从 Flask 到 Gin 系列的第 5 篇。


Python 提供了一个有趣且有用的语法糖:装饰器。使用装饰器,我使用装饰器在 Flask 中实现了鉴权:用户进行数据提交的时候,需要提供一个 TOKEN,这个 TOKEN 如果解密成功,就正常进行 response 相关的逻辑,反之则直接返回 403 错误。

鉴权逻辑就是使用 Python 的装饰器实现的。但 Golang 没有装饰器特性,我会使用 Gin 的中间件机制来代替它。

实现 mjst_checker 装饰器

mjst_checker 是一个装饰器,经过它装饰的路由方法,会自动处理请求中带来的名为 mjst 的 token,token 中包含用户的权限信息,还有 token 有效期等等信息,如果信息正常,那么路由会继续正常处理,否则会得到 403 错误。

来看看 mjst_checker 装饰器的定义:

from functools import wraps

class PYConf(dict):
    """基于 Python dict 的配置文件。

    dict 默认不适合当作配置文件对象使用。如要有下面几点不便:

    #. 对于不存在的 key,会 raise KeyError 错误;
    #. dict不能使用 ``.`` 语法访问。

    :class:`PYConf` 解决了这些问题,还另外提供了一些方法在使用上更加方便。

    """

    def __missing__(self, key):
        return None

    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        self[name] = value

    def __delattr__(self, name):
        del self[name]


def mjst_checker(*typeids):
    """ MJST 的检测器,检测 HEADER 中传来的 MJST 是否正常
    :param typeids: typeid 数组
    """

    def decorator(f):
        @wraps(f)
        def decorated_fun(*args, **kwargs):
            # _get_mjst 从请求中获取 mjst 的值
            mjst = _get_mjst()
            # 将要加入 kwargs 中的mjst解析后的值,默认是一个错误,代表没有解析成功
            mjstarg = PYConf({'error': True, 'code': 403, 'message': 'MJST DECODE ERROR!'})
            try:
                # _decode_mjst 为具体的解密方法
                mjstobj = _decode_mjst(typeids, mjst)
                if mjstobj is not None:
                    mjstarg = mjstobj
            except Exception as e:
                logger.error('@mjst_checker decode_mjst:%s error:%s', mjst, str(e))
            # 此处的 mjstarg 一定为非 None,至少包含上面提供的 403 错误。这里不使用 HTTP 403,让客户端知道出了什么错
            kwargs['mjst'] = mjstarg
            return f(*args, **kwargs)

        return decorated_fun

    return decorator

其中涉及的 _get_mjst_decode_mjst 方法的作用已经在注释中解释过了,具体实现就不提供了。让我们看看 mjst_checker 这个装饰器应该怎么使用。

mjst_checker 的用法

从 Flask 到 Gin —— SQLAlchemy 和 gorm 中我们定义了两个路由方法,registeractive 是没有增加鉴权功能的,现在可以补全了:

@audible.route('/register/', methods=['GET'])
@mjst_checker(51, 55)
def register(mjst):
    """ 获取注册数据
    """
    if mjst.error:
        return responseto(data=mjst)
    return _response_register_or_active(LogRegister)


@audible.route('/active/', methods=['GET'])
@mjst_checker(51, 55)
def active(mjst):
    """ 获取活跃数据
    """
    if mjst.error:
        return responseto(data=mjst)
    return _response_register_or_active(LogActive)

如果 mjst 解析失败,得到的响应是一个 http code 200,但返回的 json 中的 code 值为 403。responseto 的具体定义见 从 Flask 到 Gin —— 处理 JSON

实现 MjstChecker 中间件

Gin 为我们提供了中间件支持,让我们可以在请求收到以及响应之间做许多事。看看 MjstChecker 这个中间件的定义:

package middlewares

import (
	"net/http"
	"github.com/gin-gonic/gin"
)
// MjstChecker 检查 mjst 的权限是否匹配
// typeids 支持的 usertype
func MjstChecker(typeids []int) gin.HandlerFunc {
	return func(c *gin.Context) {
        // getMjst 从请求中获取 mjst 的值
		mjstObj := getMjst(typeids, c)
		if mjstObj == nil {
			c.AbortWithStatusJSON(http.StatusOK, gin.H{
				"error":   true,
				"code":    403,
				"message": "MJST DECODE ERROR!",
			})
			return
		}
		c.Set("mjst", mjstObj)
		c.Next()
	}
}

getMjst 在注释中已经说明了用途,也就不提供具体定义了。和 Python 版本不同的是,这里把获取 mjst 请求参数以及解密 token 写到了一个方法中。如果解析失败的话,会直接返回一个 code 403 的 JSON 响应。

MjstChecker 的用法

从 Flask 到 Gin —— SQLAlchemy 和 gorm 中,我们定义了初始化路由的方法 InitAudible。现在进行一点点修改,将 MjstChecker 这个中间件串联到路由方法前面,增加鉴权功能:

package routers

import (
	"mjp/middlewares"
	"github.com/gin-gonic/gin"
)
// InitAudible register the audible api
func InitAudible(router *gin.RouterGroup) {
    router.GET("/register", middlewares.MjstChecker([]int{51, 55}), AudibleRegister)
	router.GET("/active", middlewares.MjstChecker([]int{51, 55}), AudibleActive)
}

如果鉴权失败,客户端收到的结果为:

{
  "code": 403,
  "error": true,
  "message": "MJST DECODE ERROR!"
}

参考


阅读系列所有文章:从 Flask 到 Gin

全文完