[认证受权] 3.基于OAuth2的认证(译)

原文: [认证受权] 3.基于OAuth2的认证(译)

OAuth 2.0 规范定义了一个受权(delegation协议,对于使用Web的应用程序和API在网络上传递受权决策很是有用。OAuth被用在各钟各样的应用程序中,包括提供用户认证的机制。这致使许多的开发者和API提供者得出一个OAuth自己是一个认证协议的错误结论,并将其错误的使用于此。让咱们再次明确的指出:html

OAuth2.0 不是认证协议。
OAuth2.0 不是认证协议。
OAuth2.0 不是认证协议。

混乱的根源来自于在认证协议的内部实际上使用了OAuth,开发人员看到OAuth组件并与OAuth流程进行交互,并假设经过简单地使用OAuth,他们就能够完成用户认证。这不只不是事情的真相,并且对服务提供商,开发人员以及最终用户而言都是危险的事情。git

本文旨在帮助潜在的身份提供者如何基于OAuth2构建用户身份认证。实际上,若是你说“我有OAuth2,而且我须要身份认证”,那么请继续阅读。github

什么是认证(Authentication)?

在用户访问一个应用程序的上下文环境中认证会告诉应用程序当前用户是谁以及其是否存在。一个完整的认证协议可能还会告诉你一些关于此用户的相关属性,好比惟一标识符、电子邮件地址以及应用程序说“早安”时所须要的内容。认证是关于应用程序中存在的用户,而互联网规模的认证协议须要可以跨网络和安全边界来执行此操做。web

然而,OAuth没有告诉应用程序上述任何信息。OAuth对用户没有任何说明,也没有说明如何证实他们的存在,即便他们就在那里。对于OAuth的Client而言,它请求一个token,获得一个token,并用这个token访问一些API。但它不知道是谁受权的应用程序,以及甚至还有一个用户在那里。实际上,OAuth的大部分问题在于Client和被访问的资源之间的链接上在用户不存在的状况下使用这种委托访问。这对于Client受权来讲是好的,可是对于用户身份认证来讲却很是糟糕,由于认证须要肯定用户是否存在(以及他们是谁)。json

另一个的混淆的因素,一个OAuth的过程一般包含在一些认证的过程当中:资源全部者在受权步骤中向受权服务器进行身份验证,客户端向令牌端点中的受权服务器进行身份验证,可能还有其余的。OAuth协议中的这些认证事件的存在不可以说明OAuth协议自己可以可靠地传送认证。(译注:我以为可能做者想表达的是虽然OAuth是这些认证事件的消费者,但却不是生产者,因此不能由于使用了认证,就等同于OAuth能够直接提供认证。)跨域

事实证实尽管如此,还有一些事情能够和OAuth一块儿使用,以便在受权和受权协议之上建立身份认证协议。几乎在全部的这些状况下,OAuth的核心功能都将保持不变,而发生的事件是用户将他们的身份委派给他们正在尝试登陆的应用程序。而后,客户端应用程序成为身份API的消费者,从而找出先前受权给客户端的用户。以这种方式创建身份验证的一个主要好处是容许管理最终用户的赞成,这在互联网规模的跨域身份联合中是很是重要的。另外一个重要的好处是,用户能够同时将访问其余受保护的API委托给他们的身份,使应用程序开发人员和最终用户管理更简单。经过一个调用,应用程序能够找出用户是否登陆,应该调用什么用户,下载照片进行打印,并将更新发布到其消息流。这种简单性是很是有吸引力的,但当这两件事情同时进行时,许多开发人员将这两个功能混为一谈。安全

认证(Authentication) VS 受权(Authorization) : 一个比喻

为了帮助弄清楚这件事情,能够经过一个比喻来思考这个问题:巧克力 VS 软糖。在一开始,这两件事情的本质是大相径庭的:巧克力是一种原料,软糖就是糖果。巧克力能够用来作许多不一样的事情,甚至能够本身使用。软糖能够由许多不一样的东西制成,其中一种多是巧克力,可是须要多种成分来制造软糖,甚至不会用到巧克力。所以,巧克力等于软糖是错误的,而巧克力等于巧克力软糖确定是夸大其词的。服务器

在这个比喻中,OAuth是巧克力。这是一个多功能的原料,对许多不一样的东西是相当重要的,甚至能够本身使用。认证更像是软糖,至少有一些成分必须以正确的方式聚集在一块儿​​,使其成为可能,OAuth也许是这些成分之一(多是主要原料),但可能也根本不须要参与其中。你须要一个配方来讲明说明如何组合它们。网络

事实上,有一些众所周知的配方能够与特定的供应商进行合做,好比Facebook Connect、使用Twitter登陆以及OpenID Connect(为Google的登陆系统提供了支持)。这些配方每一个都添加了一些项目到OAuth中以建立身份认证协议,好比通用的profile API。能够在没有OAuth的状况下构建身份验证协议吗?固然能够,就像有不少种非巧克力软糖同样。可是咱们今天在这里谈论的是专门针对基于OAuth2的身份认证,以及可能出现什么问题,以及如何确保安全和美味。ide

使用OAuth进行认证的常见误区

即便使用OAuth来构建身份验证协议是很是有可能的,可是在身份提供者或者身份消费者方面,有许多事情可能会让这些人脱节。本文中描述的作法旨在通知身份提供商的潜在的常见风险,并向消费者通报在使用基于OAuth的身份认证系统时可避免的常见错误。

Access Token做为身份认证的证实

因为身份认证一般发生在颁发access token的以前, 所以使用access token做为身份认证的证实是很是诱人的。然而, 仅仅拥有一个access token并无告诉Client任何东西。在OAuth 中, token被设计为对Client不透明(译注:上一篇[认证受权] 2.OAuth2受权(续) & JSON Web Token中有介绍), 但在用户身份认证的上下文环境中, Client须要可以从token中派生一些信息。

此问题的根源在于Client不是OAuth access token的预期受众。相反, 它是该token的受权提出者, 而受众其实是受保护的资源。受保护的资源一般不可以仅经过token的单独存在来判断用户是否存在, 由于 oauth 协议的性质和设计, 在客户端和受保护资源之间的链接上用户是不可用的。为了应对这一点, 须要有一个针对客户自己的假象,这能够经过定义一个双重目的(dual-purposing)的Client能够解析和理解的access token来完成。可是因为通常的OAuth没有为access token自己定义特定的格式货结构,所以诸如OpenId Connect的ID Token和Facebook Connect的Signed在响应中提供一个次要的标记,它将和access token一块儿发送给Client中。这可使得Client对主要的access token保持不透明,就像常规的OAuth中的那样。

访问受保护的API做为身份认证的证实

因为access token能够用于获取一组用户属性,所以拥有一个有效的access token做为身份认证的证实也是很诱人的。在一些状况下,这种假设是成立的,由于在受权服务器商通过身份认证的用户上下文中,token是刚刚被建立的。可是在OAuth中,这并非获取access token的惟一方法,Refresh Token和assertions(Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants:https://tools.ietf.org/html/rfc7521)能够在用户不存在的状况下获取access token。而在某些状况下,用户无需身份验证便可得到access token(译注:好比[认证受权] 1.OAuth2受权 - 5.4 Client Credentials Grant)。

此外,在用户不存在后,access token一般还会存在很长时间。记住,OAuth是一个受权协议(delegation protocol),这对它的设计相当重要。这意味着,若是一个Client想要确保身份认证是有效的,那么简单的使用token获取用户属性是不够的,由于OAuth保护的是资源,获取用户属性的API(identity API)一般没有办法告诉你用户是否存在。

注入Access Token

另一个额外的威胁(很是危险)是当Client接受来自token endpoint的token时。这可能会发生在使用implicit流程(这个流程中直接把acces token做为url的hash参数(译注:[认证受权] 1.OAuth2 受权 - 5.2.2 Access Token Response))中,而且Client不正确的使用state参数的时候。若是应用程序在不一样的组件中传递 access token以“共享”访问权限的时候,也会发生此问题。这里的问题在于它开辟了一个注入access token到应用程序外部(并可能在应用程序外部泄露)的地方。若是Client不经过某种机制验证access token,则它没法区分access token是有效的令牌仍是攻击的令牌。

能够经过使用Authorization code来缓解这一点,而且只能经过受权服务器的token API(token endpoint)并使用一个state的值来避免被攻击者猜中。

缺少受众限制

另一个问题是,经过access token获取一组用户属性的OAuth API一般没有为返回的信息的受众作任何限制。换句话话说,极可能有一个幼稚的(naive)Client,从其余的Client拿到一个有效的token来做为本身的登陆事件。毕竟令牌是有效的,对API的访问也会返回有效的用户信息。问题在于没有用户作任何事情来证实用户存在,在这种状况下,用户甚至都没有受权给幼稚的(naive)Client。

经过将Client的认证信息与Client能够识别和验证的标识符一块儿传递给Client,能够缓解此问题,从而容许客户端区分自身的身份认证与另外一应用程序的身份认证。经过在OAuth的过程当中直接向Client传递一组身份认证信息,而不是经过受OAuth保护的API这样的辅助机制来缓解它,从而防止Client在稍后的过程当中注入未知来源的不可信的信息。

注入无效的用户信息

若是攻击者可以拦截或者替换来自Client的一个调用,它可能会改变返回的用户信息,而客户端却没法感知这一状况。这将容许攻击者经过简单地在正确的调用序列中交换用户标识符来模拟一个幼稚的(naive)Client上的用户。经过在身份认证协议过程当中(好比跟随OAuth的Token的颁发过程)直接从身份提供程序中获取身份认证信息,并经过可校验的签名保护身份认证信息,能够缓解这一点问题。

每一个潜在的身份提供商的不一样协议

基于OAuth 身份(identity)API的最大问题在于,即便使用彻底符合OAuth的机制,不一样的提供程序不可避免的会使用不一样的方式实现身份(identity)API。好比,在一个提供程序中,用户标识符多是用user_id字段来表示的,但在另外的提供程序中则是用subject字段来表示的。即便这些语义是等效的,也须要两份代码来处理。换句话说,虽然发生在每一个提供程序中的受权是相同的,可是身份认证信息的传输多是不一样的。此问题能够在OAuth之上构建标准的身份认证协议来缓解,这样不管身份认证信息来自何处,均可以用通用的方式传输。

这个问题之因此出现,是由于此处讨论的身份认证的机制被明确的排除在OAuth的范围以内。OAuth定义了一个没有特定格式的token(no specific token format),定义了一个没有通用的范围(no common set of scopes)的access token,而且没有解决受保护资源如何验证access token

基于OAuth的用户认证的标准:OpenId Connect

OpenID Connect是2014年初发布的开放标准,定义了一种基于OAuth2的可互操做的方式来来提供用户身份认证。实际上,它是众所周知的巧克力软糖的配方,已经被多数的专家们尝试和测试了。应用程序没必要为每一个潜在的身份提供程序构建不一样的协议,而是能够将一个协议提供给多个提供程序。因为OpenId Connect是一个开放标准,因此能够自由的没有任何限制的和知识产权问题的来实现。

OpenId Connect是直接创建在OAuth2之上的,在大多数状况下,部署在一个基于OAuth的基础设施之上。它还使用JOSN签名和加密规范,用来在传递携带签名和加密的信息。OpenId Connect避免了上面讨论的不少误区。

ID Tokens

OpenID Connect Id Token是一个签名的JSON Web Token(JWT:RFC7519),它和OAuth access token一块儿提供给Client应用程序。Id Token包含一组关于身份认证会话的声明(claim),包括用户的标识(sub)、颁发令牌的提供程序的标识符(iss)、以及建立此标识的Client的标识符(aud)。此外,Id Token还包含token的有效生存期(一般很是短)以及其余相关的上下文信息。因为Client知道Id Token的格式,所以它能直接分析出token的内容而无需依赖外部服务。此外,OpenId Connect还颁发access token给Client,容许Client保持对token的不透明,由于这是属于OAuth规范的一部分。最后,token自己是由提供程序的私钥进行签名的,除了在获取token中受TLS的保护以外,还添加了一个额外的保护层,以防止相似的模拟攻击。经过对此token的一些校验检查,Client能够保护本身免受大量常见的攻击。

因为Id token是受权服务器签名的,它还提供了在authorization code(c_hash)和access token(at_hash)上添加分离签名的位置,这些hash能够由Client来验证,同时仍保留authorization code和access token对Client不透明的语义,从而防止这一类的注入攻击。

应该指出的是,Client再也不须要使用access token,由于Id token已经包含了处理身份认证所需的全部信息。然而,为了保持和OAuth的兼容性,OpenId Connect会同时提供Id token和acces token。

UserInfo Endpoint

除了Id token包含的信息以外,还定义了一个包含当前用户信息的标准的受保护的资源。如上所述,这些信息不是身份认证的一部分,而是提供附加的标识信息。好比说应用程序提示说“早上好:Jane Doe”,总比说“早上好:9XE3-JI34-00132A”要友好的多。它提供了一组标准化的属性:好比profile、email、phone和address。OpenId Connect定义了一个特殊的openid scope,能够经过access token来开启Id token的颁发以及对UserInfo Endpoint的访问。它能够和其余scope一块儿使用而不发生冲突。这容许OpenId Connect和OAuth平滑的共存。

动态服务发现以及客户端注册

OAuth2为了容许各类不一样的部署而编写,可是这样的设计并无指定这些部署如何设置以及组件之间如何互相了解,在OAuth本身的世界中这是没问题的。在使用OpenId Connect时,一个通用的受保护的API部署在各类各样的Client和提供者中,全部这些都须要彼此互相了解才能运行。对于每一个Client来讲,不可能事先了解有关每一个提供程序,而且要求每一个提供者了解每一个潜在的Client,这将大大削弱扩展性。

为了抵消这种状况,OpenId Connect定义了一个发现协议,它容许Client轻松的获取有关如何和特定的身份认证提供者进行交互的信息。在另外一方面,还定义了一个Client注册协议,容许Client引入新的身份提供程序(identity providers)。经过这两种机制和一个通用的身份API,OpenId Connect能够运行在互联网规模上运行良好,在那里没有任何一方事先知道对方的存在。

兼容OAuth2

即便拥有这些强大的身份认证功能,OpenId Connect(经过设计)仍然与纯粹的OAuth2兼容,使其能够在开发人员花费最小代价的状况下部署在在OAuth系统之上。实际上,若是服务已经使用了OAuth和JOSE规范(以及JWT),该服务以及能够很好的支持OpenId Connect了。

译注 & 原文

原文成文应该时比较早,一些信息已通过时了,我作了部分的删减,如今OpenId Connect已经成为了一个很是庞大的协议族了,有不少相关的辅助协议来完善认证受权的相关需求。OpenId Connect具体的信息参见这里:http://openid.net/connect/。本人翻译水平通常,若有错误之处,欢迎指正!

原做者:Justin Richer 。文章地址: https://oauth.net/articles/authentication/。

备注:原文标题是“User Authentication with OAuth 2.0”,以为有点不妥,原本不少人对于AuthenticationAuthorization的认知就有一些混淆,而OAuth2是一个Authorization协议,而不是Authentication的协议,故而在翻译的时候调整了原文的名称。同时提了一个Pull Request(https://github.com/aaronpk/oauth.net/pull/154),不知道会不会被接受。

相关文章
相关标签/搜索