0. 概述

在前面两篇(列表见文末)中,我已经介绍了如何开发一个 GRPC 的服务,并且以 GRPC 或者 Restful 的形式提供服务;同时,我们可以尝试了 GRPC 和 Http 两种不同的 Client 如何进行调用。然而,当我们准备着可以把服务发布出去的时候,一个很严肃的问题不得不面对,那么就是如何进行用户的认证,当然,更进一步还有鉴权。这是一个系统性的话题,但是,这里我尝试抛弃业务的复杂性,从 GRPC 的实现角度来看如何在代码层面对用户进行认证。

在当前的业界使用中,有两种认证形式比较普遍,分别是基于 Token 和证书的认证,基于 Token 的认证常见于公开 API,或者企业提供服务的 API 中,即用户通过用户名密码或者 SSO 等方式获得一个代表身份的 Token,有些场景下还会有一个用于续约的 Token2;而在现在的微服务体系下,内部服务之间既不能没有认证,也不能太业务相关的认证,所以基于 TLS 证书的认证方式得到比较多人的认可,并且被使用,但是对于公开 API 来说这却不是一种合适的方式,基于本文的目的,我这里就介绍基于 Token 的认证方式,而基于证书的方式在后面我聊微服务的时候会重新炒一次冷饭。

1. Token 认证服务

Token 认证也是有很多方式,有多种不同的实现,但是,我这里为了方便,使用一种比较简单的 JWT 实现方式,而且,为了简便,我甚至都不提供 JWT 的服务端,直接在客户端中进行用户认证。如果你对 JWT 不了解的话,不妨看下我的这篇文章:JWT介绍和实战

那么这里我就先来写一下当客户端传递过来一个 JWT Token,我要如何验证这个 Token 的有效性,在验证之前,我们首先还是要先有个 JWT 的 Token 的,所以我用回前面这篇文章的 Python 代码生成一个:

这里生成了一个 token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imh0dHBzOi8vbGlxaWFuZy5pbyIsImlhdCI6MTU0NzEzNjc5Miwicm9sZSI6InN1cGVyX2FkbWluIiwiZXhwIjoxNTQ5NzI4NzkyfQ.J-c3vUgOpDEcDZuPnt82pyyVTAxtHe3yAF1kaXziAu0,有点长,但是里面附带了一些比较有用的信息,可以帮助我们更快得处理用户相关的一些请求。当有了这个 Token 之后,那么接下来就使用 Go 代码进行校验了,我这里直接给了一份 Go 代码用来校验,当 Token 有效的时候就返回 nil,Token 无效的时候返回对应的 err

有了这份校验的代码,在 GRPC 中验证用户就变得比较简单了。当然,一个比较笨的方法就是在每个调用的地方做一个验证,例如回到我们第一篇中的 GRPC Server,

像这样,我们在每个业务函数的入口做一个检查,如果用户的 token 无效,那么我们就返回一个未认证的错误。在 Go 语言中,一个比较常见的 Pattern 就是用 Context 来传递前后文的信息,如果对此有比较深兴趣的同学,可以找一下我博客中关于 Context 的介绍;如果不是太感兴趣,那么将它认为是一个 map[string]interface{} 来用就好了。

这里在每个业务函数中校验的方法可以行得通,但是缺乏灵活性和一致性,首先一个问题就是每次添加一个业务函数就得加一遍这段代码,而且一旦我们的认证方法发生改变,例如不返回 error 了,而是返回其他信息,亦或者认证错误之后不提示那么详细的信息了,而是简单得说 “认证错误”,那么整体都会比较麻烦;其次,如果你是 Ctrl + C/V 来填这段代码的话,还好一些,如果你每次都手动写,那么会不会有时会漏掉一些东西?和其他认证不一样?所以应该有一些更好的办法来解决。

还好,GRPC 作为一款流行的 RPC 框架,还是有一些方式可以做到这一点的,那就是通过拦截器来实现,至于什么是拦截器,这里可以先不用考虑,因为后面几篇我马上就会说到,现在你只需要这么理解,当客户端请求到服务端的时候,需要经历这么写地方:

  1. Client -> Server Listen -> GRPC Pre Work -> SayHello -> GRPC Post Work -> Client

而所谓的拦截器就是在将请求交给 SayHello 之前可以被调用的代码,而这段代码可以决定是否继续调用你的 SayHello 代码,还是说可以直接返回了。OK,有一个叫做 grpc-ecosystem 的小组提供了很多不同功能的拦截器,其中就有一个用于做认证的 Grpc-Auth,这个用起来也非常简单,我们来看一下怎么使用:

这里可以看到,相比较于我们在第一篇中看到的 Server 的初始化代码,这里多了一段,而这一段就是 GRPC 的 Interceptor 的用法,一般都是需要添加两种类型,分别是:StreamUnary。而我们现在只需要做的工作就是编写一个符合 Grpc-Auth 定义的函数,然后将我们的认证代码填进去就可以了,这样,在我们的业务函数中就不用填写认证代码了。

当然,这里我还偷了一个懒,那就是这里只做了认证,但是,如果你觉得后面的业务需要当前登录用户的信息的话,例如用户 ID,用户的角色等等,那么可以在这里查询出来(JWT 也可以存储),然后放进 Context 里面,这样,后面的业务就可以从 Context 直接获取到用户信息,而不需要自己再去查询一遍。

到此,本文关于 GRPC 中一个比较方便的认证实现,当然,这其实也是开了一个引子,因为这后面还引发了其他几篇文章,希望这篇文章对你有所帮助。