【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完整代码地址

相关文章
相关标签/搜索