核心概念
-
认证(authentication)
-
授权(authorization)
-
凭证(credential)
认证(authentication)
认证的目的是确定进入系统的用户的身份。
常见认证方式有Basic(账号密码)、Bearer(OAuth2),Http协议本身也支持用户的认证。
Http 认证框架
http是无状态协议,在访问资源时,为了保护资源的安全,需要对用户身份进行认证。rfc7235规范定义了如何通过http协议认证用户身份。主要涉及4个Hearder和2个Http Status code。
// Header
+---------------------+----------+----------+-------------+
| Header Field Name | Protocol | Status | Reference |
+---------------------+----------+----------+-------------+
| Authorization | http | standard | Section 4.2 |
| Proxy-Authenticate | http | standard | Section 4.3 |
| Proxy-Authorization | http | standard | Section 4.4 |
| WWW-Authenticate | http | standard | Section 4.1 |
+---------------------+----------+----------+-------------+
// Status Code
+-------+-------------------------------+-------------+
| Value | Description | Reference |
+-------+-------------------------------+-------------+
| 401 | Unauthorized | Section 3.1 |
| 407 | Proxy Authentication Required | Section 3.2 |
+-------+-------------------------------+-------------+
当我们要访问某个资源时,资源服务器会在响应状态码401或407,请求头里返回支持的认证方式,格式如下:
401 Unauthorized
WWW-Authenticate: <认证方案> realm=<保护区域的描述信息>, <认证方案> realm=<保护区域的描述信息>
407 Proxy Authentication Required
Proxy-Authenticate: <认证方案> realm=<保护区域的描述信息>, <认证方案> realm=<保护区域的描述信息>
WWW是对资源服务器的认证,Proxy是对代理服务器的认证。
收到响应后,客户端讲要在输入身份凭证内容。然后在请求投带上凭证,再次请求资源服务器。
Authorization: <认证方案> <凭证内容>
Proxy-Authorization: <认证方案> <凭证内容>
认证方案
认证方案就是身份认证方式,可以是账号密码、授权码、生物指纹等。已注册的方式在这能看到:Hypertext Transfer Protocol (HTTP) Authentication Scheme Registry。常见的有Basic、Bearer等。
Basic 基本认证 [rfc7617]
触发Basic认证
WWW-Authenticate: Basic realm="foo", charset="UTF-8"
携带认证信息:传入base64(账号:密码)的结果
Authorization: Basic dGVzdDoxMjPCow==
列子里dGVzdDoxMjPCow==是base64(test:123£)的结果
Bearer 认证 [rfc6750]
Bearer是一种基于OAuth2.0认证框架([rfc6749])来证明身份的方式。认证过程如下:
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
-
触发Bearer认证
WWW-Authenticate: Bearer realm="example"
-
客户端请求资源服务器拿到授权code后,通过授权code请求认证服务器拿到acess_token
{
"access_token":"mF_9.B5f-4.1JqM",
"token_type":"Bearer",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}
-
携带认证信息:
Authorization: Bearer mF_9.B5f-4.1JqM
生物认证 [webauthn]
WebAuthn全称是Web Authentication API,是由W3C和FIDO在 Google、Mozilla、Microsoft、Yubico 等公司的参与下编写的规范。WebAuthn是一组在服务器、浏览器和身份验证器之间启用无密码身份验证的技术。一般使用非对称加密技术,用公私钥代替传统的密码,来注册和验证用户。主流浏览器Chrome、Firefox、Edge 和 Safari 都支持 WebAuthn。
注册流程
|
认证流程
|
授权(authorization)
授权是系统对用户赋予权限的过程,对于平台系统会注重权限隔离,包括功能权限、数据权限等。C端应用一般只访问自身数据,所以权限比较简单,认证通过就能获得账号的全部处理权限。
有一点和平台系统类似,当应用需要和三方进行交互时,尤其需要从三方系统获取用户操作权限。
例如,通过三方注册登录的场景,用户要让应用知道他在三方系统上有账号和个人信息。最简单的做法是把账号密码提供给应用,让应用直接登录三方账号得到用户信息。很明细这种方案是不安全的,而OAuth2.0认证授权协议就是解决这个问题的最佳实践。
OAuth 2.0 认证授权
OAuth使用token来代替密码,避免密码的泄露。认证过程如下:
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
角色
-
第三方应用:需要获取资源的应用
-
资源拥有者(resource owner):能够授予对受保护资源的访问权限的实体。当资源所有者是个人时,它称为最终用户。
-
授权服务器(authorization server):在成功对资源所有者进行身份验证并获得授权后,向客户端颁发访问令牌的服务器。
-
资源服务器(resource server):托管受保护资源的服务器,能够使用访问令牌接受和响应受保护的资源请求。
-
操作代理(client):代表资源所有者并授权发出受保护资源请求的应用程序。
以app通过Google授权登录为例,这里的角色分别是:
-
第三方应用:app
-
资源拥有者:当前用户
-
授权服务器:Google授权服务
-
资源服务器:Google账号服务
-
操作代理:浏览器
概念
-
授权码
-
访问令牌(Access Token)
-
刷新令牌(Refresh Token)
授权码
授权码是用户授权成功之后,授权服务器颁发给客户端的凭证。它无法直接从资源服务器获得到资源,得通过授权服务器获取访问令牌。在获取访问令牌时,需要验证三方应用的身份。所以,直接截获授权码并没有用
访问令牌
访问令牌是用于访问受保护资源的凭证。表示访问特定资源的权限范围和有效时间。它可以是一个简单的id标识,也可以自包含授权信息(JWT)。
刷新令牌
刷新令牌是用于获取新的访问令牌的凭证。它是可选项,如果颁发了,就能通过刷新令牌向授权服务器获得新的访问令牌。
+--------+ +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+
认证方式
OAuth2.0为支持不同应用场景,安全力度从高到低分为4种模式:
-
授权码模式
-
隐式授权模式
-
密码模式
-
客户的模式
授权码模式
这个OAuth2.0的完全体,也是最严谨的模式。
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
在发起授权前,第三方应用要在授权服务器上注册自己,得到一个secretKey,表示应用的身份。
在客户端拿到授权码后,第三方应用拿着授权码+secretKey向授权服务获取访问令牌。
授权成功后,第三方应用会获得access_token(有效期短)和refresh_token(有效期长)。
为什么会有两种令牌?
这里涉及token的安全。不记名token在颁发后,在过期前都是有效。这就导致token泄露后,账号存在很大风险。一种做法是控制token的有效期较短(access_token),又要避免频繁让用户授权。折中方案就是定时去刷新access_token,在刷新的时候判断用户授权是否有效。
隐式授权模式
这种模式是为了应对没有第三方应用的情况,对于前端应用,直接从授权服务器得到access_token,省略了授权码的环节。
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+
为了尽量保证access_token的安全,access_token放在回调URL里的Fragment,避免后续客户的发起请求,把access_token给带出去了。
密码模式
这种模式是用户直接提供密码给第三方应用,应用通过密码向授权服务器获取access_token。这是高度信任客户端的情况下才会使用。
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
密码模式需要用户提供密码,在C端应用基本不可行。
客户的模式
这种模式是客户的直接向授权服务器申请访问access_token,不需要任何授权。
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
这种模式下,授权过程要保持完全可信。只有在后的微服务的架构下,服务间调用需要access_token,服务在调用之前会从授权服务器获取access_token。
凭证(credential)
凭证是系统和用户之间的承诺,授权过程就是为了获取这份承诺。
凭证的形式
-
Cookie-Session
-
JWT
Cookie-Session [rfc6265]
在web领域,这是主流的存储用户状态(身份)的方案。用户信息存在服务端的session里,把sessionId通过cookie传给客户端。每次Http请求都会带上cookie信息,服务端讲能知道请求来自哪个用户。
所以,对于授权通过的用户,在session加上授权信息。只要cookie不泄露,用户信息就是安全的。
存在的问题:
-
对于移动端,用户操作的不再是浏览器,所以像cookie的维护、同源策略的安全保护都需要重新实现。
-
分布式场景下,session面临分布式存储和校验问题。
JWT [rfc7519]
JWT是JSON web token大家简写,它是主流的token格式,也是移动端使用最广泛的用户身份存储方案。
常用的做法是用jwt做认证:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJpY3lmZW5peCIsInNjb3BlIjpbIkFMTCJdLCJleHAiOjE1ODQ5NDg5NDcsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiIsIlJPTEVfQURNSU4iXSwianRpIjoiOWQ3NzU4NmEtM2Y0Zi00Y2JiLTk5MjQtZmUyZjc3ZGZhMzNkIiwiY2xpZW50X2lkIjoiYm9va3N0b3JlX2Zyb250ZW5kIiwidXNlcm5hbWUiOiJpY3lmZW5peCJ9.539WMzbjv63wBtx4ytYYw_Fo1ECG_9vsgAn8bheflL8
协议格式
-
Header
-
Payload
-
Signature
Header
Header包含type和alg两个字段。type是固定的“JWT”,alg是签名的算法,除了HS256还支持RS256、ES256等,在JSON Web Tokens - jwt.io上能看到完整的列表。
{
"alg": "HS256",
"typ": "JWT"
}
Payload
payload的JWT具体的内容,内容是可以自定义的,RFC 7519推荐了7种类型:
-
iss(Issuer):签发人。
-
exp(Expiration Time):令牌过期时间。
-
sub(Subject):主题。
-
aud (Audience):令牌受众。
-
nbf (Not Before):令牌生效时间。
-
iat (Issued At):令牌签发时间。
-
jti (JWT ID):令牌编号。
Signature
使用密钥,通过指定的算法对header和payload进行签名。确保JWT里的内容没有被其他人篡改。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload) , secret)
编解码示例
字段 | 原始 | base64url编码 |
header | {"alg":"HS256","typ":"JWT"} | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 |
payload | {"sub":"1234567890","name":"John Doe","iat":1516239022} | eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ== |
secret | abcd |
JWT结果:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.cq-uoLxOu3V4RjxnbUAFZ36aSZ24BXiAH8RFDYVA6XU