本篇为青训营大项目笔记,用于查漏补缺,尚未整理供食用。
注:下文的第二版指的是第一个版本合代码之后的第二版即v1版,而原始仓库的v2版本应为添加grpc微服务架构的版本,本文未讨论。
阅读源码架构
与第一版v0相比,第二版v1有以下区别:
controller控制层
-
Init.go
-
- 第二版使用了嵌套的路由组来组织和注册路由,在这个版本中,所有的路由都在同一个
Init函数中注册,并使用middleware.Jwt.MiddlewareFunc()来为特定的路由组添加JWT鉴权中间件。
- 第二版使用了嵌套的路由组来组织和注册路由,在这个版本中,所有的路由都在同一个
-
- 第一版则更模块化,它将每个功能模块的路由注册分离到其自己的函数中,如
RegMessageAction。Init函数只调用了其他的注册函数,如RegExample,每个注册函数都直接为特定的路由注册了处理函数,而没有使用路由组。
- 第一版则更模块化,它将每个功能模块的路由注册分离到其自己的函数中,如
-
-
middleware即jwt中间件
- 这里分为初始化jwt中间件及jwt鉴权流程中所需要用到的工具,比如定义claims结构体,生成token,从token中获取用户 ID等
-
ctlModel和ctlFunc
- 自定义结构体均统一放置在
controller/ctlModel下,同时针对某几个结构体的类型进行变更,比如ID由之前的unit改为int64;- 原消息接口的response结构体
service服务层定义的结构体
- 使用
controller/ctlFunc包中的函数统一进行响应- response.go => controller/ctlFunc
- resp的结构体统一定义在
controller/ctlModel
- 自定义结构体均统一放置在
-
message.go
controller中的messageAction.go聊天操作及messageChatList.go聊天记录将函数Action和Chat集合到一个文件controller/message.go中,并对逻辑进行了优化。
-
dal数据层GetMessage方法进行了优化,提供preMsgTime写入日志,使用了更健全更简洁的SQL查询语句,将错误处理删除?还是转移到哪里? -
service服务层定义的结构体提取到
controller/ctlModel,由于ID由之前的unit改为int64,这里在convertMessagesToChatResponse方法对ID进行了int64(msg.ID)的类型转换。
JWT
JWT 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为 JSON 对象。这种信息可以被验证和信任,因为它是数字签名的。
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims Claims // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}
controller/middleware/jwtutil.go
-
初始化 jwtKey:
jwtUtilInit函数从全局配置中获取 JWT 密钥并初始化jwtKey变量。
-
定义 Claims 结构体:
Claims结构体扩展了 JWT 的标准声明,并添加了一个UserId字段,用于存储用户 ID。
-
生成 Token:
ReleaseToken函数接收一个用户 ID,并使用该 ID 生成一个新的 JWT。此函数返回生成的 token 或任何错误。
-
获取用户 ID:
GetUserID函数从上下文中提取用户 ID。如果 JWT 中间件未在路由中使用,或者上下文中不存在用户 ID,该函数将记录一个错误并返回 0。
-
解析 Token:
ParseToken函数接收一个 token 字符串,并尝试使用 JWT 密钥来解析它。该函数返回解析的 token、声明和任何错误。
-
从 Token 中获取用户 ID:
GetUserIDFromToken函数解析 token 并从中提取用户 ID。
生成 Token:
ReleaseToken 函数接收一个用户 ID,并使用该 ID 生成一个新的 JWT。此函数返回生成的 token 或任何错误。
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
这里的生成逻辑是由JWT 软件包 - github.com/dgrijalva/jwt-go - Go 软件包提供的
func NewWithClaims(method SigningMethod, claims Claims) *Token
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims Claims // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}
jwt.NewWithClaims 是 github.com/dgrijalva/jwt-go 包中用于创建一个新的 JWT(JSON Web Tokens)的方法。这个方法接受一个签名方法(如 jwt.SigningMethodHS256)和一组声明(如 claims)作为参数,并返回一个未签名的 token。
让我们深入了解这里的每个参数:
-
SigningMethodHS256:
- 这是 HMAC 使用 SHA-256 哈希算法的签名方法。
- JWT 支持多种签名方法,其中 HS256(HMAC 使用 SHA-256)是最常用的一种。
- 使用 HMAC 进行签名意味着你需要一个秘密密钥(在您的代码中,这个密钥是
jwtKey)。 - 当你创建一个新的 JWT 时,它会使用这个秘密密钥和 HS256 算法对 JWT 的头部和有效载荷进行签名。签名的结果是 JWT 的第三部分,即签名部分。
-
claims:
- 声明是 JWT 的有效载荷。它是一个键值对的集合,可以包含任何你想在 token 中传递的信息。
- 在您的代码中,
claims包括一个UserId和一些标准声明,如ExpiresAt、IssuedAt、Issuer和Subject。 - 这些声明将被编码为 JSON,并成为 JWT 的第二部分,即有效载荷部分。
如何生成 token:
- 首先,JWT 的头部和有效载荷部分分别被 Base64Url 编码。
- 这两个编码后的字符串通过一个点号 (
.) 连接在一起,形成一个未签名的 token。 - 然后,使用提供的签名方法(HS256)和秘密密钥对未签名的 token 进行签名,生成签名。
- 最后,签名被添加到未签名的 token 后面,再次通过一个点号 (
.) 连接,形成完整的 JWT。
当验证 JWT 时,可以使用相同的密钥和签名方法对 token 进行签名,并与提供的签名进行比较,以验证 token 的完整性和真实性。
controller/middleware/jwtmiddleware.go
使用 github.com/hertz-contrib/jwt 的第三方库来实现 JWT 功能。
从 token 提取用户信息
IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
claims := jwt.ExtractClaims(ctx, c)
return claims[identityKey]
},
认证使用md5
password := encrypts.Md5(reqObj.Password + global.Config.PasswordSalt)
登陆成功后为向 token 中添加自定义负载信息PayloadFunc
登录校验成功,将token返回给前端
LoginResponse使用ctlModel和ctlFunc构造resp
鉴权Authorizator单一角色 不设权限校验
设置 jwt 校验流程发生错误时响应所包含的错误信息HTTPStatusMessageFunc
调用ctlFunc构造jwt 验证流程失败的响应函数Unauthorized
GLOBAL/CONFIG 新增
-
AliOSS 结构体:
-
这个结构体可能是用于配置阿里云的对象存储服务 (OSS)。以下是该结构体中字段的简要描述:
Bucket: OSS 的存储空间名称。Endpoint: OSS 的服务入口地址。AccessKeyId: 访问OSS的API的ID。AccessKeySecret: 访问OSS的API的密钥。
-
这些字段通常用于SDK或API调用,以连接和与OSS服务进行交互。
-
-
PasswordSalt 字段:
- 这个字段是一个字符串,可能是用于加密或哈希密码的盐。使用盐可以增加哈希函数的复杂性,并使每个用户的哈希密码唯一,即使他们的实际密码相同。
待解决的问题
- utils中的conv应用场景?
- 数据流向变化待整理。
- 现在调用的jwt-go有新版本,为什么选旧版本?以及为什么初始化jwt要选用hertz框架的jwt包?除了这个包之外还有哪些技术选型的选项?选择hertz框架的jwt包比其他选项好在哪里?
参考资料
JWT 软件包 - github.com/dgrijalva/jwt-go - Go 软件包 hertz-contrib/jwt: JWT middleware for Hertz (github.com)