这篇文章主要是微服务技术的学习笔记,参考资料:
time.geekbang.org/course/intr…
www.bbsmax.com/A/RnJWNaBwz…
持续总结更新。。。
微服务安全解决什么问题
背景
OAuth2
提出的背景就是开放系统间授权问题。比如我有一组照片存储在七牛云上,我想让云冲印打印出来,他门之间的关系如下:
对于上面那种情况,可以分成三种角色:资源拥有者、客户应用、受保护的资源。也就是说我的照片,那么我本人就是照片的拥有者,而存储在七牛云上的照片就是受保护的资源,云冲印就是客户应用。那么我想让云冲印打印照片,那么云冲印就需要去七牛云访问我的照片,这就出现了 开放系统间授权的问题。
针对以上的问题,考虑一下解决方案:
-
方案一:密码用户名复制
也就是说,资源拥有者需要把用户名密码给云冲印,然后云冲印拿着用户名密码去访问七牛云。
显然这种方式,十分不安全,用户名密码泄露给云冲印了,这种方式常用于公司内部的应用。而开放系统间,这种第三方应用一般是不信任的。 -
方案二:万能钥匙
客户应用和受保护的资源间商定一个通用的开发者钥匙,这种方式需要客户应用和受保护的资源间有信任关系的,比如合作商等,这样才可以。但是这种developer key
也会被丢失。
-
方案三:特殊令牌
这种方式是资源拥有者给客户应用一个特殊令牌,这个令牌只能访问受保护的资源。这种方式和OAuth2
很相似。当然这里面牵扯着令牌发放、过期等一些管理令牌的操作。
除了开放系统间授权问题还有传统单块应用安全问题。
现代微服务安全
现代微服务安全主要有这样的问题,微服务粒度划分比较小,服务之间如何认证鉴权,应用形态也变成多种多样,不再是单一的浏览器应用。这个时候不再是传统服务去授权,需要独立的服务去做认证鉴权,核心的技术点也从原来的用户名密码改为Token
机制。
总结
OAuth2
解决问题域和场景:
- 开放系统间授权(社交联合登录、开放API平台)
- 现代微服务安全(单页浏览器App(HTML5/JS/无状态)、无线原生App、服务器端WebApp、微服务和API间调用)
- 企业内部应用认证授权(IAM/SSO)
OAuth2
白话介绍
OAth2
最简向导(简述流程):
- 有用户的数据;
- 有个资源服务器(负责管理用户数据);
- 有个客户应用需要访问用户的数据;
- 给资源服务器按个门暴露用户数据称为API。
- 客户应用可以通过API访问用户数据。
- 资源服务器返回用户数据。
安全问题
但是如何有恶意客户应用通过API来调用,也来访问用户数据那怎么办呢?如果没有安全机制,资源服务器也会把用户数据返回给恶意客户应用。
授权服务器
针对上面说的安全问题,所以需要一个安全机制来保护用户数据。
那么业界实践是提前给客户应用颁发一个Access Token
,它表示客户应用被授权可以访问用户数据。
客户应用去调用API时会携带 Access Token
,资源服务器会校验这个 Access Token
是否合法。
这样就可以完成安全的数据访问。
上面这个机制工作的前提就是必须给客户应用颁发 Access Token
,也就是需要一个颁发 Access Token
的角色。这样就引入一个新的角色叫 授权服务器。
授权服务器干的事情就是生成 Access Token
,并且把 Access Token
颁发给客户应用。
授权服务器和客户应用的关系
总结,一共有以下角色:
- 一个授权服务器:负责生成
Access Token
,并颁发给客户应用。 - 一个客户应用:客户应用携带
Access Token
通过API去资源服务器获取用户数据。 - 一个资源服务器:校验客户应用携带的
Access Token
是否具有访问用户数据的权限,校验成功,返回数据给客户应用。
上面流程中第一步是授权服务器生成Access Token
,但不是自作主张可以颁发给客户应用的,在真实流程中,在颁发Token
前先要征询用户(资源拥有者)同意。如果资源拥有者同意才可以颁发给客户应用。所以还需要用户这个角色参与进来,当客户应用向授权服务器申请 Access Token
时,授权服务器就会把页面跳转到用户端,询问用户是否同意将权限授予客户应用,如果用户确认可以,那么授权服务器就会把Token颁发给客户应用。
OAuth 2.0
标准化了Access Token
的请求和响应部分,OAuth2.0
的细接在RFC 6749(OAuth 2.0
授权框架)中描述。
OAuth2的正式定义
什么是 OAuth 2.0?
- 用户REST、APIs的代理授权框架(delegated authorization framework)。
- 基于令牌Token的授权,在无需暴露用户密码的情况下,使应用能获取对用户数据的有限访问权限。
- 解耦认证和授权。
- 事实上的标准安全框架,支持多种用例场景:
- 服务器端webApp
- 浏览器单页SPA
- 无线/原生App
- 服务器对服务器之间
令牌可以类比仆从钥匙,给应用授予有限的访问权限,让应用能够代表用户去访问用户的数据。
OAuth2.0 优势
OAuth2.0
比OAuth1.0
易于实现- 更安全,客户端不接触用户密码,服务器端更容易集中保护
- 广泛传播并被持续采用
- 短寿命和封装的
token
,token
可以配置生效时间,token
上还可以封装一些用户信息 - 资源服务器和授权服务器解耦
- 集中式授权,简化客户端
HTTP / JSON
友好,易于请求和传递token
- 考虑多种客户端架构场景(服务器端webApp、浏览器单页SPA、无线/原生App、服务器对服务器之间)
- 客户可以具有不同信任级别
OAuth2.0 不足
- 协议框架太宽泛,造成各种实现的兼容性和互操作性差,各家具体实现都有些差异,甚至是不兼容
- 和
OAuth 1.0
不兼容 OAuth2.0
不是一个认证协议,是一个授权框架,OAuth2.0
本身并不能告诉你任何用户信息,当然也可以通过扩展获取用户信息
OAuth2.0 主要涉及角色
- 授权服务器(AS):在客户应用成功认证并获得授权之后,向客户应用颁发访问令牌 Access Token
- 资源拥有者(RO):数据拥有者,想要分享某些资源给第三方应用,比如:七牛云上图片拥有者
- 客户应用:第三方应用,通常是一个web或者无线应用,它需要访问用户的受保护资源,比如:云冲印服务
- 资源服务器(RS):数据存储的地方,一般是一个web站点或者web service API,用户的受保护数据保存于此,比如:七牛云
OAuth 术语
- 客户凭证(Client Credentials):客户的clientId和密码用于认证客户,客户应用想要访问资源服务器需要有凭证,一般在授权服务器上注册获得的
- 令牌(Tokens):授权服务器在接收到客户请求后,颁发的访问令牌
- 作用域(Scopes):客户请求访问令牌时,由资源拥有者额外指定的细分权限
总结
OAuth
的本质是如何获取Token
,如何使用Token
;
OAuth
提供一个宽泛的协议框架,具体安全场景需要定制;
OAuth
是一种在系统之间的代理授权协议;
OAuth
使用代理协议的方式解决密码共享反模式问题。
OAuth 2.0 有哪些典型的模式
典型的 OAuth Flow 和选型
授权码模式(最复杂的也是最安全的,是日常开发中最常用的)
解释流程:
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
下面是上面这些步骤所需要的参数。
A步骤中,客户端申请认证的URI,包含以下参数:
- response_type:表示授权类型,必选项,此处的值固定为”code”
- client_id:表示客户端的ID,必选项
- redirect_uri:表示重定向URI,可选项
- scope:表示申请的权限范围,可选项
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
案例:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
复制代码
C步骤中,服务器回应客户端的URI,包含以下参数:
- code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
案例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
复制代码
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
- grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。
- code:表示上一步获得的授权码,必选项。
- redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
- client_id:表示客户端ID,必选项。
案例:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
复制代码
E步骤中,认证服务器发送的HTTP回复,包含以下参数:
- access_token:表示访问令牌,必选项。
- token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
案例:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
复制代码
简化模式
简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了”授权码”这个步骤。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
解释流程:
(A)客户端将用户导向认证服务器。
(B)用户决定是否给于客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端指定的”重定向URI”,并在URI的Hash部分包含了访问令牌。
(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。
(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。
(F)浏览器执行上一步获得的脚本,提取出令牌。
(G)浏览器将令牌发给客户端。
A步骤中,客户端发出的HTTP请求,包含以下参数:
- response_type:表示授权类型,此处的值固定为”token”,必选项。
- client_id:表示客户端的ID,必选项。
- redirect_uri:表示重定向的URI,可选项。
- scope:表示权限范围,可选项。
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
案例:
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
复制代码
C步骤中,认证服务器回应客户端的URI,包含以下参数:
- access_token:表示访问令牌,必选项。
- token_type:表示令牌类型,该值大小写不敏感,必选项。
- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
案例:
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
&state=xyz&token_type=example&expires_in=3600
复制代码
密码模式
解释流程:
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
(C)认证服务器确认无误后,向客户端提供访问令牌。
B步骤中,客户端发出的HTTP请求,包含以下参数:
- grant_type:表示授权类型,此处的值固定为”password”,必选项。
- username:表示用户名,必选项。
- password:表示用户的密码,必选项。
- scope:表示权限范围,可选项。
案例:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w
复制代码
C步骤中,认证服务器向客户端发送访问令牌
案例:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
复制代码
之前我们说 OAuth2 主要解决问题就是用户名密码不能泄露,尽量不要传递客户端,那为什么还是有一种密码模式允许客户端拿到用户名密码呢?
其实这也是有场景的,如果说这个应用程序也是你们公司自己造的,这种情况下,输入用户名密码也不会有什么问题。
客户端模式
这种更简单,都没有资源拥有者的角色,一般是机器对机器的场景,比如 docker client 要拉取镜像,它可以通过客户端模式去获取访问令牌,然后去拉取镜像。
解释流程:
(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
(B)认证服务器确认无误后,向客户端提供访问令牌。
A步骤中,客户端发出的HTTP请求,包含以下参数:
- granttype:表示授权类型,此处的值固定为”clientcredentials”,必选项。
- scope:表示权限范围,可选项。
案例:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
复制代码
认证服务器必须以某种方式,验证客户端身份。
B步骤中,认证服务器向客户端发送访问令牌
案例:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"example_parameter":"example_value"
}
复制代码
刷新令牌
如果用户访问的时候,客户端的”访问令牌”已经过期,则需要使用”更新令牌”申请一个新的访问令牌。
客户端发出更新令牌的HTTP请求,包含以下参数:
- granttype:表示使用的授权模式,此处的值固定为”refreshtoken”,必选项。
- refresh_token:表示早前收到的更新令牌,必选项。
- scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。
案例:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
复制代码