什么是认证
对于大多数应用系统离不开身份验证。因为我们需要保护一些数据,不让“非法”用户获取。所以我们必须得根据自身项目情况来添加对身份验证的支持功能。
在这之前,我们先不要考虑什么Bearer,JWT,OpenId等概念,忘掉他们,让我们从0开始。
假如我们现在有一个Web Api应用程序,在没有任何标准协议和框架的支持下,我们会如何对一个用户进行身份验证呢?
最基础的验证
或许您已经想到了,既然用户是通过账号和密码来登录的,那么我就可以通过账号和密码来对他进行验证呀。让用户直接把用户名和密码传给我,我不就知道是他了吗?
那怎么传值呢?用Get? 比如下方的这个请求:
http://your-address/Book/Get?user='myName'&pwd='abc123'
这样每次请求的时候我就能够得到用户名和密码了,然后通过和数据库校验就能够判断当前的用户是不是通过了。
但是这种方式您很快就能发现问题,每个api不都要增加一些参数吗?url是一个很普通的东西,这样很容易就把账号密码泄露了。
所以,我们改变一下方案,把用户名和密码放到Http的请求头(Header)里面,该项的Header Key值叫做Authorization。
那么我们的请求可能就像这样了:
Request URL: http://your-address/Book/Get
Request Header:
:method: GET
Authorization:myName:abc123
当然,如果把用户名密码信息在加密一下就更好了。为了让服务端能够解密,所以采用了Base64加密。所以请求就可能成为了这个样子:
Request URL: http://your-address/Book/Get
Request Header:
:method: GET
Authorization:bXlOYW1lOmFiYzEyMw==
这样服务端很容易就能够通过Header来进行用户验证。 获取header的Authorization项 -> 进行Base64解密 -> 根据数据库内容判断用户名和密码 -> 验证通过。
这种验证方案是不是很简单呢? 但是到这里,您可能会说,这种方案也太简陋了吧。如果我拦截到了请求的包,那不等于这个人直接把用户名和密码送到我的手里吗?
确实是这样的,如果我们在进行Http请求的时候受到了中间人攻击,那么账号和密码都将被泄露,“非法分子”可以拿着得到的用户名和密码登录系统进行任何操作。
所以,我们必须采用Https传输。这样,中间人得到的信息是加密的,他也无法解析出来。
而这种直接把用户名和密码放置在请求头中传输的方案,正是伴随Http协议一同提出的Basic验证方案:Wiki Basic access authentication。
身份信息自包含(令牌)
当身份验证服务和咱们的业务系统粘连在一起的时候(比如传统的单体环境),基础的验证方案其实能够很好的满足咱们的需求。但是,当身份验证服务被独立出来,我们就需要使用过多的成本去进行验证:比如身份验证服务部署在服务器A,而业务服务在服务器B,如果按照上面的验证方案,我们每访问一次服务器B,那么服务器B就需要把该请求所携带的信息转发至服务器A去验证,服务器A根据转发过来的Header中的Authorization项,从数据库中或者内存中查询对应的身份信息,进行通过或者拒绝操作,然后服务器B再根据服务器A所返回的信息进行处理。
而网络通信的成本是昂贵,假如不需要身份验证的话,只需要一次就能够完成业务,而现在,会被拆分成多次,时间开销是很大的。再一点,所有的访问压力都会被推到身份验证服务器,如果有B,C,D三个业务服务器,那岂不是所有的服务器都要于身份验证服务器进行交互?
所以,我们必须得使用另外的手段来应对这种身份验证方案,那就是自包含的身份信息:当身份验证服务器验证通过时,就发一个类似于令牌的东西给客户端,与上面的那种方案较为不同的是,该令牌是一种包含了必要验证信息的加密字符串。
比如我们每次身份验证都是为了获取到userId这一项信息。基础验证方案中,我们通过传递username和password来获取userId。而现在,我们就直接让令牌来包含userId这一项内容,而以后我们每次携带该令牌去访问API的时候,就不需要再到数据库中进行查找用户来获取Id了。这样就能大幅度够减缓服务器的查找压力。
用户传递了username和password到身份验证服务器,服务器通过与数据库中的用户信息进行匹配,发现是userId = 3
的用户。此时身份验证服务器则产生一个类似于userId:3&userName:myName
的字符串返回给用户,下一次用户访问时,就携带上该字符串在请求头部进行传递,而其它的服务器看到该信息后,就认为此刻的用户是userId为3的用户,则返回该用户对应的数据。
上方是咱们根据已有的结论来模拟的验证方案,但是您会发现,该方案其实有很大的漏洞。 比如客户端接收到了userId:3&userName:myName
的验证令牌,但是他突然起了坏心眼,既然我是id为3的用户,那肯定在我之前就有id为2或者为1的用户,那我直接改一下这个数值,然后再进行访问,是不是就可以得到其它用户的信息了呢?所以我们必须要做的事情就是:“将结果加密”。当然,加密的方式有对称加密和非对称加密。对称加密就是加密和解密共用一个密匙,所以密匙是必须得严格保护。而非对称加密就是产生一个公钥和私钥,可以用私钥来加密,然后别人可以用公钥来进行解密验证。
有些时候,身份验证服务器不愿意与其它业务服务器共享密匙,因为知道的人越多,泄露的风险就越大,那么他就可以使用非对称加密的方案。身份验证服务器独享一个私钥来进行加密,而业务服务器可以从身份验证服务器处获取到公钥来进行验证。
这样我们就完成了自包含的身份信息令牌的颁发,但是不要急,还有问题。因为这个令牌的生效区间是什么时候呢? 我们现在只是颁发了信息,但是您想啊,这样不是一发出去了之后就一发不可收拾了吗? 用户可以一直使用该令牌来进行访问,即使他已经更改了密码,但是令牌还是依旧生效的,如果令牌一泄露,那他的账号就永久的凉凉了。
所以,我们必须得给这个令牌一个过期时间,如果令牌超过了过期时间,那么该令牌就是无效的。