Vapor OAuth

Language Test Status Code Coverage MIT License

Vapor OAuth 是一个为 Vapor 编写的 OAuth2 提供程序库。您可以将此库集成到您的服务器中,以便为应用程序提供授权,使其能够连接到您的 API。

它遵循 RFC 6749RFC6750,并且有一个广泛的测试套件,以确保它符合规范。

它还实现了 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()))

要集成该库,您需要设置许多东西,这些东西实现了所需的各种协议。

请注意,在 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 资源服务器进行授权。此授权类型的基本概要是:

  1. 客户端 (另一个应用程序) 将资源所有者 (拥有您的信息的用户) 重定向到您的 Vapor 应用程序。
  2. 然后,您的 Vapor 应用程序验证用户身份,并询问用户是否要允许客户端访问所请求的 scopes (想想使用您的 Facebook 帐户登录某些内容 - 这就是此方法)。
  3. 如果用户批准该应用程序,则 OAuth 服务器会将 OAuth 代码 (通常有效期约为 60 秒) 重定向回客户端。
  4. 然后,客户端可以交换该代码以获得访问令牌和刷新令牌。
  5. 客户端可以使用访问令牌向资源服务器 (OAuth 服务器或您的 Web 应用程序) 发出请求。

实施细节

除了实施 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 时,表单数据必须包括:

隐式授权 (Implicit Grant)

隐式授权几乎与授权码流程相同,除了您不会被重定向回一个代码,然后用该代码交换令牌,而是使用片段中的令牌重定向回您。然后,由客户端 (例如 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 设置都需要:

设置好这些之后,您就可以像往常一样调用 request.oauth.user()request.oauth.assertScopes()