看任何文章的初衷都是希望自己有所收获,有所成长。浪费时间。在看这篇文章之前,为了打消这个念头,提前把本文涉及的所有干货罗列出来。实现“对症下药”。为什么cookie是最方便的认证方式?为什么session在认证中逐渐被抛弃?Token认证工作流程比较主流的认证登录方式:SSO单点登录APP自动登录后台HTTP是无状态HTTP为什么是无状态状态协议呢?因为它的每一个请求都是完全独立的,每个请求都包含了处理请求所需的完整数据,发送请求不涉及状态变化。即使在HTTP/1.1上,允许在同一个连接上传输多个HTTP请求时,如果第一个请求失败,后面的请求一般都可以继续处理(当然,如果协议解析失败或者出现报文分片错误类自然是排除在外的)可以看出,这个协议的结构比有状态的协议要简单,总体来说实现起来也更简单。不需要使用状态机,循环即可。为了方便理解什么是有状态协议和无状态协议,这里举个小例子。HTTP设计的初衷是为了解决超文本的传输,从更广义的意义上说,它支持资源的传输。然后,在客户端浏览器向HTTP服务器发送请求,然后HTTP服务器将相应的资源返回给客户端的过程中,无论是客户端还是服务器端,都不需要记录这个过程。认证的初衷是随着前端业务越来越复杂,出现了表单等交互项,导致需要维护一个状态,保证是登录状态。最简单的方法是标记它。标签可以理解为用户的唯一标识符。对于前端来说,获取标记很容易。用户登录后,拿到后台返回的字段就可以了。主要问题是它存在于何处?存储在内存或sessionStroage中,刷新页面或重启浏览器时需要登录,相当于存储在localStroage中的“临时卡”,无论怎么刷新都不需要登录,相当于“永久卡”。请求接口的时候带上这个标识,服务端可以识别。但很显然,以上两种都不是很好的解决方案。两种方案都过于极端,而且对于前端来说,主动存储数据,把数据带到界面上,是非常麻烦和繁琐的。这时候就需要浏览器的帮助---Cookie。Cornerstone-Cookie是的,Cookie是身份验证的基石。作为前端存储数据的一种方式,cookies相对于localStroage等存储方式来说是最方便的,因为它可以利用浏览器的能力在前端不知情的情况下植入数据。因此,网页认证的一般流程是:登录界面登录成功后,通过HTTP返回Response头中的Set-Cookie字段,直接种在current下后由浏览器发起的一系列请求浏览器的域名,HTTP请求头Request中的cookie字段会携带sessionId。后端验证sessionIdcookie的好处是前端不需要主动存储mark,也不需要关心mark会存在多久,因为cookie支持Expires/Max的配置-age字段,浏览器可以根据本地时间判断cookie是否过期。值得注意的是,本地时间可能不准确,会导致cookie在服务器端指定时间后无法过期。了解cookies后,我们知道cookies是维护HTTP请求状态最方便的方式。大多数前端认证问题都是通过cookie来解决的。绊脚石-Session对于前端来说,CookiesessionId的方案似乎相当理想,解决了令人头疼的HTTP无状态问题;但是对于服务器端来说,就不太友好了,每个人只需要保存自己的sessionId,而服务器应该保存每个人的sessionId!如果接入服务器很多,那肯定有几万,甚至几十万。这对服务器来说是一个巨大的开销,严重限制了服务器的扩展能力。比如我用两台机器组成一个集群,一个人通过机器A登录系统,那么sessionId就会保存在机器A上。假设如果这个人的下一个请求转发到机器B上呢?机器B没有这个人的sessionId。这时有两种解决方案:(1)将机器A上的sessionId复制到机器B:(2)将所有sessionId存储在一台机器上:可以看到方案1的代价太大了。尤其是千万级用户的应用,各机之间同步sessionId费时费力,且准确性无法保证;方案二貌似解决了方案一的同步问题,但是新的问题接踵而至,如果C机宕机,我就拿不到数据了,所有用户都得重新登录。所以这个sessionId似乎不是一个好的认证方案。对前端来说虽然方便,但是对服务器的压力太大了。有没有一种前后端都更方便的认证方式?答案是肯定的。点亮灯-Token仔细想想,你会发现,你是用sessionId来交换信息的,交换的无非就是你的登录信息,但是对于一个存在于session中的登录信息来说,有多大呢?,为什么不直接打包传给前端,这样客户端就不需要用id交换数据了,可以直接拿到cookie里面的数据进行校验。令牌安全是一个老生常谈的问题。sessionId和Token都存在于前端Cookie中。简单的加密方式base64(其实这不是加密)也容易被破解,进而获取到用户的信息,所以Token中不能携带一些密码等隐私信息。其次,服务端需要手动维护一个签名和加密方式,通过维护的加密方式对用户信息和签名进行加密,然后传递给前端,这样用户的数据也有保障。上述方式虽然保证了数据的安全性,但是没有标准的格式要求。为了保证传递的Token的格式规范,可以使用JWT。JSONWebToken(JWT)是一种开放标准,它定义了一种传递JSON信息的方式。信息经过数字签名以确保真实性。第一段是Header部分(header),定义了加密方式和Token类型。第二段是Payload部分(load),定义了我们需要加密的数据,base64url将数据加密为JSON格式。第三段是Signature部分(Signature),上图中前两个加密的base64url密文用.拼接在一起,然后HS256加密,然后HS256密文base64url加密,最后是第三段获得了令牌。最后这三个字符串通过拼接在一起。生成最终的jwt令牌。这样可以保证用户的真实信息不会被轻易获取。但是不能保证如果一个人的Token被别人偷了,客户端也会认为小偷是合法用户,这其实和一个人的sessionId被别人偷了是一样的。Token验证过程之前提到的cookie过期时间的问题也解决了,因为每次请求接口之前,服务端都会在接口的header中验证Token,直接把过期时间写在Token中。如果登录信息过期后返回错误信息,前端可以直接重定向到登录页面。RefreshToken控制权限Token主要有两个作用:保证用户处于登录状态,无需重新登录;知道用户有什么权限(权限在后台项目中尤为重要);其中,对于第二点,对权限敏感的业务,客户端要求的Token时间要尽可能的短。一是避免被别人盗用,二是如果用户的权限突然发生变化,Token也会发生变化。对于Token的获取,目前的解决方案是登录换取Token,但这显然是不合理的,所以我们需要添加一个临时Token(accessToken)并支持刷新,以保证用户权限和业务安全。对于RefreshToken同样失效的场景,直接跳转到登录页面即可,保证用户无需感知刷新即可修改用户个人权限和敏感业务权限。对于正常的前端(包括前端和后端)服务来说,上述的Token认证足以满足开发的需要,对于用户端来说也是一种无感知的良好体验。风向标——单点登录随着每个公司业务的不断发展,公司内部可能会出现多个应用。对于用户端来说,肯定是希望同一个公司的所有产品都只需要登录一次就可以使用,而不是一个个应用。登录。比如在飞书登录了某个工具平台应用后,不登录也可以继续在飞书使用其他工具平台。名字。对于应用都在同一个域名下的场景,只需要在cookie中设置domain为主域名,每个应用都可以拿到token中的数据进行分析,这里不做讨论。真正的单点登录是实现一次登录,一刀切。一般来说,有一个独立的认证服务系统来管理Token,即SSO登录。SSO登录介绍及接入:具体操作可分为以下几个步骤:(1):用户第一次打开应用A,没有SSO凭证,先跳转到SSO后再次跳转到登录页面用户后登录成功,跳转到SSO获取SSO证书和Token。前端会在应用A的当前域名下植入Token,再次访问应用A并携带token。后端会验证来自SSO的token,并返回接口数据(2):用户第一次打开应用B,有SSO证书,跳转到SSO直接获取Token。前端会在应用B的当前域名下植入Token,再次访问应用B并携带token。后端会验证来自SSO的token,并返回接口数据。以上是最简单的单点击登录的做法,但是对于浏览器来说,跨域要求越来越严格,已经不能支持SSO返回的Token被种到指定域名下了,所以操作cookie的种植还是需要放在业务端去完成。有以下改进。看似过程繁琐,但说白了就是在兑换代币的过程中多了一个步骤。具体操作还是可以分为以下几个步骤:(1):用户第一次打开没有SSO证书的AppA,先跳转到SSO,然后再次跳转到登录页面,用户登录成功后,跳转到SSO获取SSO证书和Code再次访问应用A并携带Code。后端验证来自SSO的Code,更改为Token返回接口。在Set-Cookie中设置获取到的Token。再次访问应用A,携带Token,后台验证来自SSO的Token。验证通过,返回接口数据(2):用户第一次打开应用B,有SSO证书,直接跳转到SSO获取Token。验证Code后,切换到Token返回界面,将获取到的Token设置在Set-Cookie中,再次访问AppB并携带Token。后端验证来自SSO的Token并验证通过,返回额外的两个接口数据。这样分步操作,既避免了无法跨域设置cookie的问题,又保证了前端不参与Token的存储和携带。对于服务端来说,只是多了一个验证过程,验证双赢!!!Trend-APP自动登录前面讨论的内容是浏览器页面的登录逻辑。对于一个合格的手机APP来说,以上操作显然是不合理的。用户首次登录后,进入APP后不得再次登录,必须保持长期登录状态(即至少1年内无需登录)。一般的做法是借鉴RefreshToken的思想来实现自动登录。通俗的说,我是用long-termtoken换取临时登录token(具体过程和RefreshToken控制权限的过程大体是一致的,这里不再赘述),主要列出不同点:(1)兑换Token的参数验证//接口的请求参数可以参考如下参数{//用户账号,大部分APP使用手机号登录,这里也可以使用其他值,以及表名可以是自动登录的用户"userid":"xxxxxxxxxxxxxxxxx",//移动设备唯一值"imei":"xxxxxxxxxxxxx",//刷新token"refresh_token":"xxxxxxxxxxxxxxx""}(2)如果refresh_token没有过期,则发出新的refresh_token,通过接口,会得到两组Token,分别是access_token和newrefresh_token,保证刷新后的refresh_token在短时间内不会过期时间;//返回参数示例{//状态位,ok表示成功"status":"0",//应用的有效token值"token":"xxxxxxxxxxxx",//token过期时,用于刷新access_token值,设置有效期为30天"refresh_token":"xxxxxxxxxxxx"}对于过期的refresh_token,说明用户如果长时间没有进入APP,需要跳转到登录页面。优化方法(更好的用户体验)上述方法的缺点是数据过于固定。对于不同的imei请求,需要跳转到登录页面,这对于一些追求极致用户体验的场景来说显然是不友好的。的。例如,用户A购买了某品牌型号为X的手机。在该品牌应用市场下载并登录APP并使用一段时间后,用户A再次购买了同品牌的ModelY手机,备份数据后打开APP。APP,一般情况下,因为移动设备的唯一价值的限制,会让用户A重新登录,实际上失去了数据备份和同步的意义。那么如何解决这个问题呢?“在编程中,如果遇到什么问题,可以通过增加一个中间层来解决!”这句话同样适用于这种场景。所以这里针对IOS系统简单分析一下,其他手机同理。对于同一个AppleID的用户,即使他中途换了设备,我其实是在通过接口获取imei后多了一个请求,通过IOS提供的接口获取当前设备所属的AppleId给开发人员。使用AppleId验证是否属于同一设备。简单的说就是在真正的APP登录之前先连接苹果登录,通过设备交换同一个AppleId的公钥换取同一个AppleId,并以公钥作为唯一标识。总结对于以后大规模的多应用认证,可以选择单点登录+RefrshToken的开发模式,既避免了重复登录,又保证了权限!对于手机APP,本文仅简单说明一下自动登录的逻辑。如果你想做深入的研究和分析,你需要阅读和掌握更多的技术知识。