【GO-Micro】 micro API 网关增加 JWT 鉴权功能

本贴最后更新于 2129 天前,其中的信息可能已经时移世改

github 完整代码地址

micro API 网关

micro API 网关是基于 go-micro 开发的,具有服务发现,负载均衡和 RPC 通信的能力。

业界普遍做法是将鉴权,限流,熔断等功能也纳入 API 网关。micro API 网关本身是可插拔的,可以通过新增插件的方式加入其他功能。

JWT (JSON Web Token)

JWT 是是微服务中常用的授权技术,关于 JWT 的技术原理可以参考阮一峰的博文

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 中的配置。

动态跟新实现就是利用 go-config 的 watch 方法,当 consul KV 里的配置更改,Watch 函数返回再通过 Next 方法读取新数据。将 watch 读取的操作起一个协程循环执行(没有考虑优雅退出),通过读写锁来保证操作安全。

实现 API 网关插件

将 JWT Token 在 HTTP 头中携带,通过 HTTP 中间件过滤每一个 HTTP 请求,提取头中的 Token 鉴权,通过则继续执行,不通过就直接返回。

//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。
aa823c9a7244f19509708a20f6faaa3b.png

  • 注册插件
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 完整代码地址

  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    498 引用 • 1395 回帖 • 249 关注
  • Consul
    6 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...