原创分享 go-kit 微服务 身份认证 (JWT)

hwholiday · 2020年02月12日 · 608 次阅读

Jwt 官网介绍

  • JSON Web 令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对对 JWT 进行签名。

简介

开始

下载依赖

  • 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 方法

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 一起讨论

完整代码地址

联系 QQ: 3355168235

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册