首页 > 解决方案 > 如何让 HWI OAuth Bundle 在反向代理后面的容器化应用程序中表现良好?

问题描述

语境

多年来,我一直在 Symfony 3.x 中运行 Intranet 管理面板。用户使用 google oauth 登录,系统会检查电子邮件是否与查找列表中经过验证的电子邮件匹配。oauth 客户端处理是通过“HWI OAuth Bundle”完成的。

为了以一种干净的方式将此管理面板迁移到 SF4 以及后来的 SF5,我们开始将我们的单体分解为在 docker 中运行的微服务。

移动到反向代理后面的 docker

今天我们将这个管理面板移动到一个 docker 中。然后我们让公众对运行管理面板的 docker 进行apache2操作。ProxyPass假设 docker 运行在http://1.2.3.4:7540我们假设公共地址是https://admin-europe.example.com

发生的情况是 symfony 应用程序有一个相对 URL,就像在 中google_login配置的路由routing.yml和在 中定义的服务配置中一样security.yml

路由:

# Required by the HWI OAuth Bundle.
hwi_oauth_redirect:
    resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
    prefix:   /connect

hwi_oauth_connect:
    resource: "@HWIOAuthBundle/Resources/config/routing/connect.xml"
    prefix:   /connect

hwi_oauth_login:
    resource: "@HWIOAuthBundle/Resources/config/routing/login.xml"
    prefix:   /login

# HWI OAuth Bundle route needed for each resource provider.
google_login:
    path: /login/check-google

logout:
    path: /logout

安全:

firewalls:
    # disables authentication for assets and the profiler, adapt it according to your needs
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false

    secured_area:
        anonymous: true
        logout:
            path:   /logout
            target: /
            handlers: [ admin.security.logout.handler ]
        oauth:
            resource_owners:
                google:        "/login/check-google"
            login_path:        /
            use_forward:       false
            failure_path:      /

            oauth_user_provider:
                service: admin.user.provider

因此,当应用程序未 dockerized 时,它可以正常运行,因为请求成为 google 的“重定向路由”的路由是https://admin-europe.example.com/login/check-google.

尽管如此,现在它在 docker 内部,当 HWI 包构建数据块以发送给 google 时,它​​请求http://1.2.3.4:7540/login/check-google将其授权为“重定向 URI”,但它当然不应该。当然,重定向 URI 应该继续是https://admin-europe.example.com/login/check-google.

我自然会收到此错误消息:

谷歌错误信息

反向代理

我们已经在反向代理中拥有了ProxyPassReverse,事实上,相同的配置已经在我们已经成功迁移的另一个微服务上运行了一个多月(但该服务不需要身份验证,是一个公共站点)。

这是很自然的,就像ProxyPassReverse处理 http 数据一样,但 google-oauth 信息块不由 处理ProxyPassReverse,因为它很自然。

问题

这里的问题是验证此地址(将域别名放入私有 IP 地址等)

这里的问题是如何从 docker 内部生成“正确的公共 URL”,而不会在将要运行的环境的功能中为容器内容创建硬依赖。这样做将是一种反模式。

探索解决方案

当然,“简单”的解决方案是对容器内的“外部路由”进行“硬编码”。

但这有一个缺陷。如果我还希望从相同的 docker 访问,比如说,https://admin-asia.example.com/(注意-asia而不是-europe),我会遇到问题,因为亚洲用户将被重定向到欧洲路线。这只是一个例子,不要关心具体的欧亚事情......关键是容器应该意识到周围的架构。或者至少,有意识地“交互”,但绝对不要在容器内“硬编码”依赖于环境的东西。

即:忘记-europe 和-asia 的事情。想象一下访问权限是 admin-1111。如果有一天我希望它可以作为 admin-2222 访问,我必须“重新编译”和“重新部署”容器是没有意义的。

临时解决方案

我认为将 中的路由rounting.yml和 中的配置都security.yml指向“参数”(在 3.x 中parameters.yml)然后在更新到 SF4 时将其移动到环境变量中可以解决问题,但我不确定symfony 的缓存编译器如何处理一个没有值的路由,但是一个“动态改变”的路由。

然后在容器启动时传递重定向的值。这只能部分解决问题:所有容器都将绑定到启动时设置的重定向路由,但仍然无法解决通过不同名称访问相同容器实例从而需要多个重定向路由的情况。相反,当运行非 dockerized 时,它只需要“主机名”来在相对路径定义上构建绝对路径。

调查至今

访问时,浏览器显示我要去

https://accounts.google.com/o/oauth2/auth
?response_type=code
&client_id=111111111111-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.apps.googleusercontent.com
&scope=email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.profile.emails.read+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.login
&redirect_uri=http%3A%2F%2Fmy.nice.domain.example.com%3A7040%2Fapp_dev.php%2Flogin%2Fcheck-google

在这里我们看到redirect_uri参数是我们将控件传递给谷歌后返回的地方。

所以需要有人来构建这个 URL。

我在源代码中寻找“redirect_uri”,发现所涉及的类是GoogleResourceOwnerwhich extends GenericOAuth2ResourceOwner

