【HTTP】 认证和单点登录 【瞎写的…】

■  Cookie,Session,Token

  HTTP协议是一种无状态的协议。换言之,每一个HTTP请求在得到HTTP回应之后就会断开客户端到服务端的连接。客户端可能会有下一次请求,但是那是下一次的事情,在不做任何处理的情况下,两次请求之间互相独立没有联系。这种联系的缺失会带来很多不便,比如大多数网站都是需要用户登录的,如果上一次请求时输入账密登录过,这次再请求由于和上次请求互相独立,那么势必要再输入一次账号密码。

  要解决这个问题,很自然的一个想法就是可以不可以将每次请求中都要用到的信息自动地给出,而这个就是Cookie机制。

  Cookie将一些信息以键值对的形式保存在客户端处,并且将这些键值对和特定的域名联系起来。当浏览器访问某个域名的时候,会自动检查是否有Cookie符合当前域名,如果有那么就在请求中自动带上。由于这是浏览器自身的行为对用户是透明的,所以用户就省去了一次次输入重复信息的操作。

 

  早期的HTTP服务提供的往往是静态的HTML文件,所有人看到的都是同样的文件,因此服务端也没有必要去分别到底是谁来请求看文件,总之给人家就行了。但是随着应用的发展,开始出现越来越多的动态Web应用,即针对不同用户的请求,给出不同的响应。这个问题本质上就是上面说的Cookie解决的问题,即用户可以通过Cookie中的信息在请求中声明自己的身份,这样服务端就可以进行针对性的响应了。

  那么具体用什么样的形式放入cookie呢?单写一个用户名?似乎不太好,这样另一个人只要在请求中声明他是你就可以看到你才能看到的信息了。写上用户名 & 密码?略减小了冒充的可能性,但是密码总归还是好猜的,而且把密码就直接写在请求里总感觉怪怪的。于是就有人想出了Session即会话这种方法。

  一个会话最开始由客户端传递用户名和密码开始,服务端接受到账密之后尝试着验证是否正确。如果密码正确那么就将生成一个被称为sessionid的随机字符串,在服务端维护上用户与sessionid之间的关联之后将sessionid返回给客户端。这个过程也被称为会话的建立。客户端将这个sessionid放在cookie中,之后每次请求中,客户端都会带上这个sessionid,这样子服务端只要解析一下就能知道发出访问的用户是谁。而且一个由服务端随机生成的字符串远比用户自己的密码要难猜。后来随着web的发展,会话的含义开始变得广泛。一个比较显著的特点就是,后来的会话除了那个sessionid之外,往往还指包括了一些服务端存储的东西。比如一些频繁用到的,跟用户相关的信息(比如用户的角色,用户的访问次数等等),会通过缓存或者数据库的方式保存在服务端,和sessionid关联在一起。每当这个会话再次发来请求时,这些数据就可以直接使用了,很方便。

 

  随着web发展,会话又遇到了问题。比如面对越来越多的用户,如何维护越来越多的sessionid是一个问题。会话的本质是特定的客户端和服务端之间的关系,如果服务端现在是一个集群,客户端先去访问了A机后获得了一个sessionid,如何将这个id在第二次访问流向B机时还能发挥sessionid的作用是个问题。解决方案有多种比如在A机生成sessionid时同步给B机,或者建立一个sessionid中心,所有集群中关于会话的操作都交给一台机器做。但是这样依旧是治标不治本,如果用户越来越多,session中心本身还需要集群化,搞得越来越复杂。所以又有人想出了token这种东西。

  token其实是用了CPU的计算时间换取了内存/磁盘等原本用来存储sessionid内容的存储空间。token是这么玩的:

  当客户端第一次访问服务端,服务端通过认证后,服务端不是生成一个随机的sessionid,而是使用一定的算法将用户信息结合一个只有服务端自己知道的秘钥加密成一串字符串。这个字符串作为token返回给客户端。客户端将其保存之后(可以保存在cookie中或者其他的存储介质中),每次新请求时都会带上自己是谁的明文身份声明,再附带这个token。服务端接收到之后会根据声明查找其内部存储的相关用户信息,然后将信息加密,对比请求送过来的token。如果两者一致,那么就认为这个请求由此用户发出,因此就可以给出这个用户能看到的信息了。

  在上述过程中,服务端没有将token保存落地,而是通过了“算法”这一纽带,把用户和请求关联了起来。如果说sessionid是鱼,那么token可以说是渔,其基于规则做实时的用户身份验证。由于token没有在服务端保存落地,所以就不存在上述存储以及集群同步相关的问题了。

 

  以上只是一些很粗浅地对于三个关于“HTTP持续性”概念的解释。实际上Session,Token之类的概念现在已经有了很大的拓展。

  比如在form表单中存在的利用“token”来防止重复提交的操作。其流程可能是这样的,首先在表单所在页面被渲染之前由服务端随机生成一个字符串并保存,这个字符串被称为了token,且token会作为一个hidden的input被放到页面上。然后客户端在填写完表单提交时要求附带上这个token。服务端接收到后比较提交数据的token和服务端之前保存着的。如果一致,认为这是客户端第一次提交数据,则进行处理并且将token重新随机。如果因为一些问题,客户端在没有GET新表单(token已经变化过的表单),而是将原表单再POST提交一次,那么服务端就可以看到第二次提交数据中的token还是老token,和已经随机过的新token不同,因此不予以处理。

  如果说session和token之间主要区别在于是否在服务端进行数据的落地保存的话,那么这种防止重复提交的token很明显是属于session范畴的,因为要保存token在服务端校验用。只不过由于习惯问题等,还是称其为token。

  另外token和session也不是完全独立的,经常有session和token并用,来确保更加好的登录验证功能。

 

