go微服务框架Kratos笔记(七)使用jwt认证中间件
引言
Json web token (JWT) 是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
jwt构成
JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串,就像这样
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEiLCJkaWZmZXJlbnRpYXRlIjoiMSIsImV4cCI6MTYzNjUyMTYzNywiaXNzIjoiTWljcm9zZXJ2aWNlLkJGRiJ9.5LQ_mBJLkHUxjSqoE8evh7_UahrK8WqLgvhpZ2HIIiI
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)
header
jwt的头部承载两部分信息
typ:声明类型
alg:声明加密的算法
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

playload
载荷就是存放有效信息的地方,这些有效信息包含三个部分
- 标准中注册的声明
- 公共声明
- 私有声明
标准声明(建议但不强制使用):
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共声明:
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有声明:
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
然后将其进行base64加密,得到Jwt的第二部分。

signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header(base64后的)
- payload(base64后的)
- secret
此部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
将这三部分用.连接成一个完整的字符串,构成了最终的jwt
在kratos框架中的使用
实现jwt自签逻辑
新建auth授权方法,可以在用户登录成功后调用Auth方法传入参数后返回jwt
package biz
import (
"kanxun_bff/internal/conf"
"time"
"github.com/golang-jwt/jwt"
jwtv4 "github.com/golang-jwt/jwt/v4"
)
type AuthUseCase struct {
key string
}
func NewAuthUseCase() *AuthUseCase {
return &AuthUseCase{
key: "testKey",
}
}
type MyClaims struct {
UserName string `json:"username"`
jwtv4.StandardClaims
}
func (receiver AuthUseCase) Auth(username string) (string, error) {
myClaims := MyClaims{
username,
jwtv4.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 2).Unix(), //设置JWT过期时间,此处设置为2小时
Issuer: conf.GetConfig().GetString("project.name"), //设置签发人
},
}
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaims)
//加盐
return claims.SignedString([]byte(receiver.key))
}
自定义jwt中间件
本文使用自定义jwt中间件,未使用kratos官方提供的jwt
kratos jwt middleware
// NewAuthServer jwt Server中间件
func NewAuthServer(authUc *biz.AuthUseCase) func(handler middleware.Handler) middleware.Handler {
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
var jwtToken string
if md, ok := metadata.FromIncomingContext(ctx); ok {
jwtToken = md.Get("x-md-global-jwt")[0]
} else if header, ok := transport.FromServerContext(ctx); ok {
jwtToken = strings.SplitN(header.RequestHeader().Get("Authorization"), " ", 2)[1]
} else {
// 缺少可认证的token,返回错误
return nil, auth.ErrAuthFail
}
token, err := authUc.CheckJWT(jwtToken)
if err != nil {
// 缺少合法的token,返回错误
return nil, auth.ErrAuthFail
}
ctx = context.WithValue(ctx, "username", token["username"])
ctx = context.WithValue(ctx, "differentiate", token["differentiate"])
reply, err = handler(ctx, req)
return
}
}
}
// NewAuthClient jwt Client中间件
func NewAuthClient(authUc *biz.AuthUseCase) middleware.Middleware {
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (interface{}, error) {
if header, ok := transport.FromServerContext(ctx); ok {
//如果Token为空则生成token,如果
if header.RequestHeader().Get(authorizationKey) != "" {
jwtToken := strings.SplitN(header.RequestHeader().Get(authorizationKey), " ", 2)[1]
//元数据传递
ctx = metadata.AppendToOutgoingContext(ctx, "x-md-global-jwt", jwtToken)
} else {
token, err := authUc.Auth(
req.(*user.GetUserNameRequest).Username,
req.(*user.GetUserNameRequest).Differentiate,
)
if err != nil {
return nil, jwt.ErrGetKey
}
ctx = metadata.AppendToOutgoingContext(ctx, "x-md-global-jwt", token)
}
}
return handler(ctx, req)
}
}
}
路由白名单与jwt中间件的引入
//client
conn, err := grpc.DialInsecure(
context.Background(),
grpc.WithEndpoint("discovery:///xxx.xxx.grpc"),
grpc.WithDiscovery(r),
grpc.WithMiddleware(
recovery.Recovery(),
logging.Client(logger), //日志中间件,
tracing.Client(), //链路追踪中间件
metadata.Client(), //元数据传递中间件
jwt.NewAuthClient(authUc), //jwt中间件
),
)
//server
var opts = []grpc.ServerOption{
grpc.Middleware(
recovery.Recovery(), //异常恢复中间件
tracing.Server(), //链路追踪中间件
logging.Server(logger), //日志中间件
metrics.Server(), //监控中间件
validate.Validator(), //参数校验
metadata.Server(), //元数据中间件
selector.Server( //jwt中间件
jwt.NewAuthServer(authUc),
).Match(func(operation string) bool {
// 白名单
r, err := regexp.Compile("/api.user.v1.User/GetUserName")
if err != nil {
// 自定义错误处理
return true
}
return r.FindString(operation) != operation
}).Build(),
),
}
下游服务获取用户信息
value := ctx.Value("username")
fmt.Println(value)
如有错误请留言反馈
References
https://www.jianshu.com/p/576dbf44b2ae
https://zhuanlan.zhihu.com/p/86937325
https://go-kratos.dev/docs/component/middleware/auth
https://go-kratos.dev/docs/component/metadata
go微服务框架Kratos笔记(七)使用jwt认证中间件的更多相关文章
- go微服务框架Kratos笔记(一)入门教程
kratos简介 Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关功能及工具 本文基于kratos v2.0.3,windows平台,其他系统平台均可借鉴参考 环境搭建 Golang开发 ...
- go微服务框架Kratos笔记(六)链路追踪实战
什么是链路追踪 借用阿里云链路追踪文档来解释 分布式链路追踪(Distributed Tracing),也叫 分布式链路跟踪,分布式跟踪,分布式追踪 等等,它为分布式应用的开发者提供了完整的调用链路还 ...
- go微服务框架Kratos笔记(三)引入GORM框架
介绍 GORM是一个使用Go语言编写的ORM框架.中文文档齐全,对开发者友好,支持主流数据库. GORM官方文档 安装 go get -u github.com/jinzhu/gorm 在kratos ...
- go微服务框架kratos学习笔记七(kratos warden 负载均衡 balancer)
目录 go微服务框架kratos学习笔记七(kratos warden 负载均衡 balancer) demo demo server demo client 池 dao service p2c ro ...
- kratos微服务框架学习笔记一(kratos-demo)
目录 kratos微服务框架学习笔记一(kratos-demo) kratos本体 demo kratos微服务框架学习笔记一(kratos-demo) 今年大部分时间飘过去了,没怎么更博和githu ...
- go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时])
目录 go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时]) 静态配置 flag注入 在线热加载配置 远程配置中心 go微 ...
- # go微服务框架kratos学习笔记六(kratos 服务发现 discovery)
目录 go微服务框架kratos学习笔记六(kratos 服务发现 discovery) http api register 服务注册 fetch 获取实例 fetchs 批量获取实例 polls 批 ...
- go微服务框架kratos学习笔记四(kratos warden-quickstart warden-direct方式client调用)
目录 go微服务框架kratos学习笔记四(kratos warden-quickstart warden-direct方式client调用) warden direct demo-server gr ...
- go微服务框架kratos学习笔记八 (kratos的依赖注入)
目录 go微服务框架kratos学习笔记八(kratos的依赖注入) 什么是依赖注入 google wire kratos中的wire Providers injector(注入器) Binding ...
随机推荐
- 这是我见过最简单的博客文只有一张图,Python基础10分钟学完
- 如何从阿里云Code升级至云效Codeup
如果你还在使用阿里云Code,不防看看如何从阿里云Code升级至云效Codeup,云效代码管理Codeup是阿里云出品的一款企业级代码管理平台,提供代码托管.代码评审.代码扫描.质量检测等功能,全方位 ...
- react之组件生命周期
四个阶段 初始化 运行中 销毁 错误处理(16.3以后) 初始化 constructor static getDerivedStateFromProps() componentWillMount() ...
- 飞猪基于 Serverless 的云+端实践与思考
作者 | 王恒飞(承荫) 本文整理自飞猪旅行前端技术专家--王恒飞(承荫)在[阿里云 Serverless Developer Meetup 上海站]上的分享.点击查看直播回放:https://dev ...
- Java到底怎么学?
你现在是不是想学Java,但很迷茫不知该从何下手,那么请认真看完这篇文章,希望对你有所帮助! 作为零基础刚刚接触Java的朋友们来说,我的建议还是先看视频,虽然有很多人说看视频学习慢,建议直接买优秀的 ...
- 题解 [AGC017C] Snuke and Spells
题目传送门 Description 有 \(n\) 个球排在一起,每个球有颜色 \(a_i\),若当前有 \(k\) 个球,则会将所有 \(a_i=k\) 的球删掉.有 \(m\) 次查询,每次将 \ ...
- C/C++入门级小游戏——开发备忘录
很多工科的学生在大一都有一门课程,叫C语言程序设计.大概就是装个IDE然后和一个黑乎乎的窗口打交道,期末到了考完试就结束了.然而很多人可能都有一个疑惑:C语言究竟能干什么?除开嵌入式单片机这些高大上的 ...
- HCNP Routing&Switching之BGP邻居建立条件、优化和认证
前文我们了解了BGP相关概念.AS相关概念以及BGP邻居类型.基础配置等,相关回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15370838.html:今天我们 ...
- pip 安装软件报 Requirement already satisfied
pip 安装的时候报错了,以为是豆瓣源有问题,换了还是一样,于是我们只需要加入一个参数 --target=路径 给它一个指定的位置就可以解决这个问题 安装位置不变,只是增加了一个参数在后面
- HCNP Routing&Switching之BGP路由宣告
前文我们了解了BGP报文结构.类型以及邻居状态相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15422924.html:今天我们来聊一聊BGP路由宣告 ...