暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Gokit微服务:JWT身份认证

码路印记 2019-03-11
149

为了保证系统安全稳定,保护用户数据安全,服务中一般引入身份认证手段,对用户的请求进行安全拦截、校验与过滤。常用的身份认证方式有:

  • JWT: JWT提供了一种用于发布接入令牌(Access Token),并对发布的签名接入令牌进行验证的方法。 令牌(Token)本身包含了一系列声明,应用程序可以根据这些声明限制用户对资源的访问。

  • OAuth2:OAuth2是一种授权框架,提供了一套详细的授权机制(指导)。用户或应用可以通过公开的或私有的设置,授权第三方应用访问特定资源。

  • Basic:即用户名和密码认证,每次请求都携带,不安全。

实战演练

本文将在go-kit微服务中引入jwt验证机制,实现token的签发与验证。关于jwt的原理不再阐述,大家可到最后的参考文献中查阅。简单说下实现思路:

  • 新建登录接口,验证用户名和密码。验证通过生成token,返回客户端。

  • 为calculate接口增加token验证机制,这里使用go-kit提供的中间件进行封装。

  • 本示例使用第三方jwt的go实现dgrijalva/jwt-go

Step-1:代码准备

复制目录arithmetic_circuitbreaker_demo,重命名为arithmetic_jwt_demo,重命名register目录为service。

安装依赖的jwt第三方库:

1go get github.com/dgrijalva/jwt-go

复制

Step-2:创建jwt.go

service
目录下创建文件jwt.go

  • 首先定义生成token时需要的密钥(这个是jwt中最重要的东西,千万不能泄露)。

  • 自定义声明。在StandardClaims
    基础上增加了UserId
    Name
    两个字段,可以根据实际需要扩展其他字段,如角色。

  • 定义keyfunc
    ,该方法在验证token时作为回调函数使用,后面会有描述。

  • 定义生成token的方法Sign
    。这里直接调用jwt第三方库生成,为了演示方便设置token的过期时间为2分钟。

如下为jwt.go
的全部代码:

 1//secret key
2var secretKey = []byte("abcd1234!@#$")
3
4// ArithmeticCustomClaims 自定义声明
5type ArithmeticCustomClaims struct {
6    UserId string `json:"userId"`
7    Name   string `json:"name"`
8
9    jwt.StandardClaims
10}
11
12// jwtKeyFunc 返回密钥
13func jwtKeyFunc(token *jwt.Token) (interface{}, error) {
14    return secretKey, nil
15}
16
17// Sign 生成token
18func Sign(name, uid string) (string, error) {
19
20    //为了演示方便,设置两分钟后过期
21    expAt := time.Now().Add(time.Duration(2) * time.Minute).Unix()
22
23    // 创建声明
24    claims := ArithmeticCustomClaims{
25        UserId: uid,
26        Name:   name,
27        StandardClaims: jwt.StandardClaims{
28            ExpiresAt: expAt,
29            Issuer:    "system",
30        },
31    }
32
33    //创建token,指定加密算法为HS256
34    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
35
36    //生成token
37    return token.SignedString(secretKey)
38}

复制

Step-3:新增登录接口

Service层:新增登录接口,按照go-kit的架构方式,依次:

  • 在接口Service中新增Login方法。

  • 在ArithmeticService中实现Login方法:验证用户名和密码,调用jwt的Sign方法生成token;

  • 在loggingMiddleware中实现Login方法;

  • 在metricMiddleware中实现Login方法;

 1// Service Define a service interface
2type Service interface {
3        //……
4
5    // HealthCheck
6    Login(name, pwd string) (string, error)
7}
8
9func (s ArithmeticService) Login(name, pwd string) (string, error) {
10    if name == "name" && pwd == "pwd" {
11        token, err := Sign(name, pwd)
12        return token, err
13    }
14
15    return "", errors.New("Your name or password dismatch")
16}

复制

Endpoint层:新增登录接口所需的请求和响应实体结构,编写创建Endpoint的方法。

 1// AuthRequest
2type AuthRequest struct {
3    Name string `json:"name"`
4    Pwd  string `json:"pwd"`
5}
6
7// AuthResponse
8type AuthResponse struct {
9    Success bool   `json:"success"`
10    Token   string `json:"token"`
11    Error   string `json:"error"`
12}
13
14func MakeAuthEndpoint(svc Service) endpoint.Endpoint {
15    return func(ctx context.Context, request interface{}) (response interface{}, err error) {
16        req := request.(AuthRequest)
17
18        token, err := svc.Login(req.Name, req.Pwd)
19
20        var resp AuthResponse
21        if err != nil {
22            resp = AuthResponse{
23                Success: err == nil,
24                Token:   token,
25                Error:   err.Error(),
26            }
27        } else {
28            resp = AuthResponse{
29                Success: err == nil,
30                Token:   token,
31            }
32        }
33
34        return resp, nil
35    }
36}

