从0开始构建一个Oauth2Server服务 18
AccessToken
访问令牌是应用程序用来代表用户发出 API 请求的东西。访问令牌代表特定应用程序访问用户数据的特定部分的授权。
访问令牌不必是任何特定格式,尽管对不同的选项有不同的考虑,这将在本章后面讨论。就客户端应用程序而言,访问令牌是一个不透明的字符串,它会接受任何字符串并在 HTTP 请求中使用它。资源服务器需要了解访问令牌的含义以及如何验证它,但应用程序永远不会关心理解访问令牌的含义。
访问令牌在传输和存储过程中必须保密。唯一应该看到访问令牌的各方是应用程序本身、授权服务器和资源服务器。应用程序应确保同一设备上的其他应用程序无法访问访问令牌的存储。访问令牌只能通过 HTTPS 连接使用,因为通过非加密通道传递它会使第三方拦截变得微不足道。
令牌端点是应用程序发出请求以获取用户访问令牌的地方。本节介绍如何验证令牌请求以及如何返回适当的响应和错误。
授权码请求 Authorization Code Request
当应用程序为访问令牌交换授权代码时,将使用授权代码授予。用户通过重定向 URL 返回到应用程序后,应用程序将从该 URL 中获取授权代码并使用它来请求访问令牌。此请求将发送到令牌端点。
请求参数
访问令牌请求将包含以下参数。
grant_type
(必需的)
该grant_type
参数必须设置为“authorization_code”。
code
(必需的)
该参数是客户端之前从授权服务器收到的授权码。
redirect_uri
(可能需要)
如果重定向 URI 包含在初始授权请求中,则服务也必须在令牌请求中要求它。令牌请求中的重定向 URI 必须与生成授权代码时使用的重定向 URI 完全匹配。否则服务必须拒绝请求。
code_verifier
(需要 PKCE 支持)
如果客户端code_challenge
在初始授权请求中包含一个参数,它现在必须通过在 POST 请求中发送它来证明它具有用于生成哈希的秘密。这是用于计算先前在code_challenge
参数中发送的哈希值的明文字符串。
client_id
(如果没有其他客户端身份验证则需要)
如果客户端通过 HTTP Basic Auth 或其他方法进行身份验证,则不需要此参数。否则,此参数是必需的。
如果向客户端颁发了客户端机密,则服务器必须对客户端进行身份验证。验证客户端的一种方法是接受此请求中的另一个参数,client_secret
. 或者,授权服务器可以使用 HTTP Basic Auth。从技术上讲,该规范允许授权服务器支持任何形式的客户端身份验证,并提到公钥/私钥对作为一个选项。实际上,大多数消费者服务器都支持使用此处提到的一种或两种方法对客户端进行身份验证的更简单方法。有关验证客户端的更高级方法,请参阅 RFC 7523,它定义了使用签名的 JWT 作为客户端验证的方法。
验证授权码授予
在检查所有必需的参数并验证客户端(如果客户端已获得凭据)之后,授权服务器可以继续验证请求的其他部分。
服务器然后检查授权代码是否有效,并且没有过期。然后,该服务必须验证请求中提供的授权码是否已发给已识别的客户端。最后,服务必须确保存在的重定向 URI 参数与用于请求授权代码的重定向 URI 相匹配。
对于 PKCE 支持,授权服务器应计算此令牌请求中提供的 SHA256 哈希值code_verifier
,并将其与code_challenge
授权请求中提供的值进行比较。如果它们匹配,授权服务器就可以确信发出此令牌请求的客户端与发出原始授权请求的客户端相同。
如果一切正常,该服务可以生成访问令牌并做出响应。
安全注意事项
防止replay攻击
如果多次使用授权代码,授权服务器必须拒绝后续请求。如果授权代码存储在数据库中,这很容易实现,因为它们可以简单地标记为已使用。
如果您正在实施自编码授权代码,如我们的示例代码中所示,您将需要跟踪在令牌的生命周期内使用的令牌。实现此目的的一种方法是在代码的生命周期内将代码缓存在缓存中。这样在验证代码时,我们可以先通过检查代码的缓存来检查它们是否已经被使用过。一旦代码到了它的失效日期,它就不再在缓存中,但是我们仍然可以根据失效日期拒绝它。
如果多次使用代码,则应将其视为攻击。如果可能,该服务应撤销以前从该授权代码发出的访问令牌。
Password Grant 密码授权
当应用程序将用户的用户名和密码交换为访问令牌时,将使用密码授权。这正是 OAuth 创建时首先要防止的事情,因此您永远不应允许第三方应用程序使用此授权。
支持密码授权是非常有限的,因为无法向此流程添加多因素授权,并且您检测暴力攻击的选项更加有限。该流程不应在实践中使用。
最新的OAuth 2.0 Security Best Current Practice规范实际上建议不要完全使用密码授权,并且在 OAuth 2.1 更新中将其删除。
请求参数
访问令牌请求将包含以下参数。
grant_type
(required) – 该grant_type
参数必须设置为“password”。username
(必填)– 用户的用户名。password
(必需)– 用户密码。scope
(可选)– 应用程序请求的范围。- 客户端身份验证(如果客户端被授予机密则需要)
如果向客户端发出了一个秘密,则客户端必须对该请求进行身份验证。通常,该服务将允许附加请求参数client_id
和client_secret
,或者接受 HTTP 基本身份验证标头中的客户端 ID 和密码。
client-credentials 客户凭证
当应用程序请求访问令牌以访问其自己的资源而不是代表用户时,将使用客户端凭据授权。
请求参数
grant_type
(必需的)
该grant_type
参数必须设置为client_credentials
。
scope
(选修的)
您的服务可以支持客户端凭据授予的不同范围。实际上,实际上支持这一点的服务并不多。
客户端身份验证(必需)
客户端需要为此请求验证自己。通常,该服务将允许附加请求参数client_id
和client_secret
,或者接受 HTTP 基本身份验证标头中的客户端 ID 和密码。
例子
以下是服务将收到的授权代码示例。
POST /token HTTP/1.1
Host: authorization-server.com
grant_type=client_credentials
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx
access-token-response 访问令牌
成功响应
如果访问令牌请求有效,授权服务器需要生成一个访问令牌(和可选的刷新令牌)并将它们返回给客户端,通常连同一些关于授权的附加属性。
带有访问令牌的响应应包含以下属性:
access_token
(必需)授权服务器颁发的访问令牌字符串。token_type
(必需)这是令牌的类型,通常只是字符串“Bearer”。expires_in
(推荐)如果访问令牌过期,服务器应回复授予访问令牌的持续时间。refresh_token
(可选)如果访问令牌将过期,那么返回一个刷新令牌很有用,应用程序可以使用它来获取另一个访问令牌。但是,不能为使用隐式授权颁发的令牌颁发刷新令牌。scope
(可选)如果用户授予的范围与应用程序请求的范围相同,则此参数是可选的。如果授予的范围与请求的范围不同,例如用户修改了范围,则需要此参数。
当使用访问令牌响应时,服务器还必须包含额外的Cache-Control: no-store
HTTP 标头以确保客户端不会缓存此请求。
例如,成功的令牌响应可能如下所示:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"token_type":"Bearer",
"expires_in":3600,
"refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
"scope":"create"
}
访问令牌
OAuth 2.0 Bearer 令牌的格式实际上在单独的规范RFC 6750中进行了描述。规范要求的令牌没有定义的结构,因此您可以生成一个字符串并根据需要实现令牌。不记名令牌中的有效字符是字母数字和以下标点符号:
Bearer Tokens 的一个简单实现是生成一个随机字符串并将其与关联的用户和范围信息一起存储在数据库中,或者更高级的系统可以使用self-encoded tokens,其中令牌字符串本身包含所有必要的信息。
不成功的响应
如果访问令牌请求无效,例如重定向 URL 与授权期间使用的不匹配,则服务器需要返回错误响应。
错误响应返回一个 HTTP 400 状态代码(除非另有说明),带有error
和error_description
参数。该error
参数将始终是下面列出的值之一。
invalid_request
– 请求缺少参数,因此服务器无法继续请求。如果请求包含不受支持的参数或重复参数,也可能会返回此信息。invalid_client
– 客户端身份验证失败,例如请求包含无效的客户端 ID 或密码。在这种情况下发送 HTTP 401 响应。invalid_grant
– 授权代码(或密码授予类型的用户密码)无效或已过期。如果授权授予中提供的重定向 URL 与此访问令牌请求中提供的 URL 不匹配,这也是您将返回的错误。invalid_scope
– 对于包含范围(密码或 client_credentials 授权)的访问令牌请求,此错误表示请求中的范围值无效。unauthorized_client
– 此客户端未被授权使用请求的授权类型。例如,如果您限制哪些应用程序可以使用隐式授权,您将为其他应用程序返回此错误。unsupported_grant_type
– 如果请求授权服务器无法识别的授权类型,请使用此代码。请注意,未知授权类型也使用此特定错误代码,而不是使用invalid_request
上述代码。
返回错误响应时有两个可选参数,error_description
和error_uri
. 这些旨在为开发人员提供有关错误的更多信息,而不是为了向最终用户显示。但是,请记住,无论您如何警告他们,许多开发人员都会将此错误文本直接传递给最终用户,因此最好确保它至少对最终用户也有一定帮助。
参数error_description
只能是ASCII字符,最多只能是一两句话描述错误的情况。这error_uri
是链接到您的 API 文档以获取有关如何更正遇到的特定错误的信息的好地方。
整个错误响应以 JSON 字符串形式返回,类似于成功响应。下面是错误响应的示例。
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
{
"error": "invalid_request",
"error_description": "Request was missing the 'redirect_uri' parameter.",
"error_uri": "See the full API docs at https://authorization-server.com/docs/access_token"
}