OAuth 2.0 构建客户端、受保护资源、授权服务器 介绍 2
一.客户端
OAuth 客户端和授权服务器需要相互了解才能通信,客户端必需有一个client_id(标识符 用字符串表示),client_id由授权服务器来创建管理,每个客户端标识符必须是唯一的。客户端程序除了client_id还需要一个client_secret(共享秘钥),用于与授权服务器交互时,对客户端自身的身份认证。
客户端除了上二项,还有其它一此配置选项。例如redirect_uri, 请求权限范围集合等,这些不由授权服务器分配。
另一方面,客户端需要知道自己在与哪个授权服务器交互,客户端需要指定端点(如授权端点、令牌端点)是一个http地址
客户端还需要指定授权许可类型,指定grant_type 参数,常见的包括:隐式许可类型、客户端凭据许可类型、授权码许可类型,password许可类型,其中授权码许可类型是核心的许可类型,是最复杂的一种,其它OAuth 授权许可类型都是对这一许可类型的优化。
客户端拿到了访问令牌,使用HTTP Authorization头部来传递令牌, Http头部以关键字Bearer开头,后跟一个空格,再跟令牌值本身,格式:Authorization:Bearer xxxxx 。http规范还规定了Authorization和Bearer这二个关键词不区分大小写,但令牌值本身是区分大小写的。
刷新令牌:是指OAuth 2.0提供了一种在无需用户参与的情况下获取新访问令牌的方法,因为用户在初次授权完成之后不会一直在场,当访问令牌过期,客户端访问受保护资源时会得到HTTP 401的状态码,可以通过刷新令牌重新获取访问令牌,一个设计良好的客户端应该避免访问令牌过期后去访问受保护的资源。
二.受保护的资源
一个简单的资源服务器,它可以供客户端调用,并由授权服务器保护。资源服务器需要做的就是解析客户端的访问令牌,验证令牌,并确定客户端能请求哪些资源。
资源服务器验证访问令牌有二种方法:1.请求授权服务器提供的令牌内省端点,让资源服务器能够在运行时检查令牌的状态,代价就是需要网络流量。2.资源服务器能够直接解析并理解访问令牌,jwt就是这样一种数据结构。
在很多API设计中,不同操作需要不同的访问权限,一些API会根据授权者不同而返回不同的结果,我们可以利用OAuth 的权限范围机制来对API资源进行权限控制,如下面C# webapi 资源服务,在访问Action方法中时,可以使用下面代码获取OAuth 的scope权限范围。
this.HttpContext.User.Claims.Where(t=>t.Type.ToString()=="scope").Select(r=>r.Value).First() "openid profile email"
添加一个写入权限做法:如在授权服务器中添加scope权限值write,在客户端请求访问令牌时,添加scope的write,此时令牌中会包含scope: openid profile email write。webapi 中最简单的权限判断就是在Action中获取scope,判断该客户端scope是否有write,如果有可以操作该action写入数据,不能返回http 403状态。
keycloak中的具体实现参考:keycloak API授权范围自定义
三.授权服务器
授权服务器是OAuth生态系统中最复杂的组件,它是整个OAuth系统中的安全权威中心。只有授权服务器能够对用户进行身份认证,注册客户端,颁发令牌。在OAuth 2.0的规范中,已经尽可能的将复杂性从客户端和受保护的资源转移至了授权服务器,这是由各组件数量决定的:客户端数量远大于受保护资源的数量,受保护资源的数量又远多于授权服务器的数量。
1.管理OAuth客户端
为了让客户端与OAuth服务器交互,OAuth服务器需要为每一个客户端分配唯一的客户端标识符。可以是代码中写死的静态注册客户端(每次添加新客户端需要重启授权服务器),也可以像keycloak产品一样实现了动态注册客户端。注册完成后将客户端ID和密钥颁发给客户端。
2.对客户端授权
OAuth协议要求授权服务器提供两个端点:授权端点,运行在前端信道上; 令牌端点,运行在后端信道上。
授权端点:用户在OAuth授权过程中的第一站是授权端点,在keycloak中端点名为authorization_endpoint,是基于openid connect的前端信道,当客户端将用户浏览器重定向到授权服务器的授权端点时,通常是一个get请求,如下所示:
https://xxxx.com/realms/master/protocol/openid-connect/auth ?client_id=xxxx-admin-console &redirect_uri=https://xxxx.com/admin/master/console/ &state=025b340c-77da-4333-8c0c-b4514cf971bd &response_mode=fragment &response_type=code &scope=openid &nonce=a1fe3233-0b56-4531-8b6f-74b95eedc551 &code_challenge=-rlXzA1s1lkdwXmBjoeB0DdbMXr3nZvvqzMdbSbgU-M &code_challenge_method=S256
客户端将用户浏览器重定向至该授权端点时,授权服务器会大概做如下动作:
1)检查客户端是否存在(client_id),如果不存在则向用户展示错误信息,以keycloak为例显示:Invalid parameter: redirect_uri
2)检查redirect_uri,是否与客户端注册信息一致, 如果不一致则向用户展示错误信息,以keycloak为例显示:Invalid parameter: redirect_uri,注册如下所示:
3)如果客户端通过检查,则授权服务器会渲染出一个登录页面来,用户需要与这个页面交互。登录成功并在同意页授权后(同意页可选配置的),由于是授权码许可类型,服务端会生成一个授权码并通过客户端重定向URL返回给客户端。
4)客户端通过后端信道向令牌端点发出POST请求(参数包括授权码,client_id,client_secret),不需要用户浏览器参与,以此获取令牌,注意授权码的值是仅供一次性使用的凭据。
其中state参数是客户端提供的,向授权服务器传递了state参数,这是客户端提供跨站保护防止CSRF 攻击。虽然不要求客户端传递该参数,但是要求授权服务器只要收到该参数就返回它。
3.增加授权范围的支持
OAuth 2.0中一个重要的机制就是权限范围,配置客户端时通过scope来指定,如"scope":"foo bar" 这个字段值是一个以空格分隔的字符串列表,每个字符串代表一个单独的oauth权限范围,这里值可以理解为,客户端可以访问受保护资源的foo 和bar 的二个接口。具体看文章上面第二大点的链接。
4.授权许可类型
在OAuth 1.0中,获取访问令牌的方式只有一种,所有客户端都必须采用这种方式,这种方式被设计的尽可能通用,以求适应各种不同的部署选项,但结果并不好适用各种场景。在OAuth 2.0中明确核心定位为一个框架而不是单个协议,支持多种不同的方式应用,最关键的一个变化就是授权许可,也叫授权流程。
1)隐式许可类型
隐式许可类型只能使用前端信道和授权服务器通信,是直接从授权端点返回令牌,完全运行在浏览器中的javascript应用就属于这种情况。隐式许可类型没有额外的保密层(不像授权码许可流程)安全性不如授权码许可流程,这种许可类型的客户端无法持有客户端秘钥,因为无法对浏览器隐藏秘钥,由于这种许可流程只使用授权端点而不使用令牌端点,因此这个限制不会影响其功能。另外该流程不可用于获取刷新令牌,因为浏览器内的应用具有短暂运行的特点,只会在被加载到浏览器的期间保持会话,所以刷新令牌在这里的作用非常有限,和其它许可类型不同,这种许可类型会假设用户一直在场,必要时可以对客户端重新授权。
客户端向授权服务器的授权端点发送请求时,使用方式与授权码流程相同,只不过response_type参数的值为token,而不是code,这样会通知授权服务器直接生成令牌,而不是生成一个用于换取令牌的授权码。由于是前端信道所以访问令牌值是通过url来传递的。
2)客户端凭据许可类型
如果没有明确的资源拥有者,或对于客户端软件来说资源拥有者不可区分,一种常见的场景,比如后端系统之间需要直接通信,它们并不一定代表某个特定用户,没有用户对客户端授权,这种就使用客户端凭据许可类型。它只使用后端信道,客户端代表自己(资源拥有者)从令牌端点获取令牌。
客户端向授权服务器的令牌端点发送请求时,grant_type参数的值为client_credentials。客户端也可以使用scope参数指定请求的权限范围,其用法与授权码和隐式许可流程中在授权端点上使用的scope参数一样。客户端凭据许可流程不会颁发刷新令牌,因为客户端能够随时获取新令牌,不需要单独的资源拥有者参与,因此这种情况下不需要刷新令牌。
3) 资源拥有者凭据许可类型
如果资源拥有者在授权服务器上有纯文本的用户名和密码,那么客户端通过后端信道使用用户名和密码获取令牌,也叫密码(password)流程,只使用令牌端点,并且只通过后端信道通信。
OAuth核心规范定义的这一许可类型是基于“询问秘钥” 反模式的,在一般情况下,不推荐使用资源拥有者凭据许可类型 ,因为客户端能够缓存凭据并且随意使用。这种许可类型只能作为过度方案,用于那些原本就直接索取用户名和密码但要转投OAuth方案的客户端,而且应该尽快将这样的客户端转到授权码许可流程上来。
这种许可类型的工作方式很简单,客户端收集用户的用户名和密码,然后将它们发送至授权服务器的令牌端点,授权服务器收到请求,并与本地存储的用户信息对比,如果匹配,则授权服务器向客户端颁发令牌。
4)授权码许可类型
授权码许可类型是最复杂的一种,所以其它OAuth许可类型都是对这一许可类型的优化,以适应特定的应用场景和环境。可参考上面3.2章节。