
阅读任何一篇文章,初衷都是登录希望对自己有收获、有所成长,鉴权但目前有些的流程文章干货过于少,读完感觉没任何收获,浅析前端还白白浪费了时间。登录在阅读本文前,鉴权为了打消这种想法,流程提前将本文所有涉及到的浅析前端干货列出。实现“对症下药”。登录
Cookie为什么是鉴权最方便的鉴权方式session为什么会在鉴权中被逐渐遗弃token鉴权的工作流程更主流的鉴权登录方式:SSO单点登录APP的自动登录为什么说HTTP是无状态的协议呢?因为它的每个请求都是完全独立的,每个请求包含了处理这个请求所需的流程完整的数据,发送请求不涉及到状态变更。浅析前端即使在HTTP/1.1上,登录同一个连接允许传输多个HTTP请求的鉴权情况下,如果第一个请求出错了,后面的源码库请求一般也能够继续处理(当然,如果导致协议解析失败、消息分片错误之类的自然是要除外的)可以看出,这种协议的结构是要比有状态的协议更简单的,一般来说实现起来也更简单,不需要使用状态机,一个循环就行了。为了方便理解什么是有状态协议和无状态协议,这里举一个小例子。

HTTP设计初衷,是为了解决超文本的传输,更加广义一些就是支持资源的传输,那么在客户端浏览器向HTTP服务器发送请求,继而HTTP服务器将相应的资源发回给客户端这样一个过程中,无论对于客户端还是服务器,都没有必要记录这个过程。
鉴权的初衷但随着前端业务逐渐复杂化,出现了表单等交互项,IT技术网这就导致需要维护一个状态来保证是登录态,最简单的方式就是打一个标记。标记可以理解为用户唯一的标识。
对于前端而言,标记很好获得,在用户登录后拿到后端返回的字段就好了,主要问题在于存在哪里?
存在内存或sessionStroage中,刷新页面或重启浏览器都需要登录,相当于“临时卡”存在localStroage中,无论怎么刷新都不需要登录,相当于“永久卡”在有了标记后,只需每次请求接口时候带上这个标识,服务端就可以辨别出来。
但很显然以上两种都不是好的方案。两种方案都太过于极端并且对于前端而言,主动存数据,又得把数据带到接口里,是一件很麻烦且累赘的事情。这时就需要借助浏览器的帮助---Cookie。
没错,Cookie就是鉴权的基石。免费信息发布网Cookie作为前端存储数据的一种方式,相比于localStroage等存储方式,最方便的就在于它可以借助浏览器的能力,在前端无感知的情况下种下数据。
所以,一般网页鉴权的过程是:
在登录接口登陆成功后,通过HTTP返回头Response里的 Set-Cookie 字段,直接种到浏览器的当前域名下之后浏览器发起的一系列请求,HTTP请求头Request里的Cookie字段会带上sessionId,后端去校验sessionIdCookie的优势在于不用前端主动去存储标记,也不用去关心这个标记会存在多久,因为Cookie支持配置Expires / Max-Age字段,浏览器可以根据本地时间,决定 Cookie 是否过期。值得注意的是本地时间可能会存在不准,会导致Cookie无法在服务端规定时间后过期。
了解了 Cookie 后,我们知道 Cookie 是最便捷的维持 HTTP 请求状态的方式,大多数前端鉴权问题都是靠 Cookie 解决的。
对于前端来说,Cookie种sessionId这个方案看起来还是挺理想的,解决了头疼的HTTP无状态问题;但对于服务端而言,可就不太友好了,每个人只需要保存自己的sessionId,而服务器要保存所有人的sessionId !如果访问服务器多了, 就得有成千上万,甚至几十万个。
这对服务器说是一个巨大的开销 , 严重的限制了服务器扩展能力, 比如说我用两个机器组成了一个集群, 一个人通过机器A登录了系统, 那sessionId会保存在机器A上, 假设那个人的下一次请求被转发到机器B怎么办?机器B可没有这个人的 sessionId啊。这时会有两种方案来解决:
(1) 将机器A上的sessionId复制到机器B上:

(2) 将所有的sessionId存在单独一个机器上:

可以看到方案1的花销过于大。尤其对于千万级别用户的应用来说,将每个机器之间的sessionId同步,耗时耗力还不能保证准确性;方案2看似解决了方案1同步问题,但新的问题又随之而来,如果机器C宕机了,我岂不是拿不到数据,所有用户都要重新登录。
所以这个sessionId看似不是一个好的鉴权方案,虽然方便了前端,但对于服务端的压力过于庞大。那有没有一种前后端都比较方便的鉴权方式呢?答案是肯定的。
仔细想想,就会发现拿着sessionId去换信息,换到的无非就是你的登录信息,但对于一个登录信息并且存在于session中,又会有多大呢,为什么不直接打包传给前端,这样客户端也不用去拿id去换数据,直接拿到Cookie中带的数据就可以验证了。
Token安全性这是一个老生常谈的问题了,不论是sessionId还是Token,都是存在于前端的Cookie中。简单的加密方式base64(其实这也不算加密)也是很容易就被破解,然后拿到用户的信息的,所以不能在Token里携带一些隐私的信息,例如密码等。其次服务端是需要手动去维护一个签名和加密方式,将用户信息和签名通过维护的加密方式加密后再传递给前端,这样用户的数据也得到了保障。

上面的做法虽然保证了数据的安全性,但没有规范的格式要求。为了保证传递Token的格式规范,可以使用JWT。
JSON Web Token (JWT) 是一个开放标准,定义了一种传递 JSON 信息的方式。这些信息通过数字签名确保可信。

最终,再把这三段字符串通过.拼接起来生成最终的jwt的token。这样可以保证用户的真实信息不会被轻易拿到。但是无法保证的是,如果一个人的Token被别人偷走了,客户端也会认为小偷就是合法用户,这其实和一个人的sessionId 被别人偷走是一样的。
Token校验流程
之前提到的那个Cookie过期时间的问题也随之解决了,因为在每一次请求接口前,服务端都会去校验接口header头里的Token,直接把过期时间写在Token里。如果登录信息过期后,返回错误信息,前端可以直接重定向到登录页面。
Refresh Token控制权限Token最主要的功能有两个:
保证用户处于登录态无需再登录;可以获知用户拥有什么样的权限(在后台项目中权限尤为重要);其中对于第二点,对于权限敏感的业务,客户端所希望的Token时间肯定是越短越好,一是避免被他人盗用,二是如果这个用户的权限突然发生了改变,Token也会发生改变。对于Token的获取,目前的处理方案只有登陆换取Token,但这很明显不合理,所以我们是需要再增加一个临时Token(access Token)而且支持刷新来保障用户的权限及业务安全。

对于Refresh Token也失效的场景,直接重定向到登录页即可,这样保证了用户在无感知刷新的情况下修改用户的个人权限以及敏感业务权限。
对于正常的前端(包括前台及后台)业务,上述的Token鉴权已经足以满足发开需求,对于用户侧而言也是体验良好且无感知的。
随着各公司业务不断壮大,公司内部可能出现了多款应用,对于用户侧而言,肯定是希望同一公司的产品只用登录一次就可以使用所有,而不是每个应用都要登录。
例如登录飞书某个工具台应用后,我可以无需登录的继续使用飞书的其它工具台
不同域名下的单点登录本文只讨论不同域名下的登录鉴权。对于应用都在同一域名下的场景,只需在Cookie中设置domain为主域名就好,每个应用拿到Token中数据解析就好,故不做讨论。
真正的单点登录是实现一次登陆,全线通用。通俗来说就是有一个独立的认证服务系统来管理Token,
即SSO登录。SSO登录介绍与接入:

具体操作可以分为以下几步:
(1):
用户首次打开应用A,无SSO凭证,首先跳转到SSO后再次跳转到登录页用户登录成功后,跳转到SSO获得SSO凭证和Token前端将Token种在应用A当前域名下再次访问应用A并且携带Token,后端从SSO处校验Token校验通过,返回接口数据(2):
用户首次打开应用B,有SSO凭证,跳转到SSO直接获得Token前端将Token种在应用B当前域名下再次访问应用B并且携带Token,后端从SSO处校验Token校验通过,返回接口数据上述是一个最简单的单点登录实践,但对于浏览器而言,对于跨域的要求限制越来越严格,不能再支持将SSO返回的Token种在指定的域名下,所以对于种Cookie的操作,还是需要放在业务侧来实现。就有了以下的改进方案。