根据作为字符串传递的测试,这两个类似乎都属于$redirectUri,该字符串需要由调用者构建。

涉及的方法是public function getAuthorizationUrl($redirectUri, array $extraParameters = array())。这会接收重定向 URI 并使用编码为参数的重定向 URI 构建身份验证 URI。

那么,谁是消费者/客户getAuthorizationUrl()

我只在函数OAuthUtils中的一行中找到了一个客户端用法return $resourceOwner->getAuthorizationUrl($redirectUrl, $extraParameters);public function getAuthorizationUrl(Request $request, $name, $redirectUrl = null, array $extraParameters = array())

我看到这主要OAuthUtils是充当 SymfonyRequest和 OAuth 域模型之间的适配器。在这个方法中,我们主要找到创建 $redirectUri 的代码。

对我来说最干净的解决方案是创建一个OAuthUtilsBehindProxy继承自的子类OAuthUtils,覆盖该方法getAuthorizationUrl()并让它解释X-FORWARDED-*请求的标头,然后在使用 OAuthUtils 的任何地方使用依赖注入来自动装配我的类,希望没有人在做anew OAuthUtils并且此类的每个用户都将其传递给构造函数。

这将是干净的并且会起作用。

但坦率地说,这对我来说似乎有点矫枉过正。我很确定我之前的某个人已经在反向代理后面放置了一个需要使用 HWI 制作的 Google OAuth 的应用程序,我想知道是否有一个“配置选项”我丢失了,或者我真的必须重新编码所有这些并注入它通过 DI

所以,问

关于如何为 google-oauth 服务构建“重定向路由”,在反向代理后面的 docker 容器中运行时,如何让 HWI-OAuth 包正常运行?

有没有办法告诉 HWI 包或 symfony 在 X-FORWARDED-* 标头“如果可用”的功能中添加“完整主机”前缀?这将使 docker 映像“固定”并在“任何”环境中运行。

标签: dockerreverse-proxygoogle-oauthhwioauthbundle

解决方案


根本原因是 Symfony 从相对路径或路由名称生成完整地址的方式。

这是调查:

  • 该方法HWI/OAuthUtils::getAuthorizationUrl()是生成 OAUth auth URI 并使用该方法Symfony/HttpUtils::generateUri()来获取将在 Auth URI 中编码的 redirect_to 回调的绝对 URI。

  • 该方法Symfony/HttpUtils::generateUri()生成一个绝对 URI(在我们的例子中将是回调),为此,该方法处理 3 种一般情况:

    • 参数已经是一个绝对URI(返回的是参数不做进一步处理)
    • 参数是一个相对URL(该函数调用Request类来构建proto+host+port+project-path前缀来添加到相对URI)
    • 参数为路由名称(函数调用Router类构建绝对URI)

在我的示例中,我正在配置一个相对 URL ( google: "/login/check-google"),security.yml以便HttpUtils委托给Request类。

查看Request我们观察到的类的来源:

  • 该类Request能够使用代理标头来构建绝对类。
  • 但是为了安全起见,默认情况下 symfony 不会仅仅因为存在X-FORWARDED-*头文件就相信代理的存在。
  • 事实上,它更安全且更灵活。
  • 有 2 个安全级别:
    • 在某个地方,我们需要告诉Request班级哪些是访问应用程序的代理的受信任 IP 列表。
    • 在其他地方,我们需要告诉Request类哪些特定的代理标头是可信的,哪些标头不是,即使它支持不同的标准标头(RFC 标头、非 RFC Apache 标头等)

这里说明https://symfony.com/blog/fixing-the-trusted-proxies-configuration-for-symfony-3-3是需要通过调用静态方法在前端控制器中配置可信代理Request::setTrustedProxies();

因此,在前端控制器中添加这几行代码,一条是杀死不需要的标头,另一条是代理的 IP 范围,解决了这个问题:

# app.php
<?php

use Symfony\Component\HttpFoundation\Request;

$loader = require __DIR__.'/../app/autoload.php';
include_once __DIR__.'/../var/bootstrap.php.cache';

$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();

Request::setTrustedHeaderName( Request::HEADER_FORWARDED, null );   # <-- Kill unneeded header.
Request::setTrustedProxies( [ '192.168.104.0/24', '10.0.0.0/8' ] ); # <-- Trust any proxy that lives in any of those two private nets.

$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

通过此更改:

  1. 如果通过代理调用, SymfonyRequest能够从相对地址构建正确的公共绝对地址,方法是从HTTP_X_FORWARDED_HOSTandHTTP_X_FORWARDED_PORT而不是HTTP_HOSTand中扣除主机SERVER_PORT
  2. SymfonyHttpUtils也是,因为它被委派给Request.
  3. HWI 反过来能够建立正确的绝对回调redirect_to
  4. HWI 可以设置在 AuthUri 中编码的正确回调。
  5. 包含考虑到代理效果的正确绝对 URI 的 AuthURI 被发送到 google。
  6. Google 将“公共 URI”视为在 google 配置中注册的一个。
  7. 工作流程完成,登录过程可以成功结束。

推荐阅读