前言
作为前端开发,在面试或者平时的开发中我们都应该全方位的了解多种登录授权机制,特别是OAuth 2.0、JWT、单点登录的这种概念和流程我们应该信手拈来。以下就是我我对这些开发过程中可能会遇到的登录相关的概念的总结,参考了很多网上的资料,是对于登录流程非常详尽的总结。
OAuth 2.0
授权机制
OAuth是一种授权机制。数据所有者告诉系统,同意授权第三方应用进入系统获取数据。系统产生一个短期的进入令牌token,用来代替密码供第三方使用。
这种授权机制目前是比较通用的,比如现在热门的微信小程序,就是使用的这种授权机制。
例子说明
token VS password
令牌token | 密码password | |
---|---|---|
时效 | 短期,到期自动失效 | 长期生效,用户修改才失效 |
可撤销性 | 可被数据所有者撤销 | 一般不允许被他人撤销 |
权限范围 | 有权限范围scope | 完整权限 |
OAuth2.0 优点
- 可以让第三方应用获取权限
- 随时可控,不会危机系统安全
注意:只要知道了令牌就能进入系统,一般不会再次确认身份。
token令牌必须保密,泄露令牌与泄露密码的后果一样,所以令牌的有效期一般都设置的很短。
四种授权方式
OAuth 2.0 对于如何颁发令牌的细节,规定的非常详细。具体来说,一共分成四种授权类型,也就是四种颁发令牌的方式,适用于不同的互联网场景。
- 授权码(authorization-code)
- 隐藏式(implicit)
- 密码式(password):
- 客户端凭证(client credentials)
不管哪一种授权方式,第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。
一、授权码(最常用)
指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
最常用,安全性最高的流程。适用于有后端的web应用。由前端传送授权码,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后分离,可以避免令牌泄露。
流程
- A域名的网站向B域名的网站请求授权码
bash
https://b.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
携带参数 | 含义 |
---|---|
response_type | 表示要求返回授权码 |
client_id | 让 B 知道是谁在请求 |
redirect_uri | B 接受或拒绝请求后的跳转网址 |
scope | 要求的授权范围(这里是只读) |
- 用户跳转到B网站之后,要求用户登录,询问是否统一给A网站授权。用户统一之后,B网站将会重定向到redirect_uri参数指定的网站并带回授权码参数。
ini
https://a.com/callback?code=AUTHORIZATION_CODE
- A 网站拿到授权码以后,就可以通过后端向 B 网站请求令牌。
ini
https://b.com/oauth/token?client_id=CLIENT_ID&client_secr et=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL
client_id | 表明身份-客户端id |
---|---|
client_secret | 表明身份-保密参数,因此只能在后端发送请求 |
grant_type | 授权方式,默认为AUTHORIZATION_CODE |
code | 授权码 |
redirect_uri | 令牌颁布后的回调网址 |
- B网站收到请求之后颁布令牌:向redirect_uri指定的网址发送JSON数据,A网站由此在后端可以获取到access_token(令牌)
json
{
"access_token":"ACCESS_TOKEN",
"token_type":"bearer",
"expires_in":2592000,
"refresh_token":"REFRESH_TOKEN",
"scope":"read",
"uid":100101,
"info":{...}
}
流程图
二、隐藏式(纯前端)
对于纯前端应用,允许直接向前端颁发令牌 。这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)。
流程
- A网站提供一个链接,要求用户跳转B网站授权用户数据给A网站
bash
https://b.com/oauth/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
response_type参数为token,表示要求直接返回令牌
- 用户在B网站登录并且同意授权给A网站,B网站就会跳回redirect_ui并且令牌会通过url传递
bash
https://a.com/callback#token=ACCESS_TOKEN
注意: 令牌的位置是URL锚点(#携带参数),不是查询字符串。是因为OAuth2.0允许跳转网址是HTTP协议,存在"中间人攻击"风险,而浏览器跳转时,锚点不会发送到浏览器,减少了令牌泄露的风险。
流程图
缺点
- 令牌是直接传递给前端的,有一定的安全风险,只能用于一些安全性不高的场景。
- 令牌的有效期非常短,通常是会话内有效
三、密码式(高度信任应用)
用户直接将用户名和密码直接告诉该应用,该应用直接试用密码申请令牌。
流程
- A网站要求用户提供B网站的用户名和密码。拿到以后,A直接向B请求令牌
arduino
https://oauth.b.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID
// grant_type 授权方式 password-表示密码式
// username password 表示B网站的用户名和密码
- B验证用户身份无误后,直接给出令牌(无需跳转),令牌会作为HTTP回应在JSON数据里被拿到
适用性
需要给出用户名和密码,风险很大,只适用于其他授权方式无法采用的情况,并且是用户高度信任的应用。
四、凭证式(命令行应用)
适用于没有前端的命令行应用,也就是说在命令行下请求令牌。
流程
- A应用在命令行向B发出请求
arduino
https://oauth.b.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
// grant_type 授权方式 client_credentials-表示凭证式
// client_id和client_secret用来让B确认A的身份
- B网站通过验证之后,直接返回令牌
这种方式给出的令牌,是针对第三方应用的,不是针对客户的,所以有可能多个用户共享一个令牌。
令牌的使用
A网站拿到令牌之后,就可以向B网站的API请求数据了。
每个发到API的请求,都必须带有令牌。具体做法是在请求头信息上,带上Authorization字段,令牌就存放在这个字段里面。
令牌的更新
如果令牌的有效期到了,重走上面的流程体验性不好,所以OAuth2.0允许用户自动更新令牌。
更新方法
B网站颁布令牌的时候,一次性颁布两个令牌:
- 一个用于获取数据
- 一个用于获取新的令牌 (refresh token字段)
令牌到期前,用户通过refresh token发送一个请求,去更新令牌。
ini
https://b.com/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN
Github授权尝试
JWT(JSON Web Token)
www.ruanyifeng.com/blog/2018/0...
JWT原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户。以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
JWT是什么
JWT是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。
JWT的组成
-
Header(头部)
- Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。{"alg": "HS256","typ": "JWT"}
- JSON 对象使用 Base64URL 算法转成字符串
-
Payload(负载)
- Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据
- JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分
- 也使用了Base64URL 算法
- 官方规定7个字段
-
iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience)受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
-
Signature(签名)
- 部分是对前两部分的签名,防止数据篡改。
JWT的使用
- cookies (不能支持跨域)
- 存在localStorage中,每次通过请求头中的Authorization字段发送(常用)
- JWT 就放在 POST 请求的数据体
JWT的特点
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点 是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
单点登录SSO
简单来说,单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录
单系统登录流程
- 用户登录时,验证用户的账户和密码
- 生成一个Token保存在数据库中,将Token写到Cookie中
- 将用户数据保存在Session中
- 请求时都会带上Cookie,检查有没有登录,如果已经登录则放行
- session在服务端维护
多系统登录的问题与解决
单系统登录功能主要是用Session保存用户信息来实现的,系统A的Session和系统B的Session是不共享的。
解决方案
将登录功能做成独立子系统
将登陆功能抽取为一个系统(SSO),其他系统请求SSO进行登录
同域下的单点登录
一个企业一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com和app2.a.com。我们要做单点登录(SSO),需要一个登录系统,叫做:sso.a.com。我们只要在sso.a.com登录,app1.a.com和app2.a.com就也登录了。
技术点
一、将Cookie的域设置为顶域,即.a.com
sso登录以后,可以将Cookie的域设置为顶域,即.a.com ,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时, 只能设置顶域和自己的域 ,不能设置其他的域。比如:我们不能在自己的系统中给baidu.com的域设置Cookie。
Cookie是不能跨域的,我们Cookie的domain属性是sso.a.com,在给app1.a.com和app2.a.com发送请求是带不上的。
二、 共享Session
我们在sso系统登录了,这时再访问app1,Cookie也带到了app1的服务端(Server),app1的服务端怎么找到这个Cookie对应的Session呢?这里就要把3个系统的Session共享,如图所示。共享Session的解决方案有很多,例如:Spring-Session。
sso、app1和app2是不同的应用,它们的session存在自己的应用内,是不共享的。
不同域下的单点登录:CAS流程
同域下的单点登录是巧用了Cookie顶域的特性。然而,不同域之间Cookie是不共享的。这时我们就需要用到CAS流程--单点登录的标准流程。
提示: 以下CAS Server我们统一叫做SSO系统
SSO未登录流程
- 用户访问app系统,app系统是需要登录的,但用户现在没有登录。
- 跳转到SSO登录系统 。 SSO系统也没有登录,弹出用户登录页。
- 用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。
- SSO系统登录完成后会生成一个ST(Service Ticket) ,然后跳转到app系统,同时将ST作为参数传递给app系统。
- app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
- 验证通过后,app系统将登录状态写入session并设置app域下的Cookie。
SSO已登录流程
- 用户访问app2系统,app2系统没有登录,跳转到SSO。
- 由于SSO已经登录了,不需要重新登录认证。
- SSO生成ST,浏览器跳转到app2系统,并将ST作为参数传递给app2。
- app2拿到ST,后台访问SSO,验证ST是否有效。
- 验证成功后,app2将登录状态写入session,并在app2域下写入Cookie。
ST验证的必要性
单点登录,资源都在各个业务系统这边,不在SSO那一方 。 用户在给SSO服务器提供了用户名密码后,作为业务系统并不知道这件事。 SSO随便给业务系统一个ST,那么业务系统是不能确定这个ST是用户伪造的,还是真的有效,所以要拿着这个ST去SSO服务器再问一下,这个用户给我的ST是否有效,是有效的我才能让这个用户访问。
后记
以上就是我对登录知识点的总结,囊括了OAuth 2.0、JWT、单点登录等内容,中间扩展了一些其他的知识点,相信我们在平时的工作中学习中也会遇到相关的问题,如果能够帮助你,那么这篇文章就是成功的,感谢阅读。