背景知识:
Authentication和Authorization的区别:
Authentication:用户认证,指的是验证用户的身份,例如你希望以小A的身份登录,那么应用程序需要通过用户名和密码确认你真的是小A。
Authorization:授权,指的是确认你的身份之后提供给你权限,例如用户小A可以修改数据,而用户小B只能阅读数据。
由于http协议是无状态的,每一次请求都无状态。当一个用户通过用户名和密码登录了之后,他的下一个请求不会携带任何状态,应用程序无法知道他的身份,那就必须重新认证。因此我们希望用户登录成功之后的每一次http请求,都能够保存他的登录状态。
目前主流的用户认证方法有基于token和基于session两种方式。
基于session的用户认证
基于session的认证流程如下:
-
用户输入其登录信息
-
服务器验证信息是否正确,并创建一个session,然后将其存储在数据库中
-
服务器为用户生成一个sessionId,将具有sesssionId的Cookie将放置在用户浏览器中
-
在后续请求中,会根据数据库验证sessionID,如果有效,则接受请求
-
一旦用户注销应用程序,会话将在客户端和服务器端都被销毁
基于token(令牌)的用户认证
最常用的是JSON Web Token(jwt):
-
用户输入其登录信息
-
服务器验证信息是否正确,并返回已签名的token
-
token储在客户端,例如存在local storage或cookie中
-
之后的HTTP请求都将token添加到请求头里
-
服务器解码JWT,并且如果令牌有效,则接受请求
-
一旦用户注销,令牌将在客户端被销毁,不需要与服务器进行交互一个关键是,令牌是无状态的。后端服务器不需要保存令牌或当前session的记录。
jwt的组成
jwt的认证原理:
一个jwt实际上就是一个字符串,它由三部分组成,头部、载荷与签名,这三个部分都是json格式。
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。
{
"typ": "JWT",
"alg": "HS256"
}
在这里,我们说明了这是一个JWT,并且我们所用的签名算法是HS256算法。
载荷(Payload)
载荷可以用来放一些不敏感的信息。
{
"iss": "John Wu JWT",
"iat": 1441593502,
"exp": 1441594722,
"aud": "www.example.com",
"sub": "jrocket@example.com",
"from_user": "B",
"target_user": "A"
}
这里面的前五个字段都是由JWT的标准所定义的。
iss: 该JWT的签发者
sub: 该JWT所面向的用户
aud: 接收该JWT的一方
exp(expires): 什么时候过期,这里是一个Unix时间戳
iat(issued at): 在什么时候签发的
把头部和载荷分别进行Base64编码之后得到两个字符串,然后再将这两个编码后的字符串用英文句号.连接在一起(头部在前),形成新的字符串:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0
签名(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
将这三部分用.连接成一个完整的字符串,构成了最终的jwt(token):
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
区别和优缺点:
基于session和基于jwt的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而jwt是保存在客户端的。
jwt的优点:
-
可扩展性好
应用程序分布式部署的情况下,session需要做多机数据共享,通常可以存在数据库或者redis里面。而jwt不需要。 -
无状态
jwt不在服务端存储任何状态。RESTful API的原则之一是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。用户的认证状态引入这种附加影响,这破坏了这一原则。另外jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 JWT,可以降低服务器查询数据库的次数。 -
跨平台跨域
-
相比Session,无需落地存储
jwt的缺点:
5. 安全性
由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。
- 性能
jwt太长。由于是无状态使用JWT,所有的数据都被放到JWT里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage里面。并且用户在系统中的每一次http请求都会把jwt携带在Header里面,http请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用jwt的http请求比使用session的开销大得多。
- 一次性
无状态是jwt的特点,但也导致了这个问题,jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。
(1)无法废弃
通过上面jwt的验证机制可以看出来,一旦签发一个jwt,在到期之前就会始终有效,无法中途废弃。例如你在payload中存储了一些信息,当信息需要更新时,则重新签发一个jwt,但是由于旧的jwt还没过期,拿着这个旧的jwt依旧可以登录,那登录后服务端从jwt中拿到的信息就是过时的。为了解决这个问题,我们就需要在服务端部署额外的逻辑,例如设置一个黑名单,一旦签发了新的jwt,那么旧的就加入黑名单(比如存到redis里面),避免被再次使用。
(2)续签
如果你使用jwt做会话管理,传统的cookie续签方案一般都是框架自带的,session有效期30分钟,30分钟内如果有访问,有效期被刷新至30分钟。一样的道理,要改变jwt的有效时间,就要签发新的jwt。最简单的一种方式是每次请求刷新jwt,即每个http请求都返回一个新的jwt。这个方法不仅暴力不优雅,而且每次请求都要做jwt的加密解密,会带来性能问题。另一种方法是在redis中单独为每个jwt设置过期时间,每次访问时刷新jwt的过期时间。
可以看出想要破解jwt一次性的特性,就需要在服务端存储jwt的状态。但是引入 redis 之后,就把无状态的jwt硬生生变成了有状态了,违背了jwt的初衷。而且这个方案和session都差不多了。
总结
适合使用jwt的场景:
- 有效期短
- 只希望被使用一次
比如,用户注册后发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其他可能的账户,一次性的。这种场景就适合使用jwt。
而由于jwt具有一次性的特性。单点登录和会话管理非常不适合用jwt, 需要结合OAuth2一起才能实现单点登录,如果在服务端部署额外的逻辑存储jwt的状态,那还不如使用session。基于session有很多成熟的框架可以开箱即用,但是用jwt还要自己实现逻辑。
单点登录
单点登录常用的实现方式有两大类: 站内单点登录, 站内外单点登录(就是可以在第三方服务进行登录).
站内单点登录:
实现技术有
- CAS 中央认证服务(Central Authentication Service)
- Apache Shiro
- Spring Security CAS
站内外单点登录:
实现技术有
- Spring Security OAuth2 + JWT
CAS的单点登录和OAuth2的最大区别
以下参考: https://www.jianshu.com/p/63115c71a590
CAS的单点登录时保障客户端的用户资源的安全
oauth2则是保障服务端的用户资源的安全
CAS客户端要获取的最终信息是,这个用户到底有没有权限访问我(CAS客户端)的资源。
oauth2获取的最终信息是,我(oauth2服务提供方)的用户的资源到底能不能让你(oauth2的客户端)访问
CAS的单点登录,资源都在客户端这边,不在CAS的服务器那一方。
用户在给CAS服务端提供了用户名密码后,作为CAS客户端并不知道这件事。
随便给客户端个ST,那么客户端是不能确定这个ST是用户伪造还是真的有效,所以要拿着这个ST去服务端再问一下,这个用户给我的是有效的ST还是无效的ST,是有效的我才能让这个用户访问。
oauth2认证,资源都在oauth2服务提供者那一方,客户端是想索取用户的资源。
所以在最安全的模式下,用户授权之后,服务端并不能直接返回token,通过重定向送给客户端,因为这个token有可能被黑客截获,如果黑客截获了这个token,那用户的资源也就暴露在这个黑客之下了。
于是聪明的服务端发送了一个认证code给客户端(通过重定向),客户端在后台,通过https的方式,用这个code,以及另一串客户端和服务端预先商量好的密码,才能获取到token和刷新token,这个过程是非常安全的。
如果黑客截获了code,他没有那串预先商量好的密码,他也是无法获取token的。这样oauth2就能保证请求资源这件事,是用户同意的,客户端也是被认可的,可以放心的把资源发给这个客户端了。
所以cas登录和oauth2在流程上的最大区别就是,通过ST或者code去认证的时候,需不需要预先商量好的密码。
- OAuth2 和JWT区别与联系
JWT是一种认证协议
JWT提供了一种用于发布接入令牌(Access Token),并对发布的签名接入令牌进行验证的方法。
令牌(Token)本身包含了一系列声明,应用程序可以根据这些声明限制用户对资源的访问。OAuth2是一种授权框架
另一方面,OAuth2是一种授权框架,提供了一套详细的授权机制(指导)。用户或应用可以通过公开的或私有的设置,授权第三方应用访问特定资源。