Api认证授权方案总结:HTTP Basic Authentication、HMAC、OAuth2、JWT。
关于认证,授权与凭证
- 认证(authentication)
认证指的是当前用户的身份,当用户登陆过后系统便能追踪到他的身份做出符合相应业务逻辑的操作。
认证技术解决的是我是谁的问题。
- 授权(authorization)
授权指的是什么样的身份被允许访问某些资源,在获取到用户身份后继续检查用户的权限。单一的系统授权往往是伴随认证来完成的,但是在开放 API 的多系统结构下,授权可以由不同的系统来完成,例如 OAuth。
授权技术解决的是我能干什么的问题。
- 凭证(credentials)
凭证是认证和授权的基础,用来标记访问者的身份或权利,例如服务器为每一个访问者颁发 session ID 存放到 cookie,SSH 登录的密匙、JWT 令牌…
API开发中常用的认证,授权技术
HTTP Basic Authentication
流程
- 用户在浏览器输入用户名和密码
- 组合用户名和密码然后 Base64 编码
- 给编码后的字符串添加 Basic 前缀,然后设置名称为 Authorization 的 header 头部
API 也可以提供 HTTP Basic Authentication 认证方式,那么客户端可以很简单通过 Base64 传输用户名和密码即可。
缺陷
BASE64仅仅是编码而非加密,如果以HTTP明文传输会有泄露风险。
HMAC(AK/SK)认证
很多开放API平台都会选择基于一个AK/SK的认证方式。这种基于 AK/SK 的认证方式主要是利用散列的消息认证码 (Hash-based MessageAuthentication Code) 来实现的。HMAC是利用带有 key 值的哈希算法生成消息摘要。
基本过程如下:
签名生成
基于参数timestamp,AK,请求PATH,请求参数进行key自然排序,然后进行字段与字段值拼接最后再拼接上SK(注意SK不传递),MD5加密生成Sign,将Sign加入请求参数/请求头中发送请求。
鉴权
分解请求参数得出:timestamp,AK,请求PATH,请求参数。
- 是对timestamp进行临界值判断(例如过滤掉5分钟之前的请求,规避重放攻击)。
- 是对当前请求的PATH进行权限判断,判断用户AK是否有当前PATH的请求权限。
- 是根据AK在服务端(如数据库)获取到用户的SK,对参数timestamp,AK,请求PATH,请求参数进行key自然排序,然后进行字段与字段值拼接最后再拼接上SK,MD5加密生成Sign与传递过来的Sign进行对比鉴权。
质疑/应答算法
基于时间的一次性密码认证并不能严格避免重放攻击,质疑/应答算法需要客户端先请求一次服务器,获得一个 401 未认证的返回,并得到一个随机字符串(nonce)。将 nonce 附加到按照上面说到的方法进行 HMAC 签名,服务器使用预先分配的 nonce 同样进行签名校验,这个 nonce 在服务器只会被使用一次,因此可以提供唯一的摘要。
OAuth2
OAuth2 是一个开放授权标准框架,用来解决的是第三方应用在无需用户提供账号密码的情况下经由用户授权访问用户的私有资源的一套流程规范。
OAuth2角色
- Resource Owner:资源拥有者(如图片资源的所有者)
- Resource Server:资源服务器(如存储图片资源的服务器)
- Client: 任何可以消费资源服务器的第三方应用
- Authorization Server :授权服务器,管理Resource Owner,Client和Resource Server的三角关系的中间层
以上,Authorization server和Resource server可以分别由独立的服务提供,也可以合并一起提供服务。OAuth2解决问题的关键在于使用Authorization server提供一个访问令牌给Client,使得Client可以在不获取Resource owner登录信息(account和password)的情况下 通过Resource owner授权后可以访问Resource owner的受保护资源。
OAuth2的部署与角色交互
- 增加一个Authorization server,提供授权的实现,一般由Resource server 来提供。
- Resource server 为第三方应用程序提供注册接口。
- Resource server 开放相应的受保护资源的API。
- Client 注册成为Resource server的第三方应用。
- Client 消费这些API。
资源服务提供商(需要完成1-2-3步骤)
一般情况下,Resource server提供Authorization server服务,主要提供两类接口:
- 授权接口(接受Client的授权请求,引导用户完成登陆授权的过程)
- 获取访问令牌接口(授权接口会提供许可凭据,通过这个验证并颁发Resource owner的访问令牌access_token给Client,而且Client可以更新过期的访问令牌)。
需要提供一个第三方应用程序注册管理的服务,并为注册完成的第三方应用程序分配app_id与app_secret:
- client_id:区分第三方应用程序的标识id。
- client_secret:第三方应用程序的私钥。
第三方应用程序(需要完成4-5步骤):
在Client取得client_id和client_secret之后,使用这些信息来发起授权请求获取access_token并使用access_token请求和消费 Resource owner 受保护的资源。
OAuth2授权流程
- (A)用户访问第三方应用,第三方应用向资源服务器发起授权请求。
- (B)资源服务器接受第三方应用的授权请求,鉴权后返回授权许可给到第三方应用。
- (C)第三方应用使用授权许可向Authorization server发起请求。
- (D)Authorization server验证第三方应用的身份和授权许可,发送访问令牌access_token给第三方应用。
- (E)第三方应用用访问令牌请求用户存储在资源服务器的资源。
- (F)资源服务器根据访问令牌,返回用户的资源给到第三方应用。
上述(A)到(D)是授权的过程(主要角色用户,第三方应用,资源服务器,Authorization server);(E)到(F)是消费资源的过程(主要角色第三方应用,资源服务器)。
访问令牌access_token是整个OAuth2的核心,访问令牌是对第三方应用可以在资源服务器访问用户的哪些信息这个完整权限的一个抽象,包含了第三方应用标识、用户标识、访问资源权限这三个主要信息。
四种授权许可方式(Authorization Grant)
授权许可是一个代表资源所有者授权(访问受保护资源)的凭据,客户端用它来获取访问令牌access_token。授权许可是用户授予第三方获得资源服务器访问令牌的一个凭据,OAuth2定义了四种许可类型:
- Authorization Code:授权码模式
- Implicit:隐式许可模式
- Resource Owner Password Credentials:密码授权模式
- Client Credentials :客户端模式
一下四种授权许可流程对应上述(OAuth2的授权流程)中ABDE四个阶段的展开。
Authorization Code(授权码模式)
OAuth2最常用的一种授权许可类型,要求Client具有可公开访问的Server服务器来接受Authorization Code,具体的流程如下:
- (A)Client使用浏览器(用户代理)跳转到Authorization server的提供的认证URL上。访问的时候Client需要携带一些信息,如(客户端标识,请求范围,state值和重定向回来的URL)这些参数。
Authorization Request
1. response_type:固定为"code"
2. client_id:第三方应用的标识ID
3. state:Client提供的一个字符串,服务器会原样返回给Client,避免 csrf 攻击
4. redirect_uri:授权成功后的重定向地址
5. scope:授权范围
示例:
GET /authorize?response_type=code&client_id=1&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Foauth2&scope=user,photo HTTP/1.1
Host: server.example.com
- (B)Authorization server验证Client在(A)中传递的参数信息,验证后提供一个页面供Resource owner登录进行身份认证,认证成功后选择Client可以访问Resource server的哪些资源以及读写权限。
- (C)Authorization server 在(B)流程后返回一个授权码(Authorization Code),通过重定向的方式给到Client。
Authorization Response
1. code:授权码
2. state:client传送过来的state值
示例:
HTTP/1.1 302 Found
Location: https://client.example.com/oauth2?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
Location头部信息指向步骤(A)提供的redirect_uri地址,同时携带code信息和state信息给client
- (D)Client拿着(C)中获得的授权码(Authorization Code)和(客户端标识、重定向URL等信息),请求Authorization server提供的获取访问令牌access_token的URL。此操作在后台服务器上完成的。
Access Token Request
1. grant_type:固定值“authorization_code”
2. code : Authorization Response 中响应的code
3. redirect_uri:与Authorization Request中提供的redirect_uri相同
4. client_id:Authorization Request中提供的client_id
示例:
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=123&client_id=1&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Foauth2
- (E)Authorization server验证后返回访问令牌access_token和可选的刷新令牌refresh_token以及令牌有效时间等信息给Client。
Access Token Response
1. access_token:访问令牌
2. refresh_token:刷新令牌
3. expires_in:过期时间
4. scope:授权范围
5. token_type:表示令牌类型
示例:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
Implicit(隐式许可模式)
Implicit是Authorization Code的简化版本。其中省略掉了颁发授权码(Authorization Code)给Client的过程,而是直接返回访问令牌和可选的刷新令牌。这种授权模式不通过第三方应用的后台服务器,直接在浏览器中向认证服务器申请令牌。
与Authorzation Code类型区别:省略了Authorization Response和Access Token Request,而是直接由Authorization Request返回Access Token Response信息。
具体的流程如下:
- (A)Client使用浏览器(用户代理)跳转到Authorization server的提供的认证URL上。访问的时候Client需要携带一些信息,如(客户端标识,请求范围,本地状态和重定向回来的URL)这些参数。
Authorization Request
1. response_type:值固定为“token”
2. client_id:第三方应用的标识ID
3. state:Client提供的一个字符串,服务器会原样返回给Client,避免 csrf 攻击
4. redirect_uri:授权成功后的重定向地址
5. scope:授权范围
GET /authorize?response_type=token&client_id=1&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Foauth2&scope=user,photo HTTP/1.1
Host: server.example.com
- (B)Authorization server验证后提供一个页面供Resource owner登录进行身份认证,认证成功后选择Client可以访问Resource server的哪些资源以及读写权限。
- (C)Authorization server在(B)流程后返回访问令牌access_token Hash值,通过重定向的方式给到Client。
Access Token Response
1. access_token:访问令牌
2. refresh_token:刷新令牌
3. expires_in:过期时间
4. state:client传送过来的state值
5. token_type:表示令牌类型
6. scope:授权范围
HTTP/1.1 302 Found
Location: http://client.example.com/oauth2#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&expires_in=3600&token_type=example
和Authorization Code的区别在于它是把token信息放在了url的hash部分(#)而不是作为参数(?)。这样浏览器在访问重定向的Location指定的url时,就不会把这些数据发送到服务器。
- (D)Client向Resource server发出请求(不包括上一步收到的Hash值)。
- (E)Resource server返回脚本页面,可以通过其脚本获取Hash值中的访问令牌access_token。
- (F)Client 执行上一步获得的脚本,提取出访问令牌access_token。
Resource Owner Password Credentials(密码授权模式)
和Authorzation Code类型下重要的区分就是省略了Authorization Request和Authorization Response。而是Client直接使用Resource owner提供的account和password来直接请求access_token(直接发起Access Token Request然后返回Access Token Response信息)。这种模式一般适用于Resource server高度信任第三方Client的情况下,如公司内部应用服务。
具体的流程如下:
- (A)用户向第三方应用提供account和password。
- (B)第三方应用将获取到的account和password发给向Authorization server进行身份认证。
Authorization Request
1. grant_type:固定为“password”
2. username:用户登陆名
3. passward:用户登陆密码
4. scope:授权范围
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=blackheart&password=1234
- (C)Authorization server验证后,向第三方应用返回访问令牌access_token。
Access Token Response
1. access_token:访问令牌
2. refresh_token:刷新令牌
3. expires_in:过期时间
4. scope:授权范围
5. token_type:表示令牌类型
示例:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
Client Credentials(客户端模式)
Client直接已自己的名义而不是Resource owner的名义去要求访问Resource server的一些受保护资源。这种授权模式一般应用在 B2B 的服务授权场景,属于企业间数据授权和交互的范畴。
- (A)Client向Authorization server直接进行身份认证。
Authorization Request
1. grant_type:固定为"clientcredentials"
2. scope:授权范围
3. 其他信息,如appkey,appSecret...
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=clientcredentials&...
- (B)Authorization server认证通过后,向Client返回访问令牌access_token。
Access Token Response
1. access_token:访问令牌
2. refresh_token:刷新令牌
3. expires_in:过期时间
示例:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}
OAuth2 Token传递方式
OAuth2 Token传递指的是第三方Client拿到access_token后,发送给Resouce Server的方式。在RFC6750
中定义了三种方式:
- URI Query Parameter
- Authorization Request Header Field
- Form-Encoded Body Parameter
URI Query Parameter
在HTTP请求中通过URI Query Parameter传递access_token是最常见的方式,需要注意Client需要设置Request Header Cache-Control:no-store
,避免被中间服务器缓存。
GET /resource?access_token=2YotnFZFEjr1zCsicMWpAA HTTP/1.1
Host: server.example.com
Authorization Request Header Field
在HTTP请求中使用Authorization Request Header 传递access_token,Bearer为固定access_token头部信息。
GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
Form-Encoded Body Parameter
在HTTP请求中使用Request Body传递access_token,Request Header的Content-Type
固定的application/x-www-form-urlencoded
。
POST /resource HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
access_token=2YotnFZFEjr1zCsicMWpAA
JWT
JSON Web Token
JSON Web Token是一套开放的标准rfc7519,它定义了一套简洁(compact)且 URL安全(URL-safe)的方案,以安全地在客户端和服务器之间传输 JSON 格式的信息。
无状态原则:用户登录之后,服务器会返回一串 token 并保存在本地,在这之后的服务器访问都要带上这串 token,来获得访问相关路由、服务及资源的权限。比如单点登录就比较多地使用了JWT,因为它的体积小,并且经过简单处理(使用 HTTP 头带上 Bearer 属性 + token )就可以支持跨域操作。
JWT工作流程
- 首先,某 client 使用自己的账号密码发送 post 请求 login,由于这是首次接触,服务器会校验账号与密码是否合法。
- 如果一致,则根据密钥生成一个 token 并返回给到client。
- client 收到这个 token 并保存在本地。在这之后,需要访问一个受保护的路由或资源时,必须附加上token(通常使用 Header 的 Authorization 属性)发送到服务器。
- 服务器接收到请求,会对 token 有效性进行校验,并做出响应。
JWT组成
// Header
{
"alg": "HS256",
"typ": "JWT"
}
// Payload
{
// reserved claims
"iss": "a.com",
"exp": "1d",
// public claims
"http://a.com": true,
// private claims
"company": "A",
"awesome": true
}
// $Signature
//HS256(Base64(Header) + "." + Base64(Payload), secretKey)
// JWT
//JWT = Base64(Header) + "." + Base64(Payload) + "." + $Signature
JWT三部分组成:
- 经过 Base64 编码的Header。Header是一个JSON对象,对象里有一个值为“JWT” 的 typ 属性,以及 alg 属性,值为 HS256,表明最终使用的加密算法是 HS256。
- 经过 Base64 编码 Payload。Payload被定义为实体的状态,像token自身附加元数据一样,claim 包含我们想要传输的信息,以及用于服务器验证的信息,一般有 reserved/public/private 三类。
- Signature由Header指定的算法(如HS256) 加密产生。该算法有两个参数,第一个参数是经过Base64分别编码的Header及Payload通过 . 连接组成的字符串,第二个参数是Secret Key,由生成Token的服务器持有。
JWT 仅仅是对 Payload 做了简单的 Encode 处理,并未被加密,并不能保证数据的安全性。
服务端验证
服务端接收到Token之后,会逆向构造过程,Decode 出 JWT 的三个部分,这一步可以得到 Signature ,Payload与Header,结合服务端配置的 Secret Key,可以再次生成新的 Signature,与原有的 Signature 比对以验证 token 是否有效,完成用户身份的认证,验证通过才会使用 Payload 的数据。
服务端最终只是为了验证 Signature 是否仍是自己当时下发给 client 的那个,如果验证通过,则说明该 JWT 有效并且来自可靠来源,否则说明可能是对应用程序的潜在攻击。