■  关于单点登录的来由

  单点登录的指对于多个系统,同一个用户只要输入一次账号密码进行登录,验证成功之后就无需再次输入账号密码,可以直接访问所有系统直到保持登录状态失效或其他情况。

  最朴素的单点登录想法就是让所有的系统都具有相同的验证逻辑和密码库,然后在首次登录验证成功之后,将账密写入用户的Cookie中。这样再次访问其他系统时就会自动读取账号密码,从而通过认证。

  鉴于多个系统要求相同配置等高难度操作,一个改进的想法就是建立统一的认证中心,比如建立一个维护了所有密码库的认证中心。当有用户想要访问某个应用服务器的时候,如果应用服务器发现其没有经过认证(比如不存在合适的Cookie)就可以将本请求重定向到认证中心。认证中心往往会提供一个输入账号密码的界面,用户在这个界面上输入账号密码之后如果通过认证,认证中心可以给客户端一个ticket或者说凭证,并且把这个凭证的有效性记录下来。为了更好的体验,在GET认证中心的时候参数里面会记录原先重定向前用户意图访问的应用地址。此时就可以在返回凭证之后,再重定向到原页面。

  此时,原页面接收到请求后还不是直接给出数据,而是将此时请求中带有的那个凭证ticket取出来,交由认证中心检验,如果认证中心的返回是有效的,才会去调用视图,最终向客户端呈现数据。

  当客户端带着得到的凭证再去访问其他应用服务器时,只要应用服务器和认证中心之间可以通信,那么以上后半个流程就是可以再走一遍。因此可以做到所谓的单点登录。

 

  但是仔细想想其中还是存在一些缺陷的。比如以上对于ticket的保存还是基于cookie的,而我们知道cookie是和域名绑定的。如果两个应用服务器的一级域名不同的话,那么cookie就无法通用。

  为了解决跨域的问题,以及增强整个单点登录认证过程的健壮性,有人开发了CAS这么一套单点登录的框架。同时其架构也是非常值得学习的。

 

