0. 背景
计算机本身无法判断坐在显示器前的使用者的身份,也无法确认网络的另一端的是谁。为了明确是谁在访问服务器,必须让客户端自报家门。
通常核对一些登录者本人的信息:
- 密码:只有本人知道的字符串信息
- 动态令牌:仅限本人持有的设备内显示的一次性密码
- 数字证书:仅限本人(终端)持有的信息
- 生物认证:指纹和虹膜等本人生理信息
- 身份证号、手机号等:仅限本人持有的信息
1. Basic 认证
Basic 身份认证,是 HTTP 1.0 中引入的认证方案之一。虽然方案比较古老,实现简单,同时存在安全缺陷。通过核对用户名、密码的方式,来实现用户身份的验证。
核心概念
- Userid
- Password
- Realm:认证保护域,其实就是指当前认证的保护范围
关于realm:同一个 server,访问受限的资源多种多样,比如资金信息、机密文档等。可以针对不同的资源定义不同的 realm,并且只允许特定的用户访问。跟Linux下的账户、分组体系很像。
认证流程
在进行 BASIC 认证的过程中,HTTP 的请求头字段会包含 Authorization 字段,Authorization: Basic <用户凭证>
,该<用户凭证>是 userid 和 password 的组合而成的 Base64 编码。
GET /securefiles/ HTTP/1.1
Host: www.example.com
Authorization: Basic aHR0cHdhdGNoOmY=
BASIC 认证流程图:
1. 用户在浏览器中访问了受限制的网页资源,但是没有提供用户的身份信息
2. 服务端接收到请求后返回 401 应答码(Unauthorized,未被授权的)要求进行身份验证,并附带提供了一个认证域(Access Authentication)WWW-Authenticate
说明如何进行验证的方法,例如 WWW-Authenticate: Basic realm="Secure Area"
,Basic
就是验证的模式,而 realm="Secure Area"
则为保护域(告知认证的范围),用于与其他请求 URI 作区别
3. 浏览器收到应答后,会显示该认证域给用户并提示输入用户名和密码,此时用户可以选择录入信息后确定或取消操作
4. 用户输入了用户名和密码后,浏览器会在原请求头新增认证消息字段 Authorization
并重新发送请求,过程如下:
- 拼接
Basic
,设置为 Authorization
字段,假设用户名为 admin
,密码为 password
,则拼接后为 admin:password
,使用 Base64 编码后为 YWRtaW46cGFzc3dvcmQ=
,那么最终在 HTTP 头部里会是这样:
Authorization: Basic YWRtaW46cGFzc3dvcmQ=
5. 服务端接收了该认证后并返回了用户请求的网页资源。如果用户凭证非法或无效,服务器可能会再次返回 401 应答码,客户端就需要再次输入用户名和密码
缺陷
Basic 认证的安全缺陷比较明显,它通过明文传输用户的密码,在 http 等非加密通信的线路上进行 Basic 认证的过程中如果被人监听,被盗的可能性极高。
除了安全缺陷,Basic 认证还存在无法吊销认证的情况。
2. Digest 认证
“摘要”式认证( Digest authentication)是一个简单的认证机制,最初是为HTTP协议开发的,因而也常叫做HTTP摘要,在 RFC2671 中描述。其身份验证机制很简单,它在密码发出前,先对其应用哈希函数,这相对于 HTTP Basic 认证发送明文而言,更安全。
Digest 认证同样使用质询/响应的方式(challenge/response),即一开始一方会先发送需要认证的请求给另一方,接着使用从另一方接收到的质询码计算生成响应码,最后将响应码返回给对方进行认证的方式。
参数介绍
- username:用户名
- realm:同上
- nonce:服务器发给客户端的随机的字符串,服务器每次产生一个摘要质询时,这个参数都是不一样的。nonce 通常是由一些数据通过 md5 运算构造的。这样的数据通常包括时间标识和服务器的机密短语。这确保每个 nonce 都有一个有限的生命期(也就是过了一些时间后会失效,并且以后再也不会使用),而且是独一无二的(即任何其它的服务器都不能产生一个相同的 nonce)。
nonce = BASE64(time-stamp MD5(time-stamp : ETag : private-key))
- qop:质量保护,值一般为auth或auth-int,影响摘要算法。“auth”表示只进行身份查验, “auth-int”表示进行查验外,还有一些完整性保护。
- opaque:服务端指定的值,客户端需要原值返回。
- algorithm:摘要算法,值为 MD5 或 MD5-sess,默认为 MD5
- uri:客户端想要访问的 uri,考虑到经代理转发后 Request-URI 的值可能会修改,所以事先复制一份
- response:客户端根据算法算出的摘要值。
认证流程
1. 浏览器发送http报文请求一个受保护的资源。
2. 服务端的 web 容器将 http 响应报文的响应码设为 401,响应头部比 Basic 模式复杂:
WWW-Authenticate: Digest realm="myTomcat", qop="auth", nonce="xxxxxxxxxxx", opaque="xxxxxxxx"
3. 浏览器弹出对话框让用户输入用户名和密码,浏览器对用户名、密码、nonce值、HTTP请求方法、被请求资源URI等组合后进行MD5运算,把计算得到的摘要信息发送给服务端。请求头部类似如下:
Authorization: Digest username="xxxxx", realm="myTomcat", qop="auth", nonce="xxxxx", uri="xxxx", cnonce="xxxxxx", nc=00000001, response="xxxxxxxxx", opaque="xxxxxxxxx"
其中 Authorization 内必须包含 username、realm、nonce、uri 和 responce。
4. 服务端 web 容器获取 HTTP 报文头部相关认证信息,从中获取到 username,根据 username 获取对应的密码,同样对用户名、密码、nonce 值、HTTP 请求方法、被请求资源 uri 等组合进行 MD5 运算,计算结果和 response 进行比较,如果匹配则认证成功并返回相关资源,否则再执行 2,重新进行认证。
5. 以后每次访问都要带上认证头部。
计算方法
1. 基本算法:
RFC 2069定义,由服务器生成随机数来维护安全性的摘要认证架构。
HA1 = MD5(A1) = MD5(username : realm : password)
HA2 = MD5(A2) = MD5(method : uri)
response = MD5(HA1 : nonce : HA2)
2. 安全增强算法:
RFC 2617 引入一系列安全增强的选项;“保护质量”(qop)、随机数计数器由客户端增加、以及客户生成的随机数。这些增强为了防止如选择明文攻击的密码分析。
如果 qop 的值为 "auth",那么:
HA1 = MD5(A1) = MD5(username : realm : password)
HA2 = MD5(A2) = MD5(method : uri)
response = MD5(HA1 : nonce : nc : cnonce : HA2)
如果 qop 的值为 "auth-int",那么:
HA1 = MD5(A1) = MD5(username : realm : password)
HA2 = MD5(A2) = MD5(method : uri : MD5(entityBody))
response = MD5(HA1 : nonce : nc : cnonce : HA2)
如果没有指定 qop,将以基本算法计算。
缺陷
- RFC 2617 中的许多安全选项都是可选的。如果服务器没有指定保护质量(qop),客户端将以降低安全性的早期的 RFC 2069 的模式操作。
- Digest 认证容易受到中间人攻击。举例而言,一个中间人攻击者可以告知客户端使用 Basic 认证或早期的 RFC 2069 Digest 认证模式。进一步而言,Digest 认证没有提供任何机制帮助客户端验证服务器的身份。
- 一些服务器要求密码以可逆加密算法存储。但是,仅存储用户名、realm、和密码的摘要是可能的。
- 它阻止了使用强密码哈希函数保存密码(因为无论是密码、或者用户名、realm、密码的摘要都要求是可恢复的)。
3. SSL 客户端认证
SSL 客户端认证是借由 HTTPS 的客户端证书完成认证的方式。凭借客户端证书认证,服务器可确认访问是否来自已经登录的客户端。
认证步骤
- 接收到需要认证资源的请求,服务器会发送 Certificate Request 报文,要求客户端提供客户端证书。
- 用户选择将发送的客户端证书后,客户端会把客户端证书信息以Client Certificate 报文方式发送给服务器。
- 服务器验证客户端证书验证通过后方可领取证书内客户端的公开密钥,然后开始 HTTPS 加密通信。
双因素认证
多数情况下,SSL 客户端认证不会仅依靠证书完成认证,一般会和基于表单认证组合成一种双因素认证方式使用。SSL 客户端证书用来认证客户端计算机,密码用来确定用户本人。
4. 基于表单认证
由于使用的便利性和安全性问题,HTTP 协议标准提供的 Basic 认证和 Digest 认证几乎不怎么使用。另外,SSL 客户端认证虽然具有高度的安全等级,但因为导入及维持费用等问题,还尚未普及。所以多数情况下基于表单认证。
Session-Cookie 认证
流程
- 客户端把用户 ID 和密码等登录信息放入报文的实体部分,通常是以 POST 方法把请求发送给服务器。而这时,会使用 HTTPS 通信来进行 HTML 表单画面的显示和用户输入数据的发送。
- 服务器会发放用以识别用户的 Session ID。通过验证从客户端发送过来的登录信息进行身份认证,然后把用户的认证状态和 Session ID 绑定后记录在服务器端。向客户端返回响应时,会在首部字段 Set-Cookie 内写入 Session ID。(为减轻跨站脚本攻击造成的损失,建议事先在 Cookie 内加上 httponly 属性。)
- 客户端接收到从服务器发来的 Session ID 后,会将其作为 Cookie 保存在本地。下次向服务器发送请求时,浏览器会自动发送 Cookie,所以 Session ID 也随之发送到服务器。服务器可通过验证收到的 Session ID 识别用户和其认证状态。
优点
- Cookie 简单易用,在不受用户干预或过期处理的情况下,Cookie 通常是客户端上持续时间最长的数据保留形式
- Session 数据存储在服务端,相较于 JWT 方便进行管理,也就是当用户登录和主动注销,只需要添加删除对应的 Session 就可以了,方便管理
缺点
- 非常不安全,Cookie 将数据暴露在浏览器中,增加了数据被盗的风险(容易被 CSRF 等攻击)
- Session 存储在服务端,增大了服务端的开销,用户量大的时候会大大降低服务器性能
- 用户认证后,服务端做认证记录,如果认证的记录被保存在内存中,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权资源,这样在分布式的应用上,相应的限制了负载均衡的能力,也意味着限制了应用的扩展能力
Token 认证
流程
- 用户输入登录信息并请求登录
- 服务端收到请求,验证用户输入的登录信息
- 验证成功后,服务端会签发一个 Token(通常包含用户基础信息、权限范围和有效时间等),并把这个 Token 返回给客户端
- 客户端收到 Token 后需要把它存储起来,比如放在 localStorage 或 sessionStorage 里(一般不放 Cookie 因为可能会有跨域问题,以及安全性问题)
- 后续客户端每次向服务端请求资源的时候,将 Token 附带于 HTTP 请求头 Authorization 字段中发送请求
- 服务端收到请求后,去校验客户端请求中 Token,如果验证成功,就向客户端返回请求的数据,否则拒绝返还
优点
- 服务端无状态:Token 机制在服务端不需要存储会话(Session)信息,因为 Token 自身包含了其所标识用户的相关信息,这有利于在多个服务间共享用户状态
- 支持跨域跨程序调用,因为 Cookie 是不允许跨域访问的,而 Token 则不存在这个问题
- 有效避免 CSRF 攻击(因为不需要 Cookie),但是会存在 XSS 攻击中被盗的风险,但是可选择 Token 存储在标记为
httpOnly
的 Cookie 中,能够有效避免浏览器中的 JS 脚本对 Cookie 的修改
缺点
- 正常情况下比 Session ID 更大,消耗更多流量,挤占更多宽带
- 相比较于 Session-Cookie 认证来说,Token 需要服务端花费更多时间和性能来对 Token 进行解密验证,其实 Token 相较于 Session—Cookie 来说就是一个时间换空间的方案
JWT 认证
JSON Web Token
原理
服务器认证后,生成一个 JSON 对象,返回给用户。
此后,用户与服务端通信的时候,都要返回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
数据格式
JWT 是 JSON 格式的被加密了的字符串,中间用(.)分隔成三个部分:
- Header 头部
- Payload 负载
- Signature 签名
其中 Header 和 Payload 加密的算法是 Base64URL。
Base64URL 算法跟 Base64 算法基本类似,但有一些小的不同。
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+
、/
和=
,在 URL 里面有特殊含义,所以要被替换掉:=
被省略、+
替换成-
,/
替换成_
。这就是 Base64URL 算法。
Header
Header 部分是一个JSON 对象,描述 JWT 的元数据,通常是下面的样子:
{
"alg": "HS256",
"typ": "JWT"
}
其中,alg
属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ
属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT
。
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
除了官方字段,还可以在这个部分定义私有字段:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(Header) + '.' + base64UrlEncode(Payload), secret);
特点
-
JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
-
JWT 不加密的情况下,不能将秘密数据写入 JWT。
-
JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
-
JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
-
JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
-
为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
单点登录
单点登录(Single Sign-on)又称 SSO,是指在多系统应用群中登录单个系统,便可在其他所有系统中得到授权而无需再次登录。
同域 SSO
当存在两个相同域名下的系统 A a.abc.com
和系统 B b.abc.com
时,以下为他们实现 SSO 的步骤:
- 用户访问某个子系统时(例如
a.abc.com
),如果没有登录,则跳转至 SSO 认证中心提供的登录页面进行登录 - 登录认证后,服务端把登录用户的信息存储于 Session 中,并为用户生成对应的会话身份凭证附加在响应头的
Set-Cookie
字段中,随着请求返回写入浏览器中,并回跳到设定的子系统链接中 - 下次发送请求时,当用户访问同域名的系统 B 时,由于 A 和 B 在相同域名下,也是
abc.com
,浏览器会自动带上之前的 Cookie。此时服务端就可以通过该 Cookie 来验证登录状态了。
这实际上使用的就是 Session-Cookie 认证的登录方式。
跨域 SSO
目前大多数企业的系统都是部署在不同域名下的,显然同域 SSO 并不能满足。
单点登录的标准流程要使用到 CAS。
CAS(Central Authentication Service)中央授权服务,本身是一个开源协议,分为 1.0 版本和 2.0 版本。1.0 称为基础模式,2.0 称为代理模式,适用于存在非 Web 应用之间的单点登录。
CAS 重要概念:
- Server:CAS 服务器,即 SSO 服务器。
- Service:Web 应用服务器。
- TGT(Ticket Grangting Ticket):TGT 是 CAS 为用户签发的 登录票据,拥有了 TGT,用户就可以证明自己在 CAS 成功登录过。TGT 封装了 Cookie 值以及此 Cookie 值对应的用户信息。当 HTTP 请求到来时,CAS 以此 Cookie 值(TGC)为
key
查询缓存中是否有 TGT,如果有,则表示用户已登录过。
- TGC(Ticket Granting Cookie):CAS Service 生成 TGC 放入自己的 Session 中,而 TGC 就是这个 Session 的唯一标识(SessionID),以 Cookie 形式放到浏览器端,是 CAS Service 用来明确用户身份的凭证。TGC 和 TGT 的关系相当于 key: value => TGC => TGT。
- ST(Service Ticket):ST 是 CAS 为用户签发的访问某个 Service 的票据。用户访问 Service 时,Service 发现用户没有 ST,则要求用户去 CAS 获取 ST。用户向 CAS 发出 ST 的请求,CAS 发现用户有 TGT,则签发一个 ST,返回给用户。用户拿着 ST 去访问 Service,Service 拿 ST 去 CAS 验证,验证通过后,允许用户访问资源。
CAS 流程:
详细流程:
- 用户访问 app1 系统,app1 系统是需要登录的,但用户现在没有登录,app1 的 service 会返回 302 重定向到 CAS 服务器。
- 跳转到 CAS server,即 SSO 登录系统,由于请求没有携带 TGC,所以 SSO 系统也判定为没有登录,弹出用户登录页。
- 用户填写用户名、密码,SSO 系统进行认证后,将登录状态写入 SSO 的 session,创建全局会话,产生了 TGC:TGT 键值对,同时生成一个 ST。
- SSO 将 TGC 放在 Set-Cookie 中,将 ST 携带在 URL query 中,然后浏览器跳转到 app1 系统,同时将 ST 作为参数传递给 app1 系统。
- app1 系统拿到 ST 后,从后台向 SSO 发送请求,验证 ST 是否有效。
- 验证通过后,app1 系统将登录状态写入 session 并设置 app1 域下的 Cookie。
至此,跨域单点登录就完成了。以后我们再访问 app1 系统时,会携带 app1 的登录态 Cookie,app1 就是登录的。接下来,如果访问 app2 系统时,流程如下:
- 用户访问 app2 系统,app2 系统没有登录,跳转到 SSO。
- 由于请求中携带了 TGC,SSO 判定为已经登录了,不需要重新登录认证。
- SSO 生成新的 ST,浏览器跳转到 app2 系统,并将 ST 作为参数传递给 app2。
- app2 拿到 ST,后台访问 SSO,验证 ST 是否有效。
- 验证成功后,app2 将登录状态写入 session,并在 app2 域下写入 Cookie。
这样,app2 系统不需要走登录流程,就已经是登录了。SSO,app1 和 app2 在不同的域,它们之间的session 不共享也是没问题的。
参考资料
https://juejin.cn/post/6844903586405564430
https://zh.wikipedia.org/wiki/HTTP%E6%91%98%E8%A6%81%E8%AE%A4%E8%AF%81
https://www.cnblogs.com/xiongmaomengnan/p/6671206.html
https://tsejx.github.io/blog/authentication/
https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
https://developer.aliyun.com/article/636281
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)