授权和认证是每个项目中不可或缺的一部分,脆弱的授权、认证流程会在恶意攻击中不堪一击,会在项目运行过程中无法承受高流量的冲击。在这个环节中,OAuth 认证、SSO 单点登录、CAS 中央认证服务会频繁地出现在相关业务的开发人员视野中,可是总是多多少少的懵懵懂懂。
这里笔者会尽力从源头上解决对于相关概念的理解,以及在项目中的实践。并且收录部分面试过程中会遇到的问题。
旨在解决相关人员对于概念的深入理解,对于项目的实践认识,对于面试的关键点剖析。单个篇幅无法做到面面俱到,尽力估计,过程中会提供 JAVA、C Sharp 的代码解释。
在本场 Chat 中,会讲到如下内容:
在维基百科中对于 OAuth 的解释如下:
开放授权(OAuth)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片、视频、联系人列表),而无需将用户名和密码提供给第三方应用。
OAuth 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的 2 小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth 让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。
OAuth 是 OpenID 的一个补充,但是完全不同的服务。
在百度百科中对于 OAuth 的解释如下:
OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 OAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 OAuth 是安全的。OAuth 是 Open Authorization 的简写。
在 OAuth 2.0 的官方网站中的解释如下(摘自:https://oauth.net/):
An open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop Applications.
在 IETF 组织中的对于 OAuth 2.0 Authorization Framework 对应的摘要描述如下:
The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf. This specification replaces and obsoletes the OAuth 1.0 protocol described in RFC 5849.
通过这几个方面的查看,可以明确的消除对于 OAuth 的错误认识,OAuth 不是一个技术框架,也不是一个 jar 包,也不是一个 dll 程序集,它仅仅是一个 protocol,被翻译为协议、标准、或者规则。这只是对于 OAuth 的一个宏观认识。
在 oauth.net 中的简介可以了解到,OAuth 2.0 是允许通过使用简单标准的方法从 Web、移动和桌面应用程序中进行安全授权的开放协议。
OAuth 的历史版本中有 OAuth 1.0 和 OAuth 2.0,其中 OAuth 1.0 在 2010 年发表为 RFC5849,OAuth 2.0 为 RFC6749,并且 OAuth 2.0 不向下兼容 OAuth 1.0,目前 OAuth 2.0 被广泛使用,因此这里不再对 OAuth 1.0 进行更多的描述。
上面是对 OAuth 以及 OAuth 2.0 的宏观认识,下面对于 OAuth 2.0 包括的几种方式进行介绍。
(图片来自 tools.ietf.org 的截图)
如图所示,在 RFC6749 可以得到最官方的几种授权类型:
这也是 OAuth 2.0 区别于 OAuth 1.0 的地方,OAuth 2.0 中工作组所作出的明确决定是,它不再是一个单个协议,而是一个框架。从名字上也可以看出来,1.0 是 protocol,而 2.0 的标题是 framework。
授权码类型是目前 OAuth 2.0 中最常用,最安全的一种类型。通过去授权端获取授权码,利用授权码换取 token,通过使用 token 去资源服务器获取受保护资源。
以阿里云的内容协作平台 CCP 的官方文档为实例:
(图片引用 help.aliyun.com/)
Implicit Grant 有的地方被翻译成简单授权,但是笔者认为翻译成简单授权有点偏离原有的意思。它只是针对授权码的环节做了一定简化,但是它的使用并不能被视为简单的授权类型。
授权码流程的不同步骤的关键之处,在于能够保持不同组件之间的分离,浏览器不知道客户端应该知道的内容,客户端无法获取到浏览器的状态。但是,如果把客户端放到浏览器中,该怎么办呢?这种情况在目前的 SPA 单页面应用程序中经常会出现。客户端执行的所有过程,由于都是基于浏览器实现的,所以几乎相当于在浏览器上裸奔。
(图片引用 OAuth 2.0 in Action)
使用这种方法,没有现实的方法来保护客户端的密码信息,因为它需要对于浏览器可用才能执行后续流程。
隐式授予流程不能用于获取刷新令牌,由于基于浏览器的应用基本上都是短时的连接,仅持续加载它们的浏览器的上下文的会话长度,因此,刷新令牌的用途非常有限。
与其他授予类型不同,可以假定资源所有者仍处于浏览器中,并在必要的时候可用于重新授权客户端。授权服务器仍能够应用首次使用信任(TOFU)原则从而使重新认证成为无缝的用户体验。
以阿里云的内容协作平台 CCP 的官方文档为实例:
(图片引用 help.aliyun.com)
(图片引用 OAuth 2.0 in Action)
在 RFC 中是这么进行描述的:
(图片引用:tools.ietf.org)
实际上是一个道理,只是表现形式稍微有一点不同而已。客户端直接对它自己进行验证,然后授权服务器颁发适当的令牌。
(图片引用 OAuth 2.0 in Action)
通过使用 Resource Owner 的用户名和密码来换取令牌,这种方式一般是不提倡使用的,原因是当用户名和密码暴露出来的时候,就自然会导致攻击面变得更大,风险更高。
在上述几种 Grant Type 中的 Client,它并不能被简单的理解为浏览器或者桌面应用,在 OAuth 中,只要软件使用受保护资源上的 API,那么它就被视为客户端。
以上就是对于 OAuth 的说明,以及 OAuth 2.0 framework 中涉及到的几种类型的图示和描述,以及在实际场景中的应用流程。
对于代码 OAuth 2.0 的具体实现过程,由于每一个环节都涉及很多内容,不在编码过程中消化就会很难理解,后续将会在专栏中进行详细过程的剖析。这里通过概念的引入和宏观的理解,消除平时工作过程中对于概念的错误认识。
SSO,single sign on 单点登录,单点登录为用户提供无缝的身份验证体验。在构建的应用程序中,一旦登录这些应用程序中的一个,当使用其他应用程序的情况下,不需要再次登录。反之,在登出的过程中,只要一个应用程序登出,那么所有应用对应的登录状态全是登出。
这就是 SSO,单点登录的基本含义。
CAS,Central Authentication Service,集中式身份验证。SSO 和 CAS 是密不可分的,SSO 可以理解为一个软件系统,而 CAS 是作为实现 SSO 的一种解决方案。更准确的来说,它是一个规范性质的协议。
(图片引自 apereo.github.io 截图)
对应的 C sharp 的源码可以参考如下的 GitHub 源码,地址为:
https://github.com/apereo/dotnet-cas-client
对应 Java 的源码可以参考如下 GitHub 的源码,地址为:
https://github.com/apereo/cas
首先要知道的可能是 JWT 是什么?这个概念在很多地方,要么是没有被解释,要么是被结合场景使用的解释,偏离了它本身的概念。那么参考 IETF 的说明来对 JWT 的本质进行认识:(引自:tools.ietf.org/)
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (mac) and/or encrypted.
从摘要中的描述基本可以明确,JWT 仅仅是在于两个部分之间进行传输声明的一种 compact 和 url-safe 的方式。
compact 是指针对于体积上,通过 JWT 进行签名和加密的结果体积比较小,在传输过程中减少带宽的负载。
url-safe 是指针对对应的加密算法,在加密和解密的结构上,相对来说 JWT 处理的信息更加安全。
它能够防止一定程度的攻击,如下情况。
情况 1:
如果在 html 中一样可以轻松地植入,比如:
将上图中的某一个 img 标签对应 i 的的 src 属性进行替换就会改变执行的请求,导致如下图中的修改:
可以看到,jwt.io 在这种简单的操作中,对应的 logo 就被替换掉了,如果换得 URL 不是一个图片,而是一个关于用户/权限/或者支付的业务,那结果是很可怕的。
有很多人对 CSRF(cross-site request forgery)没有明确的概念,上面的这个例子,就能简单的证明 CSRF 带来的危害。
情况 2:
如果写在 cookie 里,可以很容截获,比如下:
在浏览器中通过 F12 弹出的开发者工具中,选择 Application 左侧的 Cookie 选项,双击右侧的键值对就可以修改,这种方式会导致 cookie,或者 local storage 中存入的信息被泄露或者修改。
但是对于跨站脚本的攻击,依然起不到作用,也就是 XSS(Cross-Site Scripting),通过脚本注入可以像浏览器中植入脚本对 cookie/local storage 中的信息进行获取或者修改。具体方式就不再说明(考虑到被恶意使用进行攻击,这里只需要知道会出现这种情况)
另外对于 JWT 还有一个常见的错误认识:
可能有些同学会认为,在 http://jwt.io 上使用的时候会认为,既然可以 encode,也可以 decode,那么这种情况下,对于签名和验签还有什么意义。
但是实际上并不是我们想象的那样,在使用 secret 签名过后,在验签的时候,secret 是无法被反向解析出来的。
如下:
1. header
{
"alg": "HS256",
"typ": "JWT"
}
2. payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
3. secret
test
4. encode 后的内容:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MdiyfQ.5mhBHqs5_DTLdINd9p5m7ZJ6XD0Xc55kIaCRY5r6HRA
用点符号和换行符变成易读格式:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
5mhBHqs5_DTLdINd9p5m7ZJ6XD0Xc55kIaCRY5r6HRA
上述过程是编码的过程,下面观察解码的过程:
1. 清空 decoded 下的对应信息
2. 将上述编码后的内容,放到 encoded 下的文本框
加上 secret 后才能够完成签名的验证。这种情况下意味着,如果获取到 token 的恶意用户,即使窃取信息后再次发送,修改任何一个参数,都无法正确的进行验签。
所以很重要的一点,就是 signature 的签名,如果没有密钥的话,是无法进行可逆解析的,否则这个签名过程就没有意义了。
对于 JWT 的进一步认识可以参考如下开源代码:
https://github.com/jwt-dotnet/jwt
https://github.com/dvsekhvalnov/jose-jwt
相比于官方提供的 jose-jwt 的 .NET 版本,Jwt.Net 的封装相对来说使用起来,要稍微麻烦一点,但是它的优势在于对 .NET CORE 和 .NET OWIN 有对应的扩展。而 jose-jwt 提供的只有加密和解密的过程。
但是个人认为相对来说,官方提供的源码更加有助于对 JWT,以及相关标准进行理解。而且提供的源码不局限于 .NET。
以上就是对于 JWT 的基本信息的一些了解和说明。它和授权的关系并没有直接关系,只是在授权过程中,比如在 JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens
draft-ietf-oauth-access-token-jwt-03 中就将 access tokens 和 JWT 进行了结合。
(图片引用自:tools.ietf.org 的截图)
这里提到的 OWIN 中间件,是在 C# 进行 OAuth 2.0 环境的搭建过程中使用的中间件,对于它的基本介绍如下:
使用 OAuth 2.0 framework,第三方应用可以获得对 HTTP 服务的有限访问权限。客户端不使用资源所有者的凭据来访问受保护的资源, 而是获取一个访问令牌(表示特定范围、生存期和其他访问属性的字符串)。授权服务器将访问令牌颁发给第三方客户端,并批准资源所有者。
OWIN 定义 .NET Web 服务器和 Web 应用程序之间的标准接口。 OWIN 接口的目标是将服务器和应用程序分离,鼓励开发简单的 .NET Web 开发模块,并通过作为开放标准来鼓励 .NET Web 开发工具的开源生态系统。
来自:
https://docs.microsoft.com/zh-cn/aspnet/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server
Microsoft.Owin.Security.OAuth
从图中右侧标注的红框部分,可以看到对应的 GitHub 上的源码,对详细的过程进行剖析。比如:
用户信息赋值的关键点,在这里对 token 进行验证,如果验证不通过的情况之下,将无法通过:
在开发过程中可能还会遇到问题就是即使所有的地方都配置好了,但是死活验证不通过,原因是依赖的 dll 没有引入完整,但是也没有报错,导致的。参考下面的图中对应的 dll。
对于这几个问题,在上述内容中均能够找到答案。
很多知识点在总结的过程中发现,一个篇幅不能够面面俱到,尤其对于授权和认证的具体实现过程,不同实现方式的特点,为了能够有具体的可操作的解决方案,后续会总结出详细过程步骤以专栏的形式分章节列出。有不足的地方欢迎指正。