#关于各种权限认证方式的总结
一,为什么我整理这么一篇文章?
这段时间准备面试看到了SSO单点登录,对其从来没有了解过,然后又忽然想起JWT之类的认证方式,感觉非常模糊,是时候重新整理一下了,还是老样子,对比系列。
二,我们为什么需要权限认证?
网站服务方需要知道你是谁吧,你需要保护自己存储在服务方的数据吧,然后你需要证明自己是谁吧,就酱,我们就需要权限认证(可以简单认为是登录)。
权限认证方式大致如下:
- HTTP Basic Auth:最古老的认证方式,通过在一个弹框里输入username+password登录,但是由于用户名和密码没有被加密,直接是半明文形式,所以现在很少使用了。
- OAuth2.0(开放授权):授权访问第三方应用程序或网站的开发标准协议。例如我们用QQ/微信/钉钉登录第三方平台。
- Cookie-Session Auth:J2EE时常用,客户端存储cookie,服务端存储session。
- Token Auth:目前普通网站常用的认证方式,只在客户端存储token信息,服务端不做存储,只计算。例如最常用的就是我们的JWT喽。
- SSO(single sign on,单点登录):用户一次登录即可访问多个相关系统或应用程序。例如登录淘宝之后登录天猫则不需要登录。
- 2FA(2 factor auth,双因素认证):在输入用户名和密码之外还需要提供另一个身份验证因素,可以是手机短信验证码、电子邮件验证码、硬件令牌(如U盘)或其他认证设备。例如现在的GitHub以及Npm等著名网站都开始使用2FA。
- MFA(多因素认证):2个或者多个验证因素认证。
三,不同方式解读
1,Http Basic Auth
没什么好说的,非常简单的登录方式。
值得一提的是,当username/password输入错误时,会返回401 unauthorized表明认证失败
当认证成功后,会多一个请求头Authorization:Basic XXXX
这里的XXXX是密文,但是仅仅是对用户名和密码进行base64加密后的密文,安全系数极低。
2,OAuth2.0
OAuth1.0存在安全问题,所以在OAuth2.0完全废止了1.0版本。
原理:
三个角色:
- 服务提供方:提供受保护身份和资源的一方,指QQ/微信
- 用户:用户将身份资料存在服务提供方
- 服务调用方:第三方网站或应用,需要在服务提供方注册身份信息,然后可以通过服务提供方拿到用户的部分信息
认证过程:
- 用户想要认证登录,服务调用方向服务提供方请求一个临时token
- 服务提供方验证服务调用方身份后,给它一个临时token
- 服务调用方获得临时token后,将用户引导到服务提供方的授权页面,并请求用户授权
- 用户输入用户名密码登录,登录成功后授权服务调用方访问服务提供方的资源
- 授权成功后,服务提供方释放网页,网页跳转回服务调用方
- 服务调用方根据临时token从服务提供方请求正式access token
- 服务提供方根据临时token以及用户授权情况授予服务调用方access token
- 客户端使用access token访问用户存在服务提供方的受保护的资源(一般是基础身份信息)
拿access token的方式:
authorization code(授权码模式)
- 用户访问服务调用方,后者将前者引导到认证服务器(服务提供方的认证服务器),用户如果提供授权,认证服务器将用户导向服务调用方事先指定的重定向URI,同时附加一个授权码
- 服务调用方收到授权码,加上早先的重定向URI,向认证服务器申请令牌,请求成功返回code授权码,一般有效时间为10分钟
GET /oauth/token?response_type=code&client_id=服务调用方id&redirect_uri=重定向页面链接
- 认证服务器核对授权码和重定向URI,确认无误后向服务调用方发送access token和refresh token。
POST /oauth/token?response_type=authorization_code&code=XXXXX&redirect_uri=xxxx
implicit(简化模式)
一般用于移动应用以及Web App。access token直接从认证服务器返回(只有前端渠道),不支持refresh token。最容易受安全攻击。
resource owner password credentials
一般用于受信任客户端应用,例如组织内部应用。使用用户名密码作为授权方式从认证服务器上获取access token,一般不支持refresh token
client credential
适用客户端调用服务API型应用(比如百度API store,不同项目之间的微服务相互调用)。只有后端渠道,适用客户凭证获取access token。
3,Cookie-Session Auth
原理:
一次认证在服务端创建一个session对象,同时在客户端创建一个Cookie对象。
之后客户端请求其他内容的时候带上cookie对象来与服务器端的session对象匹配来实现状态管理。
默认我们关闭浏览器的时候,cookie会被删除;但是可以通过修改cookie的expire time使cookie在一定时间内有效。
流程:
- 客户端使用用户名密码登录
- 服务端返回200 OK 并且
Set-Cookie:sessonId=xxx
- 之后客户端请求服务端时会自动带上Cookie对象,
Cookie:sessionId=xxx
- 服务端查找session对象列表找到匹配的session,并返回客户端想要的数据
缺点:
- session增多会增加服务器开销:每个用户认证之后服务端会做一次记录,但是这个session一般都存储在内存里,用户认证数量增多,服务端的开销会明显增大。
- 分布式或多服务器环境中适应性不好:因为session存储在内存里,意味着用户下次请求还必须请求这台服务器,不利于负载均衡调节。
- 容易遭受CSRF攻击:cookie被截获之后,用户很容易受到跨站请求伪造的攻击
解决缺点:
- session数据持久化,写入数据库或其他的持久层。优点是架构清晰,缺点是工程量较大。
- 不适用session数据了,所有数据存储在客户端,每次请求都发回服务器。
4,Token Auth
原理:
服务端不需要保留用户的认证信息或者会话信息,全部存储在客户端。
流程:
- 用户使用用户名密码登录
- 服务器验证用户信息,发送用户一个token
- 客户端存储token,并在之后的所有请求都附加这个token
- 服务端每次都验证token,并返回数据
JWT(JSON Web Token):重点!
特性:
- 简洁性:可以通过url、post参数或者HTTP header发送,因为数据量小,传输速度也很快
- 自包含性:负载中包含了所有用户需要的信息,避免了多次查询数据库
- 一般放在Authorization header,用Bearer schema。长这样
Authorization: Bearer <token>
;也可以放到Cookie里自动发送,但是这样不能跨域。
结构:header.payload.signature
header:token的类型(例如JWT)+签名算法名称(例如HMAC SHA256)
{ "alg":"HS256", "typ":"JWT" }
然后通过用Base64 URL算法对这个JSON编码就得到JWT的第一部分
payload:包含声明(要求)的一部分。声明是关于实体和其他数据的声明。声明有三种类型:
- registered claims:官方预定义的声明,不是强制的,但是推荐。比如:iss (issuer,签发人), exp (expiration time,过期时间), sub (subject,主题), aud (audience,受众),nbf(Not Before,生效时间),iat(Issued At,签发时间),jti(JWT ID,编号)。
- public claims:可以随意定义的声明(字段)
- private claims:用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。
{ "sub":"1234567890",//registered claims "name":"alex lee",//public claims "admin":true }
对payload的json格式进行Base64 URL算法编码就能得到JWT的第二部分,注意不要防止敏感信息,除非已经预先经过加密。
Signature:使用header中指定的签名算法对Base64编码过的header和payload以及一个秘钥进行签名加密。
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
签名的作用:验证消息在传递过程中是否被更改,并且对于使用私钥签名的token,还可以验证JWT发送方是否为它所称的发送方。
Base64URL不等于Base64:由于JWT作为一个令牌可能会被放到url里,Base64编码后可能有这三个字符+、/、=,它们在URL里有特殊含义,所以要被替换掉:=被省略,+替换为-,/替换为_。
缺点:服务器不保存session状态,导致无法在使用过程中废止某个token或者更改token的权限。也就是说一旦JWT签发了,在到期前一直有效,除非服务器部署了其他的逻辑。
5,SSO
单点登录一般用于一个公司内部,搭建一个公共的认证中心。公司旗下所有产品的登录都可以在认证中心(passport)里完成。一个产品登陆后,其他产品都可以自动登录。
流程:
- 用户第一次访问A系统
- 由于没有登录,重定向到认证中心,并带上回调地址
- 用户在认证中心输入账号密码,提交登录
- 认证中心验证账号密码有效,重定向回到A系统登录界面,并且带上授权码ticket,并将认证中心的登录态写入Cookie
- A系统服务器拿着ticket向认证中心确认授权码ticket是否真实有效
- 验证成功后A系统服务器将登录信息写入Cookie(此时客户端存有两个cookie,分别是A系统以及认证中心的登录态,分别存储在两个网址!!)
之后继续访问A系统其他页面,由于客户端已经有A系统的登录态cookie,所以服务器直接认证成功。
如果此时访问B系统(此时已经有认证中心的Cookie存在),此时会直接从4执行,下发ticket给B系统,并继续后面的步骤。
如果你想单点退出怎么办?(一个产品中退出了登录,怎么让其他产品也都退出登录)
- 清空C系统的登录态Cookie
- 请求认证中心中的退出api
- 认证中心遍历下发过ticket中的所有产品,并调用对应的退出api,完成退出
实现方式:
- 同域名下的单点登录:
- 背景:cookie的domain属性设置为当前域的父域,并且父域的cookie会被子域共享。path属性默认为web应用的上下文路径。
- 原理:利用cookie的这个特点,我们将cookie的domain属性设置为父域的域名,同时将cookie的path属性设置为根路径,将sessionId或Token保存到父域中。这样所有子域应用都可以访问到这个cookie。
- 缺点:需要应用系统的域名都建立在一个共同的主域名下。
- 不同域名下的单点登录:
- 由于不同域cookie是不共享的,我们需要部署一个认证中心,用于专门处理登录请求的web服务。
- 纯前端实现:前端可以将拿到的sessionId/token写入当前域名的localstorage,同时写入其他域下的localstorage。每次请求时主动从localStorage中读取token并携带。
6,2FA
一般来说我们有三种类型的证据(因素,factor)来证明一个人的身份:
- 秘密信息:比如密码
- 个人物品:比如身份证、钥匙
- 生理特征:比如指纹、相貌、虹膜等
因素越多,证明力越强,2FA就是说需要两种因素来证明自己。
方式:
常见的:输入密码+短信验证码
然而,短信也不安全,容易被拦截和伪造,SIM卡也能被克隆。
可靠的:TOTP(Time-based One-time Password):基于时间的一次性密码
步骤:
- 用户开启双因素认证后,服务器生成一个密钥。
- 服务器提示用户扫描二维码(或者其他的方式,比如密钥),把密钥保存到手机或者其他地方。
- 用户登录时,手机客户端使用这个密钥和当前时间戳生成一个哈希,有效期默认是30秒。用户在有效期内把这个哈希提交给服务器。
- 服务器也使用密钥和当前时间戳生成一个哈希,跟用户提交的哈希比对。一致则允许登录。
算法:
怎么保证服务器和手机客户端生成同一个哈希呢?
//TC是时间计数器,unixtime(now)是当前unix时间戳,unixtime(0) //是约定的起始时间点的时间戳,默认是0,即1970.1.1 //TS是有效的时间长度,默认是30秒 TC = floor((unixtime(now) − unixtime(T0)) / TS) //也就是说30秒内TC是不变的(当前前提是服务器和手机的时间必须同步) //HASH是约定的哈希函数,默认是SHA-1 TOTP = HASH(SecretKey,TC)
优缺点:
- 优点:只要手机还在,账号就是安全的
- 缺点:登录多了一步,费时且麻烦,用户会感觉不耐烦。当然还有一个问题是手机万一丢了,想要恢复登录就很难。
#参考资料
面试官:什么是单点登录?如何实现? · Issue #91 · febobo/web-interview (github.com)
浅谈认证的发展历史及方向 (tangxusc.github.io)
几种常用的Web安全认证方式_最安全的平台认证方式-CSDN博客
认识JWT - 废物大师兄 - 博客园 (cnblogs.com)
JSON Web Token 入门教程 - 阮一峰的网络日志 (ruanyifeng.com)
cookie - 4种常规的登录认证方式 - 个人文章 - SegmentFault 思否
用户登录和权限认证之 —— JWT - 掘金 (juejin.cn)
面试官:什么是单点登录?如何实现? · Issue #91 · febobo/web-interview (github.com)