Vapor OAuth 是一个为 Vapor 编写的 OAuth2 提供程序库。您可以将此库集成到您的服务器中,以便为应用程序提供授权,使其能够连接到您的 API。
它遵循 RFC 6749 和 RFC6750,并且有一个广泛的测试套件,以确保它符合规范。
它还实现了 RFC 7662 规范,用于令牌自省 (Token Introspection),这对于具有共享、中央授权服务器的微服务非常有用。
Vapor OAuth 支持标准授权类型:
有关标准 OAuth 流程如何工作以及使用和实现它们时应注意什么的精彩描述,请参阅 https://www.oauth.com。
可以使用简单的提供程序将 Vapor OAuth 添加到您的 Vapor 项目中。首先,将该库添加到您的 Package.swift
依赖项中。
dependencies: [
...,
.package(url: "https://github.com/brokenhandsio/vapor-oauth", from: "0.6.0"))
]
接下来,将其导入到您设置 Droplet
的位置。
import VaporOAuth
然后将提供程序添加到您的 Config
中。
try addProvider(VaporOAuth.Provider(codeManager: MyCodeManager(), tokenManager: MyTokenManager(), clientRetriever: MyClientRetriever(), authorizeHandler: MyAuthHandler(), userManager: MyUserManager(), validScopes: ["view_profile", "edit_profile"], resourceServerRetriever: MyResourceServerRetriever()))
要集成该库,您需要设置许多东西,这些东西实现了所需的各种协议。
CodeManager
- 这负责生成和管理 OAuth 代码。它仅在授权码流程中是必需的,因此如果您不想支持此授权类型,则可以省略此参数并使用默认实现。TokenManager
- 这负责生成和管理访问令牌和刷新令牌。您可以将它们存储在内存中、Fluent 中或任何后端中。ClientRetriever
- 这负责获取您想在您的应用程序中支持的所有客户端。如果您希望能够动态添加客户端,那么您需要确保可以通过您的实现来做到这一点。如果您只想支持一组客户端,则可以使用为您提供的 StaticClientRetriever
。AuthorizeHandler
- 这负责允许用户允许/拒绝授权请求。有关更多详细信息,请参见下文。如果您不想支持此授权类型,则可以排除此参数并使用默认实现。UserManager
- 这负责验证用户身份并获取密码凭据流程的用户。如果您不想支持此流程,则可以排除此参数并使用默认实现。validScopes
- 这是您希望在系统中支持的可选 scopes 数组。ResourceServerRetriever
- 只有在使用令牌自省端点时才需要此项,并且它用于验证尝试访问该端点的资源服务器的身份。请注意,在 Vapor OAuth Fluent 包中,对于 Fluent 的不同必需协议,有许多默认实现。
然后,该提供程序将在 /oauth/authorize
和 /oauth/token
注册用于授权和令牌的端点。
Vapor OAuth 在 Request
上有一个辅助扩展,允许您轻松保护您的 API 路由。例如,假设您要确保只有具有 profile
scope 的令牌才能访问一个路由,您可以这样做:
try request.oauth.assertScopes(["profile"])
如果令牌无效或不包含 profile
scope,这将抛出一个 401 错误。这是如此常见,以至于有一个专用的 OAuth2ScopeMiddleware
来处理此行为。您只需使用该 protect
组必须要求的 scopes 数组对其进行初始化。如果您使用 nil
数组对其进行初始化,那么它将只确保令牌有效。
您还可以使用 try request.oauth.user()
获取用户。
如果您有资源服务器与 OAuth 服务器不是同一个服务器,并且希望使用令牌自省端点进行保护,则情况略有不同。有关更多信息,请参见 令牌自省 部分。
授权码流程是 OAuth 中最常用的流程。大多数 Web 应用程序将使用它来与 OAuth 资源服务器进行授权。此授权类型的基本概要是:
除了实施 Code Manager、Token Manager 和 Client Retriever 之外,最重要的部分是实施 AuthorizeHandler
。您的授权处理程序负责让用户决定是否应允许应用程序访问他们的帐户。它应该是 清晰易懂的,并且应清楚该应用程序正在请求访问的内容。
您有责任确保用户已登录并处理未登录的情况。授权处理程序的示例实现可能如下所示:
func handleAuthorizationRequest(_ request: Request, authorizationGetRequestObject: AuthorizationGetRequestObject) throws -> ResponseRepresentable {
guard request.auth.isAuthenticated(FluentOAuthUser.self) else {
let redirectCookie = Cookie(name: "OAuthRedirect", value: request.uri.description)
let response = Response(redirect: "/login")
response.cookies.insert(redirectCookie)
return response
}
var parameters = Node([:], in: nil)
let client = clientRetriever.getClient(clientID: authorizationGetRequestObject.clientID)
try parameters.set("csrf_token", authorizationGetRequestObject.csrfToken)
try parameters.set("scopes", authorizationGetRequestObject.scopes)
try parameters.set("client_name", client.clientName)
try parameters.set("client_image", client.clientImage)
try parameters.set("user", request.auth.user)
return try view.make("authorizeApplication", parameters)
}
您需要将 SessionsMiddleware
添加到您的应用程序,才能完成此流程,以便 CSRF 保护能够正常工作。
将授权表单提交回 Vapor OAuth 时,表单数据必须包括:
applicationAuthorized
- 一个布尔值,表示用户是否允许访问客户端。csrfToken
- 处理程序中提供的 CSRF 令牌,以防止 CSRF 攻击。隐式授权几乎与授权码流程相同,除了您不会被重定向回一个代码,然后用该代码交换令牌,而是使用片段中的令牌重定向回您。然后,由客户端 (例如 iOS 应用程序) 从重定向 URI 片段中解析出令牌。
此流程是为无法保证客户端密钥安全性的客户端 (客户端应用程序) 设计的,但最近已经不受欢迎,通常建议使用没有客户端密钥的授权码流程。
密码凭据流程应仅用于第一方应用程序,Vapor OAuth 强制执行此操作。此流程允许客户端收集用户的用户名和密码,并将其直接提交到 OAuth 服务器以获取令牌。
请注意,如果您正在使用密码流程,请按照 规范,您必须使用速率限制或生成警报来保护您的端点免受暴力攻击。该库将为任何未经授权的尝试向控制台输出警告消息,您可以将其用于此目的。该消息的格式为 LOGIN WARNING: Invalid login attempt for user <USERNAME>
。
客户端凭据是一种无用户流程,专为服务器访问其他服务器而无需用户而设计。访问权限是根据请求访问的客户端的身份验证授予的。
如果运行微服务架构,最好有一个处理授权的服务器,所有其他资源服务器都查询该服务器。为此,您可以使用令牌自省端点扩展。在 Vapor OAuth 中,这会添加一个端点,您可以在 /oauth/token_info
处发布令牌。
您可以向此端点发送一个 POST 请求,其中包含一个参数 token
,其中包含您要检查的 OAuth 令牌。如果它有效且处于活动状态,那么它将返回一个 JSON 有效负载,如下所示:
{
"active": true,
"client_id": "ABDED0123456",
"scope": "email profile",
"exp": 1503445858,
"user_id": "12345678",
"username": "hansolo",
"email_address": "hansolo@therebelalliance.com"
}
如果令牌已过期或不存在,那么它将只返回:
{
"active": false
}
此端点受到 HTTP 基本身份验证的保护,因此您需要使用请求发送 Authorization: Basic abc
标头。这将检查 ResourceServerRetriever
中发送的用户名和密码。
注意:根据 规范 - 令牌自省端点必须受到 HTTPS 的保护 - 这意味着服务器必须位于 TLS 证书 (通常称为 SSL) 之后。 Vapor OAuth 将此留给集成库来实现。
要使用令牌自省端点通过 OAuth 保护其他服务器上的资源,您要么需要在您要保护的路由上使用 OAuth2TokenIntrospectionMiddleware
,要么需要手动设置 Helper
对象 (中间件会为您执行此操作)。中间件和 helper 设置都需要:
tokenIntrospectionEndpoint
- 可以验证令牌的端点。client
- Droplet
的客户端,用于发送令牌验证请求。resourceServerUsername
- 资源服务器的用户名。resourceServerPassword
- 资源服务器的密码。设置好这些之后,您就可以像往常一样调用 request.oauth.user()
或 request.oauth.assertScopes()
。