当前位置: 首页 > 后端技术 > Java

认证5兄弟:cookie、session、token、jwt、单点登录,终于有人说清楚了!

时间:2023-04-01 19:51:27 Java

作者:Henrylulu\来源:juejin.cn/post/6898630134530752520在本文中,您将看到:为什么基于HTTP的前端认证后台cookie是最便捷的存储方案,操作cookie的方式有哪些,以及session方案是如何实现的,存在哪些问题?token方案是如何实现的,如何编码和防篡改?jwt是做什么的?refreshtoken的实现和意义session和token有什么异同,单点登录有什么优缺点?浏览器下的实现思路和处理从“HTTP无状态”状态开始,我们知道HTTP是无状态的。也就是说HTTP请求者和响应者之间不能保持状态,都是一次性的。它不知道之前和之后的请求发生了什么。但是在某些场景下,我们需要维护状态。最典型的是,当用户登录微博时,发帖、关注和评论都应该处于登录用户状态。“标记”的解决方法是什么?::标记::。在学校或公司,从你入学之日起,就会录入你的身份和账户信息,然后发卡给你。以后在园区进行门禁、入住、消费,只需要刷这张卡。“前端存储”这个涉及一帖一店一区。post好办,登录界面直接返回给前端。存储需要前端想办法。前提是你要随身携带卡。前端存储方式有很多种。最别扭的是挂在全局变量上,但这可是一张“经验卡”。一旦刷新页面,高端点就会丢失。会保存在cookies、localStorage等,这是一张“会员卡”。不管怎么刷新,只要浏览器没有清空或者过期,就一直是这个状态。这里不展开前端存储。如果有地方存放,可以组合成参数,在请求的时候带入接口。Cornerstone:Cookie在前端非常麻烦。您必须自己存储它们,并且必须找到一种方法将它们取出。有什么我不需要担心的吗?是的,饼干。Cookie也是一种前端存储,但是相对于localStorage等其他方式,cookie借助HTTP头和浏览器的能力,可以让前端无感知。大致过程是这样的:当提供标记的接口直接通过HTTP返回头的Set-Cookie字段“种”到浏览器时,当浏览器发起请求时,cookie会自动发送给浏览器通过HTTP请求头的Cookie字段。界面“配置:域/路径”清华校园卡不能进北大。cookie是通过Domain(域)/Path(路径)两个层次来限定::“空间范围”::的。Domain属性指定当浏览器发送HTTP请求时应将哪些域名附加到此cookie。如果不指定该属性,浏览器默认将其设置为当前URL的一级域名。比如www.example.com会被设置为example.com,以后如果访问example.com的任何子域名,HTTP请求也会带上这个cookie。如果Set-Cookie字段中服务器指定的域名不属于当前域名,浏览器将拒绝该cookie。Path属性指定当浏览器发送HTTP请求时,该cookie应伴随哪些路径。浏览器只要发现Path属性是HTTP请求路径的开头部分,就会在header信息中带上这个cookie。例如,如果PATH属性是/,那么请求/docs的路径也会包含cookie。当然前提是域名必须一致。——Cookie——JavaScript标准参考教程(alpha)《配置:Expires/Max-Age》卡片毕业后不能正常使用。Cookie还可以通过Expires和Max-Age之一来限制::"timerange"::。Expires属性指定特定的到期时间。指定时间后,浏览器将不再保留cookie。其值采用UTC格式。如果这个属性没有设置,或者设置为null,cookie只在当前会话(session)中有效。一旦浏览器窗口关闭且当前会话结束,cookie将被删除。另外,浏览器根据本地时间判断cookie是否过期。由于本地时间不准确,因此无法保证cookie会在服务器指定的时间过期。Max-Age属性指定cookie从现在开始存在的秒数,比如606024*365(即一年)。这段时间之后,浏览器将不再保留此cookie。如果同时指定了Expires和Max-Age,则Max-Age的值优先。如果Set-Cookie字段没有指定Expires或Max-Age属性,那么这个Cookie就是一个SessionCookie,即只存在于本次会话中,一旦用户关闭浏览器,浏览器将不再保留这个曲奇饼。——Cookie——JavaScript标准参考教程(alpha)《Configuration:Secure/HttpOnly》有的学校规定没有持卡人不可以刷卡(多奇葩的学校,假设);有些学校不允许自己在卡片上贴贴纸。Cookie可以限制::"使用"::。Secure属性指定浏览器只能在加密协议HTTPS下将此cookie发送到服务器。另一方面,如果当前协议是HTTP,浏览器会自动忽略服务器发送的Secure属性。这个属性只是一个开关,不需要指定值。如果通讯是HTTPS协议,这个开关会自动打开。HttpOnly属性指定不能通过JavaScript脚本获取Cookie,主要是因为Document.cookie属性、XMLHttpRequest对象、RequestAPI都无法获取该属性。这样就避免了cookie被脚本读取,只有浏览器发送HTTP请求时才会带上cookie。——Cookie——JavaScript标准参考教程(alpha)《ReadingandWritingHTTPHeaderstoCookies》回过头来看,HTTP是如何写入和传输cookies及其配置的?HTTP返回的一个Set-Cookie头用于向浏览器写入“一个(且只有一个)”cookie,格式为cookie键值+配置键值。例如:Set-Cookie:username=jimu;域名=jimu.com;路径=/博客;过期=2015年10月21日星期三07:28:00GMT;安全的;HttpOnly一次设置多个cookie怎么办?多给一些Set-Cookieheaders(一个HTTP请求中允许重复)Set-Cookie:username=jimu;domain=jimu.comSet-Cookie:height=180;domain=me.jimu.comSet-Cookie:weight=80;domain=me.jimu.comHTTP请求的Cookie头,用于浏览器向服务器发送所有符合当前“空间、时间、使用”配置的cookie。因为浏览器做了筛选判断,不需要返回配置内容,只要发送key值即可。Cookie:用户名=jimu;高度=180;weight=80"前端读写cookies"前端可以自己创建cookies。如果服务器创建的cookie没有添加HttpOnly,恭喜你,你也可以修改他给的cookie。调用document.cookie可以创建和修改cookie。和HTTP一样,document.cookie一次可以也只能操作一个cookie。document.cookie='用户名=jimu;域名=jimu.com;路径=/博客;过期=2015年10月21日星期三07:28:00GMT;安全的;HttpOnly';调用document.cookie也可以读取cookie,也和HTTP一样,可以读取所有非HttpOnly的cookie。console.log(document.cookie);//用户名=jimu;高度=180;weight=80(对于一个cookie属性,为什么读写行为不同?get/set理解)《cookie是维护HTTP请求状态的基石》了解cookie后,我们知道cookie是最方便的维护方式HTTP请求的状态。大多数前端认证问题都是通过cookie来解决的。当然你也可以选择其他的存储方式(后面或多或少会提到)。那么有了收纳工具,接下来要做什么呢?应用方案:服务器session现在想想,你刷卡的时候发生了什么?实际上,您的卡上只存储了一个ID(可能是您的学号)。当你刷卡时,物业系统会检查你的信息和账户,然后决定“你能进这扇门吗?”“这只鸡腿要从哪个账户扣钱?””。这种操作在前后端认证系统中称为session。典型的session登录/认证流程:浏览器登录并发送账号密码,服务器查询用户数据库,对用户进行验证。服务端将用户登录状态保存为session,生成sessionId通过登录接口返回,并将sessionId设置为cookie用于浏览服务端再次请求业务接口,sessionId随cookie一起传给服务器查看sessionId,session校验成功后,业务流程正常执行,返回结果为“Session存储方式”,显然,服务器只是给cookie一个sessionId,而session的具体内容(可能包括用户信息,session状态等),需要自己保存,有几种存储方式:Redis(推荐):In-memorydatabase,redis中文官网。以key-value的形式存储,正是sessionId-sessionData的场景;而且访问速度很快。内存:直接放在变量中。一旦服务重新启动,数据库就消失了:正常的数据库。性能不高。“Session过期销毁”很简单,销毁存储的Session数据即可。“会话分布式问题”通常服务器是一个集群,用户请求会经过一个负载均衡,不一定是哪台机器。一旦用户在后续界面中请求的机器与自己请求登录的机器不一致,或者请求登录的机器宕机了,那session不就失效了吗?这个问题目前有几种解决方案。一种是从“存”的角度将session集中存储。如果我们使用独立的Redis或者普通的数据库,我们可以将所有的session存储在一个库中。二是从“分布”的角度出发,让同一个IP的请求在负载均衡的时候全部发送到同一台机器上。以nginx为例,可以配置ip_hash来实现。但是通常使用第一种方式,因为第二种方式相当于阉割了负载均衡,仍然没有解决“用户请求的机器宕机”的问题。《node.js下的Session处理》上图就很清楚了。要在服务器端实现对cookies和session的访问,还有很多事情要做。在npm中,已经有封装好的中间件,比如express-session-npm,所以就不贴出使用方法了。这是它的一种cookie:express-session-npm主要实现:封装了对cookie的读写操作,并提供了配置项配置字段、加密方式、过期时间等,封装了对session的访问操作,以及提供配置项,配置session的存储方式(内存/redis),存储规则等。为req提供session属性,控制属性set/get和响应cookie和session的访问,为req.session提供一些方法。应用解决方案:tokensession的维护给服务器带来了很大的麻烦。我们要找地方存放它,还要考虑分布的问题,甚至单独为它启用一套Redis集群。有没有更好的办法?我又想到了学校。在没有校园一卡通技术之前,我们都是靠“学生证”。门卫直接将我的脸和学生证上的脸比对了一下,确认了学生证的有效期、年级等信息,才放行。回想一下,在登录的场景下,session中不需要存储太多东西,为什么不直接打包到cookie中呢?这样服务器就不需要保存了。每次只需要验证cookie携带的“凭据”的有效性,也可以携带一些轻量级的信息。这种方法通常称为令牌。token的流程如下:用户登录,服务器验证账号密码,获取用户信息,将用户信息和token配置编码成token,通过cookie集发送给浏览器,然后用户请求业务接口,并通过携带token接口的cookie验证token的有效性,正常业务接口处理“客户端token存储方式”上面提到cookie,cookie并不是客户端存储凭证的唯一方式。由于其“无状态性”,token的有效期和使用限制都包含在token内容中,对cookie的管理能力依赖性小,客户端存储起来更自由。但是web应用的主流方式还是在cookies中,毕竟不用担心。“TokenExpiration”那么我们如何控制token的有效期呢?很简单,用数据插入“过期时间”,验证时判断即可。token的编码方式由人决定。“base64”比如node端的cookie-session-npm库,这个名字不要纠结。它其实是一个token库,但是和express-session-npm保持了高度一致的用法,把要存储的数据挂在session上。默认配置下,当我给他一个userid,他会这样保存:eyJ1c2VyaWQiOiJhIn0=这里只是{"userid":"abb"}的base64。“防篡改”的问题来了。如果用户cdd将{"userid":"abb"}转成base64,然后手动修改自己的token为eyJ1c2VyaWQiOiJhIn0=,是否可以直接访问abb的数据?是的。所以根据情况,如果token涉及敏感权限,就需要想办法防止token被篡改。解决方案是对令牌进行签名,以识别令牌是否已被篡改。比如在cookie-session-npm库中,添加两个配置:secret:'iAmSecret',signed:true,这样就会有一个.sig的cookie,里面的值为{"userid":"abb"}和iAmSecret是通过加密算法计算出来的,比如HMACSHA256类(System.Security.Cryptography)|微软文档。那么,现在cdd可以伪造eyJ1c2VyaWQiOiJhIn0=,但是他不能伪造sig的内容,因为他不知道其中的秘密。“JWT”但是上面的做法增加了cookie的数量,而且数据本身没有标准化的格式,于是JSONWebToken简介——jwt.io诞生了。JSONWebToken(JWT)是一种开放标准,它定义了一种传递JSON信息的方式。信息经过数字签名以确保真实性。是一个成熟的token字符串生成方案,包括我们前面提到的数据和签名。为什么不看看JWT令牌的样子:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyaWQiOiJhIiwiaWF0IjoxNTUxOTUxOTk4fQ.2jf3kl_uKWRkwjOP6uQRJFqMlwSABcgqqcJofFH5XCo你是如何生成这个字符串的?看图:类型,加密算法选项,JWT标准数据字段,可以参考RFC7519-JSONWebToken(JWT)节点也有相关的库实现:express-jwt-npmkoa-jwt-npmrefreshtokettoken,作为权限守护者,最重要的就是“安全”。业务接口用于身份验证的令牌称为访问令牌。越是权限敏感的业务,越是希望accesstoken的有效期足够短,以免被盗用。但是过短的有效期会导致accesstoken频繁过期。到期后我该怎么办?一种方式是要求用户重新登录获取新的token,这显然不够友好。您必须知道某些访问令牌可能只会在几分钟后过期。另一种方法是有另一个令牌,专门用于生成访问令牌的令牌,我们称之为刷新令牌。访问令牌用于访问业务接口。由于有效期足够短,被盗用的风险小,请求方式也可以更宽松、更灵活。刷新令牌用于获取访问令牌,有效期可以更长。通过独立的服务和严格的请求方式增加安全性;由于不经常验证,也可以像上届一样处理。有了refreshtoken之后,几种情况的请求流程变成了这样:如果refreshtoken过期了,就只能重新登录了。Session和tokenSession和token是界限很模糊的概念。如上所述,refreshtoken也可以以session的形式组织和维护。狭义上,我们通常认为session是一种“植入cookie,数据保存在服务端”的认证方案,token是“客户端随处保存,数据保存在客户端”的认证方案令牌”。session和token的比较,本质上是“client保存cookies/保存在别处”和“server保存数据/不保存数据”的比较。"Clientsavecookies/storeselshere"保存cookie很方便,但是问题也很明显:在浏览器端可以使用cookie(其实token经常被当作cookie使用),但是在浏览器端之外,有没有cookie怎么办?cookie是域下浏览器自动携带的,容易导致CSRF攻击(前端安全系列(二):如何防范CSRF攻击?-美团技术团队)存放在别处,可以解决没有cookie的场景;通过parameters手动带上同样的方式来避免CSRF攻击。“Datastorage/non-storageontheserver”数据存储:请求只需要携带id,可以大大缩短认证字符串的长度,减少请求的大小。无数据存储:无需服务器端一套完整的解决方案和分布式处理,降低硬件成本;避免数据库检查带来的验证延迟单点登录我们已经知道,在同域下的客户端/服务器认证系统中,客户端携带凭据来维持一段时间的登录状态。但是当我们的业务线越来越多的时候,更多的业务系统会分布在不同的域名下,我们就需要“一次登录,全线通用”的能力,也就是所谓的“单点登录”。“假”单点登录(同一个主域名)很简单。如果业务系统都在同一个主域名下,比如wenku.baidu.comtieba.baidu.com,就好办了。可以直接把cookie域设置为主域名baidu.com,百度就是这么做的。“真正的”单点登录(不同的主域名),比如滴滴这样的新潮公司,它也拥有didichuxing.comxiaojukeji.comdidiglobal.com等域名,Cookies是完全无法避免的。如果能做到“一次登录,通用”,这才是真正的单点登录。在这种场景下,我们需要一个独立的认证服务,通常称为SSO。《一个完整的“从系统A登录,不登录系统B”的过程》用户进入系统A没有登录票,系统A没有登录就把他跳转到SSOSSO,所以不存在没有票的sso系统(注这个和之前的Aticket不同),输入账号密码登录sso账号密码验证成功,返回通过接口做两件事:一是在sso系统下植入凭证(记录用户在SSO中的登录状态);另一种是签发A票,客户端拿到票,保存起来,请求SystemA与SystemA接口,验证票。成功后,业务请求正常处理。此时用户第一次进入系统B,没有登录凭据(ticket),系统B会给他跳到SSOSSO登录,系统下有证书,不用再次登录,只需要发送票给客户端获取票,保存,请求系统B接口《完整版:考虑浏览器场景》上面的流程貌似没问题,其实很多APP和其他终端这样就够了.但它在浏览器中效果不佳。看这里:对于浏览器来说,如何保存SSO域下返回的数据,以便访问A时可以带上?浏览器对跨域有严格的限制,cookies、localStorage等方法都有域限制。这就需要也只有A可以提供在A域下存储凭证的能力。一般我们是这样做的:在图中,我们用颜色标记浏览器当前所在的域名。注意图中灰色背景文字描述部分的变化。在SSO域下,SSO不直接通过接口返回ticket,而是通过一个带code的URL重定向到系统A的接口。该接口通常约定A注册SSO时将浏览器重定向到A域,用代码访问A的回调接口,回调接口使用代码换票。此代码与机票不同。该代码是一次性的,并在URL中公开。之后在自己的域中设置cookie成功。在后续的请求中,只需要解析cookie中的ticket,去SSO验证即可。访问系统B是一样的。总之,HTTP是无状态的。为了维护前后端请求,需要前端存储。标记cookie是一种完整的标记方法。它通过HTTPheaders或js进行操作,并有相应的安全策略。它是大多数状态管理解决方案的基石。会话是一种状态管理解决方案。前端使用cookie存储ids,后端存储数据。但是后端要处理分布式的问题。Token是另一种状态管理解决方案。与session相比,不需要后端存储,所有数据都存储在前端,解放了后端,释放了灵活性。Token编码技术通常是基于base64,或者增加加密算法防篡改,jwt是比较成熟的编码方案。在复杂的系统中,可以通过服务令牌和刷新令牌去中心化令牌,同时满足安全性和用户体验。session和token的比较是“是否使用cookies”和“后端是否保存”的比较,单点登录要求不同域下的系统“一次性登录,跨域通用”板”。通常由一个独立的SSO系统记录登录状态并出票,各业务系统配合存储和鉴权票。近期热点文章推荐:1.1,000+Java面试题及答案(2022最新版)2.精彩!Java协程来了。。。3.SpringBoot2.x教程,太全面了!4.SpringBoot2.6正式发布,一大波新特性。。。5.《Java开发手册(嵩山版)》最新发布,赶快下载!感觉不错,别忘了点赞+转发!