一、背景
本项目是典型的前后端分离项目,为了维持登录态和用户行为鉴权,因 app 不支持cookie,且就算模拟支持也存在模拟的 cookie 被劫持修改的风险,故传统的 cookie-session
已经不适用于前后端分离的项目。JWT(Json Web Token) 因其不可篡改、不占内存等特性已经成为流行的前后端分离项目鉴权方法。
笔者所在小组因使用 Gin
作为 HTTP 框架,故 JWT 鉴权采用了第三方扩展 appleboy/gin-jwt 实现。
二、不能实现的需求
在实现用户、评论、点赞等接口时,因请求的 token 都在 query
中,故只需将gin-jwt
的鉴权中间件注册在需要鉴权的路由中即可由插件自动完成鉴权。
2.1 验证失败的返回
因 API 要求不论接口成功请求与否,都要求 HTTP 返回报文状态码为 200
。且返回信息的 json 字段 必须为 status_code
与 status_msg
,插件验证失败的默认返回不满足这些要求,实例化插件时,对其默认配置需要进行简单的修改:
jwt.GinJWTMiddleware {
...
// 未通过鉴权时返回信息
Unauthorized: func(c *gin.Context, code int, message string) {
c.JSON(http.StatusOK, gin.H{
"status_code": code,
"status_msg": message,
})
},
...
}
仅展示核心部分,完整代码见文末项目仓库:
cmd/api/middleware/auth/config.go
2.2 form-data 的支持
发布作品时,还是用插件自带的中间件鉴权注册到路由后进行开发,在测试时却发现鉴权失败,后台根本获取不到 token。看了插件的文档及issue
,发现也有青训营的其他同学遇到了同样的问题并给出了解决办法:https://github.com/appleboy/gin-jwt/issues/292#issuecomment-1136985205 。
简要概括就是因上传的方法为POST
,参数格式为multipart/form-data
,token 参数也以文本的形式一起传输。但是插件并不支持获取 multipart/form-data
格式中的参数,所以会导致鉴权失败。该同学给出的办法是修改插件源码使之支持,并且他也给插件提交了PR
。
但是截止笔者写代码时,该PR
还没被合并。笔者考虑到若直接修改源码,会导致应用部署拉取依赖时拉取的还是未修改的代码。所以考虑将插件中本项目中间件鉴权需要的部分剥离出来,再加上支持获取 multipart/form-data
格式的代码,构建一个新的中间件供视频上传鉴权使用。
这里就不贴代码了,详情见文末项目仓库地址:
cmd/api/middleware/auth/form_auth.go
同时要修改生成实例时的配置信息,添加 form: token
jwt.GinJWTMiddleware{
...
TokenLookup: "query: token, form: token",
}
仅展示核心部分,完整代码见文末项目仓库:
cmd/api/middleware/auth/config.go
2.3 视频流请求的选择
因视频流获取的请求既可带 token(登录情况下),又可不带 token(未登录情况下)。带 token 的请求就需要鉴权,不带 token 的请求就无需鉴权直接请求视频流。现有中间件无法实现此需求。故笔者实现了一个中间件并将其设置为视频流路由中的临时中间件来实现这个需求。
- 实现思路
获取请求中的 token 值,若为空,直接请求视频流的逻辑层;若不为空,则走 2.2 中实现的鉴权中间件。
- 代码展示
func SelectMiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.Query("token")
if token != "" {
formMiddlewareImpl(c)
}
}
}
三、退出登录的思考
JWT 拥有种种优点,但因其不能被修改的特点,也会带来相应的弊端。
用户成功登录后会获取一个 jwt 形式的 token 用来维持登录态、鉴权等,若用户在 token 过期前退出登录,虽然退出但此前发布给他的 token 依然有效。若用户频繁进行登录、退出操作则会产生大量有效的 token。这些 token 若泄露则对用户安全构成威胁。
针对这种情况有几种办法:
- 缩短 token 过期时间
- 限制 token 使用次数
- 构建白名单。登录的 token 都存在 redis 里
- 构建黑名单。退出登录的 token 都存在 redis 里
就本项目来说,第一种可以有效避免这种情况,但会使用户登录很快过期,使用户频繁登录,影响用户体验;
第二种需要用 redis 维护一个使用次数,违背了 JWT 不占内存这个最大的优点;
第三种方法同第二种;
第四种方法兼顾了不占内存和用户体验,是笔者认为最优的处理方法。
【注】:本项目中用户退出登录后端不可感知,故本章内容只是简要科普,项目中并不涉及实现。
四、后记
-
都看到这儿了,求个 star 呗~ https://github.com/ByteDanceCamp/micro_tiktok
-
最近“正直讲史”看多了,章节标题起名都有李正老师哪味了23333