一、从http协议的无状态性说起
HTTP 是无状态协议,它不对之前发送过的请求和响应的状态进行管理。也就是说,无法根据之前的状态进行本次的请求处理。不可否认,无状态协议当然也有它的优点。由于不必保存状态,自然可减少服务器的 CPU 及内存资源的消耗。从另一侧面来说,也正是因为 HTTP 协议本身是非常简单的,所以才会被应用在各种场景里。
但是考虑一些场景,比如用户登录,由于HTTP的无状态性,Web 页面本身也无法进行状态的管理(不记录已登录的状态),那么每次跳转新页面不是要再次登录,就是要在每次请求报文中附加参数来管理登录状态。那么久需要引入其他的技术协同完成状态的记录。
二、Cookie技术的引入
Cookie 技术通过在请求和响应报文中写入Cookie信息来控制客户端的状态。Cookie会根据从服务器端发送的响应报文内的一个叫做Set-Cookie 的首部字段信息,通知客户端保存 Cookie。当下次客户端再往该服务器发送请求时,客户端会自动在请求报文中加入Cookie 值后发送出去。看《图解http中给的cookie讲解》
第一次请求:无cookie时候
其二次请求:有cookie时候
上面两幅可以概括为,服务器端发现客户端发送过来的 Cookie 后, 会去检查究竟是从哪一个客户端发来的连接请求, 然后对比服务器上的记录, 最后得到之前的状态信息。
cookie全解
三、基于session的表单认证
基于表单认证本身是通过服务器端的 Web应用,将客户端发送过来的用户ID和密码与之前登录过的信息做匹配来进行认证的。目前用户的认证多半是基于表单的认证,基于表单的认证一般会使用 Cookie 来管理Session(Session会话,Session代表着服务器和客户端一次会话的过程,直到Session失效(服务端关闭)或者客户端关闭时结束)。
但鉴于 HTTP 是无状态协议, 之前已认证成功的用户状态无法通过协议层面保存下来。 即无法实现状态管理, 因此即使当该用户下一次继续访问,也无法区分他与其他的用户。于是我们会使用Cookie 来管理 Session,以弥补 HTTP 协议中不存在的状态管理功能。
基于session的表单认证过程
简单的来说就是,用户在登录的时候,会在Web服务器中开辟一段内存空间Session用于保存用户的认证信息和其他信息,用户登录成功之后会通过Set-Cookie的首部字段信息,通知客户端保存Cookie,而这Cookie保存的就是服务器端Session的ID,下次请求的时候客户端会带上该Cookie向服务器端发送请求,服务器端进行校验,如果Session中保存的有该ID的Session就表示用户认证通过,否则失败!
基于session的表单认证流程
- cookie和session区别总结:
Session和Cookie的目的相同,都是为了克服HTTP协议无状态的缺陷,但完成的方法不同。Session通过Cookie,在客户端保存SessionID,而将用户的其他会话消息保存在服务端的Session对象中,与此相对的,Cookie需要将所有信息都保存在客户端。因此Cookie存在着一定的安全隐患,例如本地Cookie中保存的用户名密码被破译,或Cookie被其他网站收集
四、session存储及集群下的弊端
Session 是存储在Web服务器(例如:Tomcat)中的,并针对每个客户端(客户),通过SessionID来区别不同用户的。Session是以Cookie技术或URL重写实现,默认以Cookie技术实现,服务端会给这次会话创造一个JSESSIONID的Cookie值。
但是一个显著的问题就是,在集群模式下如果通过Nginx负载均衡的时候,如果有一个用户登录的时候请求被分配到服务器A上,登录成功后设置的Session就会存放在服务器A上了,但是在服务器B上却没有该用户的Session数据,当用户再次发起一个请求的时候,此时请求如果被分配到服务器B上,则就不会查询到该用户的登录状态,就会出现登录失败的情况!
一种可以想到的方式就是将多个Web服务器上存储的Session统一存储到某一存储介质中,保证进集群中的每一台机器都可以看到所有相同Session数据,这里的同步体现在所有的Session存储在同一的存储介质里边。幸运的是我们常用的Tomcat容器已经为我们提供了一个接口,可以让我们实现将Session存储到除当前服务器之外的其他存储介质上,例如Redis等。
- 使用redis集中管理session的缺点
使用Spring Session的方式来实现Session的同步是一件相对比较麻烦的事情,我们虽然使用Redis来进行同步,但是Redis并不是100%可靠的,我们需要对Redis搭建集群、进行主从同步复制、进行持久化等,显然这是一件很复杂的事情
五、JWT横空出世
前面说了cookie和session技术的特点和缺点,以及分布式集群环境下面session的集中管理架构的劣势,那么有没有相对轻巧的解决方案呢?那就是JWT。
Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。该标准被设计为紧凑且安全的,一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息。当然该标准也可直接被用于认证,也可被加密。作为标准,它没有提供技术实现,但是大部分的语言平台都有按照它规定的内容提供了自己的技术实现,所以实际在用的时候,只要根据自己当前项目的技术平台,到官网上选用合适的实现库即可。官网:https://jwt.io
1.JWT特点:
(1)由于它们的尺寸较小,JWT可以通过URL,POST参数或HTTP头部发送。 另外,尺寸越小意味着传输速度越快。
(2)有效载荷包含有关用户的所有必需信息,避免了多次查询数据库的需要。
2.使用场景
(1)验证
这是使用JWT最常见的情况。 一旦用户登录,每个后续请求将包括JWT。它将允许用户访问该令牌允许的路由,服务和资源。 单点登录是当今广泛使用JWT的一项功能,因为它的开销很小,而且能够轻松地跨不同域使用。
(2)信息交换
JWT是在各方之间安全传输信息的好方法, 因为JWT可以被签名(例如使用公钥/私钥对进行签名)。所以你可以确定发件人是他们说的那个人。 此外,由于使用头部(header)和有效载荷(payload)计算签名,因此您还可以验证内容是否未被篡改。
3.JWT的结构
JWT包含三个由点(.)分隔的部分,通常写为:
头部(header).有效负载(payload).签名(signature)
(1)头部(header)
头部(header)通常由两部分组成:令牌的类型(即JWT)和正在使用的散列算法(如HMAC SHA256或RSA),头信息指定了该JWT使用的签名算法:
header = '{"alg":"HS256","typ":"JWT"}'
(2)有效负载(payload)
消息负载包含了JWT的意图,
payload = '{"loggedInAs":"admin","iat":1422779638}'//iat表示令牌生成的时间
(3)签名(signature)
要创建签名部分,您必须采用头部(header),有效载荷(payload),密钥(secret),以及头部中指定的算法。也就是说未签名的令牌由base64url编码的头信息和消息体拼接而成(使用"."分隔),签名则通过私有的key计算而成:
key = 'secretkey'
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)
signature = HMAC-SHA256(key, unsignedToken)
- 最后通base64编码将上面的三个部分进行拼接就成了JWT
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)
# token看起来像这样: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
- 注意
secret是保存在服务器端的,JWT的签发生成也是在服务器端的,secret就是用来进行JWT的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret,那就意味着客户端是可以自我签发jwt了。也就是说JWT常常被用作保护服务端的资源(resource),客户端通常将JWT通过HTTP的Authorization header发送给服务端,服务端使用自己保存的key计算、验证签名以判断该JWT是否可信:
4.JWT案例说明
下面显示了一个登录请求成功之后服务端返回的Token,它由编码头部(header)、编码有效载荷(payload)和签名(signature)通过(.)拼接而成,如果需要,你可以使用jwt.io的Debugger工具,来编码、验证和生成JWT。
输入用户名和密码登录后返回结果
5.JWT优点
(1)因为Json的通用性,所以JWT是可以进行跨语言支持的,像Java、JavaScript、NodeJS、PHP等很多语言都可以使用。
(2)因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
(3)便于传输,JWT的构成非常简单,字节占用很小,所以它是非常便于传输的。
(4)它不需要在服务端保存会话信息, 所以它易于应用的扩展
6.JWT使用注意点
(1)不应该在JWT的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
(2)保护好secret私钥,该私钥非常重要。
(3)如果可以,请使用HTTPS协议,不!是务必使用HTTPS!
六、JWT工作原理
在身份验证中,当用户使用他们的凭证(如用户名、密码)成功登录时,后台服务器将返回一个token,前端接收到这个token将其保存在本地(通常在本地存储中,也可以使用Cookie,但不是传统方法中创建会话,服务器并返回一个cookie)。下次用户想要访问受保护的路由或资源时,就将本地保存的token放在头部Header中发送到后台服务器。服务器接收到请求,检查头部中token的存在,如果存在就允许访问受保护的路由或资源,否则就不允许。
这是一种无状态身份验证机制,因为用户状态永远不会保存在服务器内存中。 由于JWT是独立的,所有必要的信息都在那里,所以减少了多次查询数据库的需求。具体流程如下:
七、OAuth,Token和JWT关系
1.OAuth是一个开放标准,提供了一种简单和标准的安全授权方法,允许用户无需将某个网站的用户名密码提供给第三方应用就可以让该第三方应用访问该用户在某网站上的某些特定信息(如简单的个人信息),现在一般用的是OAuth 2.0(不兼容1.0).
Oauth2.0协议处理流程
2.Token就是获取信息的凭证,如上述的Access Token
3.JWT: JSON Web Tokens, 这是一个开放的标准,规定了一种Token实现方式,以JSON为格式
总结:这三个相互连接且是由大到小的一种关系,OAuth规定授权流程,Token为其中一环的一个信息载体,具体的一种实现方式由JWT规定.
网友评论