【go-micro實踐】micro API 網關增長JWT鑑權功能

github完整代碼地址 我的博客html

micro API網關

micro API網關是基於go-micro開發的,具備服務發現,負載均衡和RPC通訊的能力。git

業界廣泛作法是將鑑權,限流,熔斷等功能也歸入API網關。micro API網關自己是可插拔的,能夠經過新增插件的方式加入其餘功能。github

JWT (JSON Web Token)

JWT是是微服務中經常使用的受權技術,關於JWT的技術原理能夠參考阮一峯的博文web

JWT庫封裝

  • lib/token 目錄下封裝了JWT的庫。有一點特殊的是,庫中利用consul的KV存儲和micro的go-config庫實現了動態更新JWT的PrivateKey功能,實際生產中仍是應該使用擁有發佈和權限管理的配置中心。
    • go-config 是micro做者實現的一個可動態加載、可插拔的配置庫,能夠從多種格式文件或者遠程服務獲取配置。詳情能夠參考文檔中文文檔|英文文檔
    • PrivateKey是JWT在編解碼時使用的私鑰,一旦泄漏,客戶端即可以利用這個私鑰篡改、僞造Token。因此通常生產環境中都必須具有動態更新私鑰的能力,一旦發現泄漏能夠當即更改,或者按期更換私鑰,提升安全性。
// InitConfig 初始化
func (srv *Token) InitConfig(address string, path ...string) {
	consulSource := consul.NewSource(
		consul.WithAddress(address),
	)
	srv.conf = config.NewConfig()
	err := srv.conf.Load(consulSource)
	if err != nil {
		log.Fatal(err)
	}

	value := srv.conf.Get(path...).Bytes()
	if err != nil {
		log.Fatal(err)
	}

	srv.put(value)
	log.Println("JWT privateKey:", string(srv.get()))
	srv.enableAutoUpdate(path...)
}

func (srv *Token) enableAutoUpdate(path ...string) {
	go func() {
		for {
			w, err := srv.conf.Watch(path...)
			if err != nil {
				log.Println(err)
			}
			v, err := w.Next()
			if err != nil {
				log.Println(err)
			}

			value := v.Bytes()
			srv.put(value)
			log.Println("New JWT privateKey:", string(srv.get()))
		}
	}()
}
複製代碼

做者已經實現了consul的KV配置的插件,因此只須要導入這個庫"github.com/micro/go-config/source/consul",即可以直接讀取consul中的配置。json

動態跟新實現就是利用go-config的watch方法,當consul KV裏的配置更改,Watch函數返回再經過Next方法讀取新數據。將watch 讀取的操做起一個協程循環執行(沒有考慮優雅退出),經過讀寫鎖來保證操做安全。安全

實現API網關插件

將JWT Token在HTTP頭中攜帶,經過HTTP中間件過濾每個HTTP請求,提取頭中的Token鑑權,經過則繼續執行,不經過就直接返回。bash

//microservices/lib/wrapper/auth

// JWTAuthWrapper JWT鑑權Wrapper
func JWTAuthWrapper(token *token.Token) plugin.Handler {
	return func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			log.Println("auth plugin received: " + r.URL.Path)
			// TODO 從配置中心動態獲取白名單URL
			if r.URL.Path == "/user/login" || r.URL.Path == "/user/register"{
				h.ServeHTTP(w, r)
				return
			}

			tokenstr := r.Header.Get("Authorization")
			userFromToken, e := token.Decode(tokenstr)

			if e != nil {
				w.WriteHeader(http.StatusUnauthorized)
				return
			}

			log.Println("User Name : ", userFromToken.UserName)
			r.Header.Set("X-Example-Username", userFromToken.UserName)
			h.ServeHTTP(w, r)
		})
	}
}
 ...

// main.go
func init() {
	token := &token.Token{}
	token.InitConfig("127.0.0.1:8500", "micro", "config", "jwt-key", "key")

	plugin.Register(plugin.NewPlugin(
		plugin.WithName("auth"),
		plugin.WithHandler(
			auth.JWTAuthWrapper(token),
		),
	))
}
const name = "API gateway"

func main() {
	cmd.Init()
}
複製代碼
  • 初始化咱們封裝JWT Token
func (srv *Token) InitConfig(address string, path ...string)
token.InitConfig("127.0.0.1:8500", "micro", "config", "jwt-key", "key")
複製代碼

"127.0.0.1:8500" 是本地consul 監聽地址,path是可變參數,傳遞consul KV中的配置路徑:micro/config/jwt-key。 app

在這裏插入圖片描述

  • 註冊插件
func Register(plugin Plugin) error //全局註冊一個插件
func NewPlugin(opts ...Option) Plugin //生成一個插件
func WithName(n string) Option //設置插件的名字
func WithHandler(h ...Handler) Option  //http handler中間件
複製代碼

註冊一個新插件的時候,還能夠定製其餘操做,具體能夠看做者的文檔英文文檔|中文文檔負載均衡

在hander中將Token進行校驗,若是鑑權成功,則調用 h.ServeHTTP(w, r) ,此時micro會調用下一個hander。 若是鑑權失敗,就修改狀態碼w.WriteHeader(http.StatusUnauthorized), 不調用 h.ServeHTTP(w, r),此時鏈式調用中斷,micro框架不會調用剩下的hander。框架

github完整代碼地址

相關文章
相關標籤/搜索