go-kit微服務:JWT身份認證

爲了保證系統安全穩定,保護用戶數據安全,服務中通常引入身份認證手段,對用戶的請求進行安全攔截、校驗與過濾。經常使用的身份認證方式有:html

  • JWT: JWT提供了一種用於發佈接入令牌(Access Token),並對發佈的簽名接入令牌進行驗證的方法。 令牌(Token)自己包含了一系列聲明,應用程序能夠根據這些聲明限制用戶對資源的訪問。
  • OAuth2:OAuth2是一種受權框架,提供了一套詳細的受權機制(指導)。用戶或應用能夠經過公開的或私有的設置,受權第三方應用訪問特定資源。
  • Basic:即用戶名和密碼認證,每次請求都攜帶,不安全。

實戰演練

本文將在go-kit微服務中引入jwt驗證機制,實現token的簽發與驗證。關於jwt的原理再也不闡述,你們可到最後的參考文獻中查閱。簡單說下實現思路:git

  • 新建登陸接口,驗證用戶名和密碼。驗證經過生成token,返回客戶端。
  • 爲calculate接口增長token驗證機制,這裏使用go-kit提供的中間件進行封裝。
  • 本示例使用第三方jwt的go實現dgrijalva/jwt-go

Step-1:代碼準備

複製目錄arithmetic_circuitbreaker_demo,重命名爲arithmetic_jwt_demo,重命名register目錄爲service。github

安裝依賴的jwt第三方庫:web

go get github.com/dgrijalva/jwt-go
複製代碼

Step-2:建立jwt.go

service目錄下建立文件jwt.go算法

  • 首先定義生成token時須要的密鑰(這個是jwt中最重要的東西,千萬不能泄露)。
  • 自定義聲明。在StandardClaims基礎上增長了UserIdName兩個字段,能夠根據實際須要擴展其餘字段,如角色。
  • 定義keyfunc,該方法在驗證token時做爲回調函數使用,後面會有描述。
  • 定義生成token的方法Sign。這裏直接調用jwt第三方庫生成,爲了演示方便設置token的過時時間爲2分鐘。

以下爲jwt.go的所有代碼:docker

//secret key
var secretKey = []byte("abcd1234!@#$")

// ArithmeticCustomClaims 自定義聲明
type ArithmeticCustomClaims struct {
	UserId string `json:"userId"`
	Name   string `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的架構方式,依次:json

  • 在接口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"`
	Pwd  string `json:"pwd"`
}

// AuthResponse
type AuthResponse struct {
	Success bool   `json:"success"`
	Token   string `json:"token"`
	Error   string `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方法。bash

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可大大減輕認證中心的壓力,使得微服務的水平擴展變得更加容易。

參考文獻

本文首發於本人微信公衆號【兮一昂吧】,歡迎掃碼關注!

相關文章
相關標籤/搜索