复制

Transport层:编写decode和encode方法,新增登录接口路由。同时,对calculate接口增加token检测逻辑:在请求处理之前,从HTTP请求头中读取认证信息,若读取成功则加入请求上下文。这里直接使用go-kit提供的HTTPToContext方法。

 1func decodeLoginRequest(_ context.Context, r *http.Request) (interface{}, error) {
2    var loginRequest AuthRequest
3    if err := json.NewDecoder(r.Body).Decode(&loginRequest); err != nil {
4        return nil, err
5    }
6    return loginRequest, nil
7}
8
9func encodeLoginResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
10    w.Header().Set("Content-Type""application/json;charset=utf-8")
11    return json.NewEncoder(w).Encode(response)
12}

复制

增加http路由:

 1r.Methods("POST").Path("/calculate/{type}/{a}/{b}").Handler(kithttp.NewServer(
2    endpoints.ArithmeticEndpoint,
3    decodeArithmeticRequest,
4    encodeArithmeticResponse,
5    //增加了options
6    append(options, kithttp.ServerBefore(kitjwt.HTTPToContext()))...,
7))
8
9// ...
10
11r.Methods("POST").Path("/login").Handler(kithttp.NewServer(
12    endpoints.AuthEndpoint,
13    decodeLoginRequest,
14    encodeLoginResponse,
15    options...,
16))

复制

Step-4:修改main.go

扩展原有ArithmeticEndpoints,增加AuthEndpoint;增加AuthEndpoint的创建逻辑代码,为其增加限流、链路追踪等包装。

 1//身份认证Endpoint
2authEndpoint := MakeAuthEndpoint(svc)
3authEndpoint = NewTokenBucketLimitterWithBuildIn(ratebucket)(authEndpoint)
4authEndpoint = kitzipkin.TraceEndpoint(zipkinTracer, "login-endpoint")(authEndpoint)
5
6//把算术运算Endpoint\健康检查、登录Endpoint封装至ArithmeticEndpoints
7endpts := ArithmeticEndpoints{
8    ArithmeticEndpoint:  calEndpoint,
9    HealthCheckEndpoint: healthEndpoint,
10    AuthEndpoint:        authEndpoint,
11}

复制

由于我们要求calculate接口只能在token有效的情况下才可访问,所以为calEndpoint增加token校验代码(最后一行代码,直接使用go-kit提供的中间件):

1calEndpoint := MakeArithmeticEndpoint(svc)
2calEndpoint = NewTokenBucketLimitterWithBuildIn(ratebucket)(calEndpoint)
3calEndpoint = kitzipkin.TraceEndpoint(zipkinTracer, "calculate-endpoint")(calEndpoint)
4calEndpoint = kitjwt.NewParser(jwtKeyFunc, jwt.SigningMethodHS256, kitjwt.StandardClaimsFactory)(calEndpoint)

复制

Step-5:运行&测试

通过docker-compose
启动consul、zipkin、hystrix-dashboard;然后启动gateyway(指定consul地址);最后启动service(指定consul和service地址)。

在Postman中设置POST请求http://localhost:9090/arithmetic/login,Body为以下内容:

1{
2    "name""name",
3    "pwd""pwd"
4}

复制

可看到以下结果:

登录

然后,请求calculate接口,并在Header中设置Authorization,结果如下:

请求成功

两分钟后,再次测试,会发现返回token过期。

token过期

总结

本文结合实例,在go-kit微服务中引入jwt。新增login接口,使得calculate接口仅在token有效的情况下才可工作。由于jwt的认证特点,登录成功后用户请求的token有效性不再依赖认证中心,相对OAuth2可大大减轻认证中心的压力,使得微服务的水平扩展变得更加容易。

参考文献

  • 本文示例代码@github

https://github.com/raysonxin/gokit-article-demo

  • JSON Web Token 入门教程(阮一峰)

http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

  • JWT官方网站

https://jwt.io/

  • dgrijalva/jwt-go(JWT的一个go实现)

https://github.com/dgrijalva/jwt-go


文章转载自码路印记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论