授权机制,当我们说到这个问题的时候,大家对它的第一印象是在哪个地方呢?是不是曾经某培训机构教授的 SSO 单点登录的,是的没错,而这种 SSO 的单点登录在当年的培训机构中,使用的就是 Session 共享,也就是用 Redis 做中间模拟 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?

1
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 ,AuthorizationInformation 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 的规范中,他告诉我们的是,建议但不强制使用,也就是说,你可以根据自身的应用去选择使用,比如官网给出的,他就没有写全面,就使用了三个:

1
2
3
4
5
{
  "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:返回令牌

我们给出一个例子,然后分析一下。

1
2
3
4
5
6
7

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,请求获取授权码。

1
https://1.com/callback?code=AUTHORIZATION_CODE //code 授权码

上面的地址就是第二步了,网站2给网站1返回授权码,

1
2
3
4
5
6
7
8

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 数据,

1
2
3
4
5
6
7
8
9
{    
  "access_token":"ACCESS_TOKEN", //访问令牌
  "token_type":"bearer",// 令牌类型
  "expires_in":2592000, // 过期时间
  "refresh_token":"REFRESH_TOKEN", // 更新令牌
  "scope":"read", // 权限范围 只读
  "uid":100101, //
  "info":{...} //
}

这时候 访问令牌 我们就要有了,这完成所有的步骤后,我们就拿到了我们访问的令牌了,也就是我们完成了所需要的授权了。

隐藏式

其实隐藏式就是简化版的授权模式,他省略了获取 授权码 的过程,而是直接请求获取 令牌 的过程。

案例如下:

1
2
3
4
5
6
7

https://2.com/oauth/authorize?
  response_type=token& 授权的类型,此处的值固定为"token"
  client_id=CLIENT_ID&  客户端ID
  redirect_uri=CALLBACK_URL& 表示重定向URL
  scope=read  权限范围 只读

上面授权类型直接就是索要令牌,

第二步也很简单,就是直接给你返回你需要的令牌

1
1
1
https://1.com/callback#token=ACCESS_TOKEN

上面的 Token 就是我们需要的令牌了,

密码式

这种为什么称之为 密码式 ,是因为它在请求的时候,是用密码去换令牌,这就需要一个前提,你对这个网站有高度的信用度,如果你不信用他,他给你账号密码作用都不大,给了你也不会授权给它 Token 不是么。

案例步骤如下:

1.请求令牌

1
2
3
4
5
6
7

https://oauth.2.com/token?
  grant_type=password& 授权方式:指定为密码式
  username=USERNAME&  用户名
  password=PASSWORD&  密码
  client_id=CLIENT_ID  客户端ID
  

2.返回令牌

1
1
1
https://1.com/callback#token=ACCESS_TOKEN

这个感觉和隐藏式差距不大,一个是直接要,一个是拿着参数要。

凭证式

这个凭证式的步骤也是比较少的,实际上阿粉感觉这种方式不知道算不算是授权的方式,因为这种模式是客户端以自己的名义向”授权服务提供者”进行认证,但是既然说是,那就暂且的认定他是,

1:请求令牌

1
2
3
4
https://oauth.2.com/token?
  grant_type=client_credentials&  授权方式:凭证式
  client_id=CLIENT_ID&   客户端ID
  client_secret=CLIENT_SECRET 客户端密钥

2.返回令牌

1
1
1
https://1.com/callback#token=ACCESS_TOKEN

这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,也就是说可能出现多个用户共享同一个令牌。

为什么要比较 JWT 和Oauth2 ,因为很多不明所以的人总是会在挑选技术的时候,会把二者拿出来对比,其实上,他们两个没有可比性,因为 JWT 是用于发布接入令牌,并对发布的签名接入令牌进行验证的方法。

OAuth2是一种授权框架,授权第三方应用访问特定资源。

也就是说:

  • OAuth2用在使用第三方账号登录的情况

  • JWT是用在前后端分离, 需要简单的对后台API进行保护

所以你知道怎么选择了么?

文章参考

《阮一峰的网络日志》 《JWT官方文档》

Java Geek Tech wechat
欢迎订阅 Java 极客技术,这里分享关于 Java 的一切。