本文转载自微信公众号「Java极客技术」,作者鸭血粉丝。转载本文请联系Java极客技术公众号。
授权机制,当我们说到这个问题的时候,大家对它的第一印象是在哪个地方呢?是不是曾经某培训机构教授的 SSO 单点登录的,是的没错,而这种 SSO 的单点登录在当年的培训机构中,使用的就是 Session 共享,也就是用 Redis 做中间模拟 Session ,但是授权机制真的有这么简单么?接下来阿粉就来强势对比一下关于授权机制了。
Cookie-Session 认证授权
Cookie-Session 认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。
但是这时候我们就得考虑一下 Cookie 的存活时间了,当我们关闭浏览器的时候,Cookie就会被删除,就算我们调整了 Cookie 的存活时间,但是他依然有很大的弊端,Cookie 是很容易被拦截到的,阿粉之前就看到过某个知名的 OA 系统,就曾经把用户的ID 放到了 Cookie 中,只不过是把 Cookie 里面的键给设置成了 imageUrl,但是实际上这种,看着有点搞人的意思,图片地址是一堆长的字符串,你在前端拦截到之后, 明眼人一眼就能知道这种肯定不是图片路径,而且当我们使用 Cookie 进行用户识别,用户就会很容易受到跨站请求伪造的攻击,也就是我们经常说的 CSRF .
JWT
既然在这里对比 JWT ,我们就得先知道 JWT 是个什么东西,JSON Web Token (JWT) 实际上用大白话说,它就是一种认证机制,让后台知道请求是来自于受信的客户端。
技术都是随着问题出现的,只要有问题,那么很快就会有解决这个问题的技术出现,同样,JWT 出现的只不过比较早而已,因为现在微服务,分布式横行遍布,不管是大公司,还是小公司,很多都开始做分布式的项目,这做分布式也不仅仅是停留在了只存在大公司了,既然选择使用了分布式,那么各种各样的问题就来了。
- 跨域身份验证
- 分布式session共享
- 分布式站点的单点登录
JWT 是个什么玩意
我们先看一下官方网站给的内容,What is JSON Web Token?
- JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA
JSON Web令牌(JWT)是一种开放标准(RFC7519),它定义了一种紧凑且独立的方式,用于在各方之间安全地作为JSON对象传输信息。由于该信息是数字签名的,因此可以验证和信任此信息。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对对对JWTs进行签名.
阿粉就直接用百度翻译了,结果翻译出来竟然差不多,看来百度翻译有时候也没有那么差劲。
那么什么时候需要去使用 JWT 呢?
在官网中,给出了两种情况下去使用 JWT ,Authorization 和 Information Exchange,一种是授权,授权我们都懂,就是当用户登录后,每个后续请求都将包括JWT,允许用户访问该令牌允许的路由、服务和资源,而资源交换,实际上简单的说,就是在数据传输中用 JWT 令牌在安全地在各方之间传输信息
那么我们既然知道了什么时候来使用 JWT, 我们就来看看 JWT 到底是长成什么样子,
JWT 构成:
- Header 头部
- Payload 有效载荷
- Signature 签名
我们从官网上获取了一段的内容,然后逐个来看,
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
既然它说 JWT 是由三段信息构成的,而这三段信息,是用 .来进行隔开的,也就是上面这长串的字符串,
Header 头部,我们看到图里面也给出了,Header 中存储的实际上就是2部分的内容。typ:类型 alg:加密算法,
然后他是对头部进行的 Base64 加密,我就是我们在官网摘下来的第一段的内容,就出现了加密字符串 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 。
Payload 有效载荷
实际上有效载荷实际上就是存储有效信息的地方,那么他都存储了一些什么内容呢?
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
jti (JWT ID):编号( jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击)
为什么会有这么多,因为在 JWT 的规范中,他告诉我们的是,建议但不强制使用,也就是说,你可以根据自身的应用去选择使用,比如官网给出的,他就没有写全面,就使用了三个:
- {
- "sub": "1234567890",
- "name": "John Doe",
- "iat": 1516239022
- }
而实际上这部分的内容却是比较重要的内容。
signature签名信息
实际上这个就是一个组装起来的,将头部和载荷用’.'号连接,再加上一串密钥,经过头部声明的加密算法加密后得到签名。
这个 Header 和 Payload 都是加密过的,而在这个地方它还进行了 “加盐” 的操作,将这三部分用.连接成一个完整的字符串,构成了最终的jwt
jwt其实并不是什么高深莫测的技术,在很多技术人的眼中,可能觉得他会非常的low ,实际上虽然不高端,但是也没有那么 low,认证服务器通过对称或非对称的加密方式利用payload生成signature,并在header中申明签名方式,这就是 JWT 的本质实现方式。
JWT 的有点其实很明显,
- 通过验证签名的方式可以直接在资源服务器本地完成授权校验
- 在payload中可以包含用户相关信息,实现了token和用户信息的绑定
使用场景一般是用在一次性的身份验证上,千万不要想着去用 JWT 去代替 Session,虽然 JWT 可以设置失效时间,但是在有效期内,它是无法作废的。
OAuth2认证
OAuth 引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者。资源所有者同意以后,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据。
其实这个 OAuth 的核心就是向第三方应用颁发令牌,而在 Oauth2 中定义了四种获得令牌的流程,也就是通俗的四种授权方式,但是我们经常使用的也就是那么一种。
- 授权码
- 隐藏式(简化)
- 密码式
- 客户端凭证
- 授权码模式
这是在 Oauth 里面的功能算是最完整的,而且流程最严密的授权模式。
授权码模式的步骤:
- 1.用户访问客户端,后者将前者导向认证服务器
- 2.用户选择是否给予客户端授权
- 3.假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码
- 4.客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见
- 5.认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)
其实授权码模式就相当于是第三方的应用去先申请一个授权码,然后再用该授权码获取令牌。
总结下来就是四个步骤 :
1:请求授权码
2:返回授权码
3:请求令牌
4:返回令牌
我们给出一个例子,然后分析一下。
- https://2.com/oauth/authorize? //授权地址
- response_type=code& //参数1:response_type :这里表示授权的类型,此处的值固定为"code"
- client_id=CLIENT_ID& //参数2:client_id :表示客户端的ID
- redirect_uri=CALLBACK_URL& //参数3:redirect_uri :表示重定向URL
- scope=read //参数4:scope: 表示申请的权限范围
上面的地址,就相当于第一步,携带所需要的参数请求 网站2,请求获取授权码。
- https://1.com/callback?code=AUTHORIZATION_CODE //code 授权码
上面的地址就是第二步了,网站2给网站1返回授权码,
- https://2.com/oauth/token?
- client_id=CLIENT_ID& 客户端ID
- client_secret=CLIENT_SECRET& 客户端密钥
- grant_type=authorization_code& 使用的授权模式 authorization_code :授权码模式
- code=AUTHORIZATION_CODE& 授权码
- redirect_uri=CALLBACK_URL 表示重定向URL
上面的地址就到第三步了,用授权码去索要令牌的请求就发送了。
请求发送完成后,2网站收到请求之后,这时候就向 重定向URL 发送以下的 JSON 数据,
- {
- "access_token":"ACCESS_TOKEN", //访问令牌
- "token_type":"bearer",// 令牌类型
- "expires_in":2592000, // 过期时间
- "refresh_token":"REFRESH_TOKEN", // 更新令牌
- "scope":"read", // 权限范围 只读
- "uid":100101, //
- "info":{...} //
- }
这时候 访问令牌 我们就要有了,这完成所有的步骤后,我们就拿到了我们访问的令牌了,也就是我们完成了所需要的授权了。
隐藏式
其实隐藏式就是简化版的授权模式,他省略了获取 授权码 的过程,而是直接请求获取 令牌 的过程。
案例如下:
- https://2.com/oauth/authorize?
- response_type=token& 授权的类型,此处的值固定为"token"
- client_id=CLIENT_ID& 客户端ID
- redirect_uri=CALLBACK_URL& 表示重定向URL
- scope=read 权限范围 只读
上面授权类型直接就是索要令牌,
第二步也很简单,就是直接给你返回你需要的令牌
- https://1.com/callback#token=ACCESS_TOKEN
上面的 Token 就是我们需要的令牌了,
密码式
这种为什么称之为 密码式 ,是因为它在请求的时候,是用密码去换令牌,这就需要一个前提,你对这个网站有高度的信用度,如果你不信用他,他给你账号密码作用都不大,给了你也不会授权给它 Token 不是么。
案例步骤如下:
1.请求令牌
- https://oauth.2.com/token?
- grant_type=password& 授权方式:指定为密码式
- username=USERNAME& 用户名
- password=PASSWORD& 密码
- client_id=CLIENT_ID 客户端ID
2.返回令牌
- https://1.com/callback#token=ACCESS_TOKEN
这个感觉和隐藏式差距不大,一个是直接要,一个是拿着参数要。
凭证式
这个凭证式的步骤也是比较少的,实际上阿粉感觉这种方式不知道算不算是授权的方式,因为这种模式是客户端以自己的名义向"授权服务提供者"进行认证,但是既然说是,那就暂且的认定他是,
1:请求令牌
- https://oauth.2.com/token?
- grant_type=client_credentials& 授权方式:凭证式
- client_id=CLIENT_ID& 客户端ID
- client_secret=CLIENT_SECRET 客户端密钥
2.返回令牌
- https://1.com/callback#token=ACCESS_TOKEN
这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,也就是说可能出现多个用户共享同一个令牌。
为什么要比较 JWT 和Oauth2 ,因为很多不明所以的人总是会在挑选技术的时候,会把二者拿出来对比,其实上,他们两个没有可比性,因为 JWT 是用于发布接入令牌,并对发布的签名接入令牌进行验证的方法。
OAuth2是一种授权框架,授权第三方应用访问特定资源。
也就是说:
- OAuth2用在使用第三方账号登录的情况
- JWT是用在前后端分离, 需要简单的对后台API进行保护
所以你知道怎么选择了么?
文章参考
《阮一峰的网络日志》 《JWT官方文档》