go-kit 微服务 身份认证(JWT)
Jwt官网介绍
- JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名。
简介
- 在上篇文章中go-kit 微服务 添加日志(并为每个请求添加UUID)中我们学会了如何使用中间件
- 这次我们在程序中加入jwt身份验证
- 使sum接口需要传入token才能实现访问
开始
下载依赖
- jwt库 go get github.com/dgrijalva/jwt-go
jwt编写
- jwtSecret 生成token的密钥
- CreateJwtToken 生成token
- ParseToken 解析token
var jwtSecret = []byte("jwtSecret_v3")
const JWT_CONTEXT_KEY = "jwt_context_key"
type Token struct {
Name string
DcId int
jwt.StandardClaims
}
func CreateJwtToken(name string, dcId int) (string, error) {
var token Token
token.StandardClaims = jwt.StandardClaims{
Audience: "", // 受众群体
ExpiresAt: time.Now().Add(30 * time.Second).Unix(), // 到期时间
Id: "", // 编号
IssuedAt: time.Now().Unix(), // 签发时间
Issuer: "kit_v3", // 签发人
NotBefore: time.Now().Unix(), // 生效时间
Subject: "login", // 主题
}
token.Name = name
token.DcId = dcId
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, token)
return tokenClaims.SignedString(jwtSecret)
}
func ParseToken(token string) (jwt.MapClaims, error) {
jwtToken, err := jwt.ParseWithClaims(token, jwt.MapClaims{}, func(token *jwt.Token) (i interface{}, err error) {
return jwtSecret, nil
})
if err != nil || jwtToken == nil {
return nil, err
}
claim, ok := jwtToken.Claims.(jwt.MapClaims)
if ok && jwtToken.Valid {
return claim, nil
} else {
return nil, nil
}
}
- 测试结果
jwt_test.go:12: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiaHdob2xpZGF5IiwiRGNJZCI6MiwiZXhwIjoxNTc3OTUwOTQ5LCJpYXQiOjE1Nzc5NTA5MTksImlzcyI6ImtpdF92MyIsIm5iZiI6MTU3Nzk1MDkxOSwic3ViIjoibG9naW4ifQ.w3us4_qpzm_HDPVlwDRSqIfcTT34DpmzD5_X8Z_g-jQ
jwt_test.go:17: map[DcId:2 Name:hwholiday exp:1.577950949e+09 iat:1.577950919e+09 iss:kit_v3 nbf:1.577950919e+09 sub:login]
Server层修改
添加Login方法
type Service interface {
TestAdd(ctx context.Context, in Add) AddAck
Login(ctx context.Context, in Login) (ack LoginAck, err error)
}
...省略...
func (s baseServer) Login(ctx context.Context, in Login) (ack LoginAck, err error) {
s.logger.Debug(fmt.Sprint(ctx.Value(ContextReqUUid)), zap.Any("调用 v3_service Service", "Login 处理请求"))
if in.Account != "hwholiday" || in.Password != "123456" {
err = errors.New("用户信息错误")
return
}
ack.Token, err = utils.CreateJwtToken(in.Account,1)
s.logger.Debug(fmt.Sprint(ctx.Value(ContextReqUUid)), zap.Any("调用 v3_service Service", "Login 处理请求"), zap.Any("处理返回值", ack))
return
}
Endpoint层修改
- 添加jwt鉴权中间件
func AuthMiddleware(logger *zap.Logger) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
token := fmt.Sprint(ctx.Value(utils.JWT_CONTEXT_KEY))
if token == "" {
err = errors.New("请登录")
logger.Debug(fmt.Sprint(ctx.Value(v3_service.ContextReqUUid)),zap.Any("[AuthMiddleware]","token == empty"), zap.Error(err))
return "", err
}
jwtInfo, err := utils.ParseToken(token)
if err != nil {
logger.Debug(fmt.Sprint(ctx.Value(v3_service.ContextReqUUid)),zap.Any("[AuthMiddleware]","ParseToken"), zap.Error(err))
return "", err
}
if v, ok := jwtInfo["Name"]; ok {
ctx = context.WithValue(ctx, "name", v)
}
return next(ctx, request)
}
}
}
- 对于需要鉴权的方法添加jwt鉴权中间件
var addEndPoint endpoint.Endpoint
{
addEndPoint = MakeAddEndPoint(svc)
addEndPoint = LoggingMiddleware(log)(addEndPoint)
addEndPoint = AuthMiddleware(log)(addEndPoint)
}
Transport层修改
- 将每个Http请求Header中的token传入Context中
httptransport.ServerBefore(func(ctx context.Context, request *http.Request) context.Context {
UUID := uuid.NewV5(uuid.Must(uuid.NewV4()), "req_uuid").String()
log.Debug("给请求添加uuid", zap.Any("UUID", UUID))
ctx = context.WithValue(ctx, v3_service.ContextReqUUid, UUID)
ctx = context.WithValue(ctx, utils.JWT_CONTEXT_KEY, request.Header.Get("Authorization"))
ctx = context.WithValue(ctx, utils.JWT_CONTEXT_KEY, request.Header.Get("Authorization"))
return ctx
}
激动人心的时刻来了,运行main方法
- 登录获取Token
- [Post 方法] http://127.0.0.1:8888/login 内容 {“account”:“hwholiday”,“password”:“123456”}
- 运行日志
2020-01-02 15:55:13 DEBUG v3_transport/transport.go:26 给请求添加uuid {"UUID": "9fcc91ac-dd8d-53cb-8c9d-f06561d82c6f"}
2020-01-02 15:55:13 DEBUG v3_transport/transport.go:29 把请求中的token发到Context中 {"Token": ""}
2020-01-02 15:55:13 DEBUG v3_transport/transport.go:56 9fcc91ac-dd8d-53cb-8c9d-f06561d82c6f {" 开始解析请求数据"unt":"hwholiday","password":"123456"}}
2020-01-02 15:55:13 DEBUG v3_service/service.go:38 9fcc91ac-dd8d-53cb-8c9d-f06561d82c6f {"调用 v3_service Seice": "Login 处理请求"}
2020-01-02 15:55:13 DEBUG v3_service/service.go:44 9fcc91ac-dd8d-53cb-8c9d-f06561d82c6f {"调用 v3_service Seice": "Login 处理请求", "处理返回值": {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiaHdob2xpZGF5IiwiRGNJZCI6MSwNTc3OTUxNzQzLCJpYXQiOjE1Nzc5NTE3MTMsImlzcyI6ImtpdF92MyIsIm5iZiI6MTU3Nzk1MTcxMywic3ViIjoibG9naW4ifQ.2s2YzKhMHSXRjzgO4yfHP7gHADeVwpsi9CyXPgAQjmQ"}}
2020-01-02 15:55:13 DEBUG v3_service/middleware.go:37 9fcc91ac-dd8d-53cb-8c9d-f06561d82c6f {"调用 Login logMiddwareServer": "Login", "req": {"account":"hwholiday","password":"123456"}, "res": {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiaHdob2xpZGF5IiwiRGNJZCI6MSwiZXhwIjoxNTc3OTUxNzQzLCJpYXQiOjE1Nzc5NTE3MTMsImlzcyI6ImtpdF92MyIsIm5iZiI6MTU3Nzk1MTcxMywic3ViIjoibG9naW4ifQ.2s2YzKhMHSXRjzgO4yfHP7gHADeVwpsi9CyXPgAQjmQ"}, "err": null}
2020-01-02 15:55:13 DEBUG v3_endpoint/middleware.go:18 9fcc91ac-dd8d-53cb-8c9d-f06561d82c6f {"调用 v3_endpoint LgingMiddleware": "处理完请求", "耗时毫秒": 0}
2020-01-02 15:55:13 DEBUG v3_transport/transport.go:75 9fcc91ac-dd8d-53cb-8c9d-f06561d82c6f {"请求结束封装返回值n":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiaHdob2xpZGF5IiwiRGNJZCI6MSwiZXhwIjoxNTc3OTUxNzQzLCJpYXQiOjE1Nzc5NTE3MTMsImlzcyI6ImtpdF92MyIsIm5iZiI6MTU3Nzk1MTcxMywic3ViIjoibG9naW4ifQ.2s2YzKhMHSXRjzgO4yfHP7gHADeVwpsi9CyXPgAQjmQ"}}
- 调用sum方法
- [Get 方法] 127.0.0.1:8888/sum?a=1&b=1 添加登录获取到的token放入Header里面(Header的key为Authorization)
- Token没过期的运行日志
2020-01-02 15:58:13 DEBUG v3_transport/transport.go:26 给请求添加uuid {"UUID": "4e44ed1f-e424-5313-83e0-b68cf5cce6e1"}
2020-01-02 15:58:13 DEBUG v3_transport/transport.go:29 把请求中的token发到Context中 {"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiaHdob2xpZGF5IiwiRGNJZCI6MSwiZXhwIjoxNTc3OTUxOTA2LCJpYXQiOjE1Nzc5NTE4NzYsImlzcyI6ImtpdF92MyIsIm5iZiI6MTU3Nzk1MTg3Niwic3ViIjoibG9naW4ifQ.7kMgtVMAQaJJ92MeFPdO7CWnNwY6cumLjUuvZGAd-Z0"}
2020-01-02 15:58:13 DEBUG v3_transport/transport.go:70 4e44ed1f-e424-5313-83e0-b68cf5cce6e1 {" 开始解析请求数据": {"a":1,"b":1}}
2020-01-02 15:58:13 DEBUG v3_service/service.go:31 4e44ed1f-e424-5313-83e0-b68cf5cce6e1 {"调用 v3_service Service": "TestAdd 处理请求", "请求用户": "hwholiday"}
2020-01-02 15:58:13 DEBUG v3_service/service.go:33 4e44ed1f-e424-5313-83e0-b68cf5cce6e1 {"调用 v3_service Service": "TestAdd 处理请求", "处理返回值": {"res":2}}
2020-01-02 15:58:13 DEBUG v3_service/middleware.go:29 4e44ed1f-e424-5313-83e0-b68cf5cce6e1 {"调用 Login logMiddlewareServer": "TestAdd", "req": {"a":1,"b":1}, "res": {"res":2}}
2020-01-02 15:58:13 DEBUG v3_endpoint/middleware.go:18 4e44ed1f-e424-5313-83e0-b68cf5cce6e1 {"调用 v3_endpoint LoggingMiddleware": "处理完请求", "耗时毫秒": 2}
2020-01-02 15:58:13 DEBUG v3_transport/transport.go:75 4e44ed1f-e424-5313-83e0-b68cf5cce6e1 {"请求结束封装返回值": {"res":2}}
- Token过期的运行日志
2020-01-02 15:59:16 DEBUG v3_transport/transport.go:26 给请求添加uuid {"UUID": "b017a114-9216-5f67-b9bb-a8e378a73828"}
2020-01-02 15:59:16 DEBUG v3_transport/transport.go:29 把请求中的token发到Context中 {"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiaHdob2xpZGF5IiwiRGNJZCI6MSwiZXhwIjoxNTc3OTUxOTA2LCJpYXQiOjE1Nzc5NTE4NzYsImlzcyI6ImtpdF92MyIsIm5iZiI6MTU3Nzk1MTg3Niwic3ViIjoibG9naW4ifQ.7kMgtVMAQaJJ92MeFPdO7CWnNwY6cumLjUuvZGAd-Z0"}
2020-01-02 15:59:16 DEBUG v3_transport/transport.go:70 b017a114-9216-5f67-b9bb-a8e378a73828 {" 开始解析请求数据": {"a":1,"b":1}}
2020-01-02 15:59:16 DEBUG v3_endpoint/middleware.go:35 b017a114-9216-5f67-b9bb-a8e378a73828 {"[AuthMiddleware]": "ParseToken", "error": "Token is expired"}
2020-01-02 15:59:16 WARN v3_transport/transport.go:20 b017a114-9216-5f67-b9bb-a8e378a73828 {"error": "Token is expired"}
结语
- 加入JWT机制,我们可以对接口实现只接受我们认证的用户访问
- 欢迎添加QQ一起讨论