■  CAS单点登录【https://www.cnblogs.com/lihuidu/p/6495247.html】

  首先上一张CAS整个流程的图。这个图中包含了客户端(以C简称)访问A应用(www)两次和B应用(mail)一次的流程。其中和认证中心(P)交互也包含在内。

  

  下面按照顺序说明各个请求/回应是怎么玩的。

  1. C向A发起了请求。由于此时C还没有用户登录,A判断其对请求的内容没有权限,因此回复了一个HTTP 302响应,将请求重定向到了认证中心。

  1,2的请求、回应如图

  3. 重定向完成后C自动发起了对P的请求。由于P也可以判断出,A的用户未登录,所以4返回了账号密码表单的界面,让A中用户登录。

  3,4的请求回应如图。可以注意到,重定向的请求3,在GET参数中有一个service参数,其值是之前1中请求的URL,即http://www.qiandu.com/。因为URI对特殊字符敏感所以这里有些转义。

 

   4和5中间的时间用户在输入账号密码,输入完成后,提交的请求就是5了(通常是POST请求)。然后P对A的请求进行验证,给出6回应。

  5,6的请求回应如图。可以看到,6回应的headers里面有一个Location参数,这个参数是一个URL。URL是怎么来的呢,其实是之前请求头中的Referer参数中的service参数(也就是最开始1请求的原生URL),再加上了一个GET参数,叫做ticket的东西。另外设置了一个Cookie,值是一个叫做CASTGC的东西。两者在P内部都是会维护记录下来的。

  Location参数什么用?想必已经猜出来了,6回应其实是一个HTTP 302回应,会将C重定向到了Location参数所指示的地址。CASTGC什么用?其实这是认证中心自身的一个Cookie。试想,虽然单点登录让我们不用每次访问不通应用都要从新登录,但是如果每次访问认证中心都要登录一次也挺麻烦,所以这里通过了一个P本身的Cookie来保证已经登录的用户再次访问认证中心时不用再输入账密登录了。

  

  获取到ticket之后C卷土重来,带着ticket这个GET参数访问了A。此时A看到了ticket参数之后不忙着回应,而是把ticket交由P验证。这就是8请求。9回应表示ticket验证通过。因为验证通过了,所以就给出了带有敏感信息的回应10。至此C拿到了内容。但是如果仅凭ticket这个凭证每次访问的话,A每次都还要去找P验证,未免太过麻烦,所以回应10中会通过Cookie或者其他什么形式把访问A的C的信息给记录下来。

  这样,在不久后C第二次访问A时,A可以看到C访问过自己,且当时C通过了P的认证,自己当时还给了它自己的Cookie。所以就认为这个访问值得信任,因此C就无需再进行登录验证。这个过程就是请求11和回应12。

  下面C要尝试访问B了。B的域名和A不一样,所以A下面保存的Cookie无法通用到B上面来。也就是说对于B而言,C仍然处于未登录状态。自然请求13不会被立刻接受。由于B也是在整个单点登录体系里面的,B将请求13重定向到P并给出回应14。这个操作就好比最开始的请求1和回应2。(另一种可能是C带着之前的ticket或者任意一个不正确的ticket访问过来,B拿到ticket后向P求证,P给出了未通过认证的回应后B再给出重定向到P的回应。两者中间差了点小操作不过最终结果还是一样的。)

  根据回应14中给出的P的地址,请求15重定向到了P,因为之前访问过一次了,所以这次的请求15会带上上次的CASTGC,即认证中心Cookie。此时回应16就来了,由于带了Cookie,不用再输入账号密码,P直接给C签发了针对B的ticket。这里需要提一下,CAS默认情况下就是针对不同的域名有不同的ticket的,否则刚才访问B的时候也不会被拒绝了。之后流程和之前访问A时也相似,这个ticket通过GET参数的形式放在URL中,而给出的回应16是一个302,让请求17重定向到了B应用地址。

  这次B获取到ticket之后再去找P验证,验证通过了,这个过程是18,19。最后B给出回应20,至此C的所有请求都得到了回应。

 

posted @ 2018-06-04 22:34  K.Takanashi  阅读(826)  评论(0编辑  收藏  举报