go-kit微服務:JWT身份認證
為了保證系統安全穩定,保護使用者資料安全,服務中一般引入身份認證手段,對使用者的請求進行安全攔截、校驗與過濾。常用的身份認證方式有:
- JWT: JWT提供了一種用於釋出接入令牌(Access Token),並對釋出的簽名接入令牌進行驗證的方法。 令牌(Token)本身包含了一系列宣告,應用程式可以根據這些宣告限制使用者對資源的訪問。
- OAuth2:OAuth2是一種授權框架,提供了一套詳細的授權機制(指導)。使用者或應用可以通過公開的或私有的設定,授權第三方應用訪問特定資源。
- Basic:即使用者名稱和密碼認證,每次請求都攜帶,不安全。
實戰演練
本文將在go-kit微服務中引入jwt驗證機制,實現token的簽發與驗證。關於jwt的原理不再闡述,大家可到最後的參考文獻中查閱。簡單說下實現思路:
dgrijalva/jwt-go
Step-1:程式碼準備
複製目錄arithmetic_circuitbreaker_demo,重新命名為arithmetic_jwt_demo,重新命名register目錄為service。
安裝依賴的jwt第三方庫:
go 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
的全部程式碼:
//secret key var secretKey = []byte("abcd1234!@#$") // ArithmeticCustomClaims 自定義宣告 type ArithmeticCustomClaims struct { UserId string `json:"userId"` Namestring `json:"name"` jwt.StandardClaims } // jwtKeyFunc 返回金鑰 func jwtKeyFunc(token *jwt.Token) (interface{}, error) { return secretKey, nil } // Sign 生成token func Sign(name, uid string) (string, error) { //為了演示方便,設定兩分鐘後過期 expAt := time.Now().Add(time.Duration(2) * time.Minute).Unix() // 建立宣告 claims := ArithmeticCustomClaims{ UserId: uid, Name:name, StandardClaims: jwt.StandardClaims{ ExpiresAt: expAt, Issuer:"system", }, } //建立token,指定加密演算法為HS256 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) //生成token return token.SignedString(secretKey) } 複製程式碼
Step-3:新增登入介面
Service層:新增登入介面,按照go-kit的架構方式,依次:
- 在介面Service中新增Login方法。
- 在ArithmeticService中實現Login方法:驗證使用者名稱和密碼,呼叫jwt的Sign方法生成token;
- 在loggingMiddleware中實現Login方法;
- 在metricMiddleware中實現Login方法;
// Service Define a service interface type Service interface { //…… // HealthCheck Login(name, pwd string) (string, error) } func (s ArithmeticService) Login(name, pwd string) (string, error) { if name == "name" && pwd == "pwd" { token, err := Sign(name, pwd) return token, err } return "", errors.New("Your name or password dismatch") } 複製程式碼
Endpoint層:新增登入介面所需的請求和響應實體結構,編寫建立Endpoint的方法。
// AuthRequest type AuthRequest struct { Name string `json:"name"` Pwdstring `json:"pwd"` } // AuthResponse type AuthResponse struct { Success bool`json:"success"` Tokenstring `json:"token"` Errorstring `json:"error"` } func MakeAuthEndpoint(svc Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { req := request.(AuthRequest) token, err := svc.Login(req.Name, req.Pwd) var resp AuthResponse if err != nil { resp = AuthResponse{ Success: err == nil, Token:token, Error:err.Error(), } } else { resp = AuthResponse{ Success: err == nil, Token:token, } } return resp, nil } } 複製程式碼
Transport層:編寫decode和encode方法,新增登入介面路由。同時,對calculate介面增加token檢測邏輯:在請求處理之前,從HTTP請求頭中讀取認證資訊,若讀取成功則加入請求上下文。這裡直接使用go-kit提供的HTTPToContext方法。
func decodeLoginRequest(_ context.Context, r *http.Request) (interface{}, error) { var loginRequest AuthRequest if err := json.NewDecoder(r.Body).Decode(&loginRequest); err != nil { return nil, err } return loginRequest, nil } func encodeLoginResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { w.Header().Set("Content-Type", "application/json;charset=utf-8") return json.NewEncoder(w).Encode(response) } 複製程式碼
增加http路由:
r.Methods("POST").Path("/calculate/{type}/{a}/{b}").Handler(kithttp.NewServer( endpoints.ArithmeticEndpoint, decodeArithmeticRequest, encodeArithmeticResponse, //增加了options append(options, kithttp.ServerBefore(kitjwt.HTTPToContext()))..., )) // ... r.Methods("POST").Path("/login").Handler(kithttp.NewServer( endpoints.AuthEndpoint, decodeLoginRequest, encodeLoginResponse, options..., )) 複製程式碼
Step-4:修改main.go
擴充套件原有ArithmeticEndpoints,增加AuthEndpoint;增加AuthEndpoint的建立邏輯程式碼,為其增加限流、鏈路追蹤等包裝。
//身份認證Endpoint authEndpoint := MakeAuthEndpoint(svc) authEndpoint = NewTokenBucketLimitterWithBuildIn(ratebucket)(authEndpoint) authEndpoint = kitzipkin.TraceEndpoint(zipkinTracer, "login-endpoint")(authEndpoint) //把算術運算Endpoint\健康檢查、登入Endpoint封裝至ArithmeticEndpoints endpts := ArithmeticEndpoints{ ArithmeticEndpoint:calEndpoint, HealthCheckEndpoint: healthEndpoint, AuthEndpoint:authEndpoint, } 複製程式碼
由於我們要求calculate介面只能在token有效的情況下才可訪問,所以為calEndpoint增加token校驗程式碼(最後一行程式碼,直接使用go-kit提供的中介軟體):
calEndpoint := MakeArithmeticEndpoint(svc) calEndpoint = NewTokenBucketLimitterWithBuildIn(ratebucket)(calEndpoint) calEndpoint = kitzipkin.TraceEndpoint(zipkinTracer, "calculate-endpoint")(calEndpoint) calEndpoint = 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為以下內容:
{ "name": "name", "pwd": "pwd" } 複製程式碼
可看到以下結果:

然後,請求calculate介面,並在Header中設定Authorization,結果如下:

兩分鐘後,再次測試,會發現返回token過期。

總結
本文結合例項,在go-kit微服務中引入jwt。新增login介面,使得calculate介面僅在token有效的情況下才可工作。由於jwt的認證特點,登入成功後用戶請求的token有效性不再依賴認證中心,相對OAuth2可大大減輕認證中心的壓力,使得微服務的水平擴充套件變得更加容易。