看似流程繁琐了,其实说白了就是与之前多了一步Code换Token的过程。
具体操作还是可以分为以下几步:
(1):
用户首次打开应用A,无SSO凭证,首先跳转到SSO后再次跳转到登录页用户登录成功后,跳转到SSO获得SSO凭证和Code再次访问应用A并且携带Code,后端从SSO处校验Code后换到Token返回接口中将拿到的Token设置到Set-Cookie中再次访问应用A并且携带Token,后端从SSO处校验Token校验通过,返回接口数据(2):
用户首次打开应用B,有SSO凭证,跳转到SSO直接获得Token前端将Token种在应用B当前域名下再次访问应用B并且携带Code,后端从SSO处校验Code后换到Token返回接口中将拿到的Token设置到Set-Cookie中再次访问应用B并且携带Token,后端从SSO处校验Token校验通过,返回接口数据多出的这两步操作,不仅避免了跨域无法设置Cookie的问题,也保证了前端不参与Token的存储和携带。对于服务端而言,也仅仅是多了一次校验过程,鉴权的双赢方案!!!
之前讨论的内容都是浏览器页面的登录逻辑,对于一个合格的移动端APP,上述操作显然是不合理的。用户在首次登录后,后续进入APP中是不应该再登录的,且要保持一个长登陆状态(即至少要1年内免登录)
一般做法对于自动登录的实现,还是借鉴了Refresh Token的思想,通俗来讲就是我用长期Token来换临时登录Token(具体流程与Refresh Token控制权限大体流程一致,这里不再重复赘述),主要将有区别的点列出:
(1)换取Token的参数校验:
// 接口的请求参数可以参考以下参数
{
//用户账号,大多数APP用的是手机号登录,这里也可以是其他值,能表名是将要自动登录的用户即可
"userid":"xxxxxxxxxxxxx",
//手机设备的唯一值
"imei":"xxxxxxxxxxxxx",
//刷新token的令牌
"refresh_token":"xxxxxxxxxxxxx"
}(2)下发新的refresh_token
对于refresh_token没过期的情况下,通过接口会拿到两组Token,即access_token和新的refresh_token,保证刷新后的refresh_token短期内不过期;
// 返回参数示例
{
//状态位,ok表示成功
"status":"0",
//申请的有效token值
"token":"xxxxxxxxxxxxx",
//token过期情况下,用来刷新access_token值,设置30天的有效期
"refresh_token":"xxxxxxxxxxxxx"
}而对于refresh_token过期的情况,表明用户长时间未进入APP进行操作,则需要跳转到登录页。
优化做法(更好的用户体验)上述做法的弊端在于数据太过于固定化,对于不同的imei请求后,则需要跳转到登录页,这对于一些追求极致用户体验的场景来说显然是不友好的。
例如用户A买了某品牌的型号为X的手机,在该品牌应用市场下载并登录APP并且使用一段时间后,用户A再次购买了相同品牌的型号为Y的手机,通过数据备份后打开这款APP,一般做法下因为手机设备唯一值的限定,会让用户A重新登录,这其实是失去了数据备份,同步的意义。那该如何解决这个问题?
“在编程中,遇到任何问题,加一层中间层就能解决! ”这句话同样适用在这个场景下。所以这里简单针对IOS系统做分析,其他手机同理。
对于同一个苹果ID的用户,即使他中间更换了设备,我通过接口拿到的imei后其实多做了一层请求,通过IOS提供给开发者的接口来获取到当前设备所属于的AppleId,通过AppleId来校验是否属于同一设备,简单理解来说就是在真正APP登录前接入了苹果登录,通过设备换取了同一个AppleId的公钥,将公钥作为了唯一标识。
对于今后规模化的多应用鉴权,可以选择单点登录+Refrsh Token的开发模式,既避免了重复登录,还保障了权限!对于移动APP,本文只是简单阐述了自动登录的逻辑,如果要做更深的研究分析,还需阅读掌握更多技术知识。
(责任编辑:IT科技类资讯)