itk-dev/openid-connect-bundle

用于 OpenID Connect 的 Symfony 扩展包

安装次数: 23,431

依赖项: 0

建议者: 0

安全性: 0

星标: 1

关注者: 4

分支: 0

公开问题: 0

类型:symfony-bundle

3.1.0 2023-08-03 12:08 UTC

This package is auto-updated.

Last update: 2024-09-03 15:26:25 UTC


README

Github Release PHP Version Build Status Codecov Code Coverage Read License Package downloads on Packagist

通过 OpenID Connect 进行授权的 Symfony 扩展包。

安装

要安装,请运行

composer require itk-dev/openid-connect-bundle

使用方法

在使用此扩展包之前,您必须拥有自己的用户实体和数据设置。

一旦拥有这些,您需要做以下操作

  • 配置 OpenId Connect 变量
  • 创建一个扩展扩展包验证器的认证器类,OpenIdLoginAuthenticator
  • 配置 LoginTokenAuthenticator 以使用 CLI 登录。

变量配置

/config/packages/ 中,您需要以下 itkdev_openid_connect.yaml 文件来配置 OpenId Connect 变量

itkdev_openid_connect:
  cache_options:
    cache_pool: 'cache.app' # Cache item pool for caching discovery document and CLI login tokens
  cli_login_options:
    route: '%env(string:OIDC_CLI_LOGIN_ROUTE)%' # Redirect route for CLI login
  user_provider: ~ #
  openid_providers:
    # Define one or more providers
    # [providerKey]:
    #   options:
    #     metadata_url: …
    #
    admin:
      options:
        metadata_url: '%env(string:ADMIN_OIDC_METADATA_URL)%'
        client_id: '%env(string:ADMIN_OIDC_CLIENT_ID)%'
        client_secret: '%env(string:ADMIN_OIDC_CLIENT_SECRET)%'
        # Specify redirect URI
        redirect_uri: '%env(string:ADMIN_OIDC_REDIRECT_URI)%'
        # Optional: Specify leeway (seconds) to account for clock skew between provider and hosting
        #           Defaults to 10
        leeway: '%env(int:ADMIN_OIDC_LEEWAY)%'
        # Optional: Allow http requests (used for mocking a IdP)
        #           Defaults to false
        allow_http: '%env(bool:ADMIN_OIDC_ALLOW_HTTP)%'
    user:
      options:
        metadata_url: '%env(string:USER_OIDC_METADATA_URL)%'
        client_id: '%env(string:USER_OIDC_CLIENT_ID)%'
        client_secret: '%env(string:USER_OIDC_CLIENT_SECRET)%'
        # As an alternative to using (a more or less) hardcoded redirect uri,
        # a Symfony route can be used as redirect URI
        redirect_route: 'default'
        # Define any params for the redirect_route
        # redirect_route_parameters: { type: user }

使用以下 .env 环境变量

###> itk-dev/openid-connect-bundle ###
# "admin" open id connect configuration variables (values provided by the OIDC IdP)
ADMIN_OIDC_METADATA_URL=ADMIN_APP_METADATA_URL
ADMIN_OIDC_CLIENT_ID=ADMIN_APP_CLIENT_ID
ADMIN_OIDC_CLIENT_SECRET=ADMIN_APP_CLIENT_SECRET
ADMIN_OIDC_REDIRECT_URI=ADMIN_APP_REDIRECT_URI
ADMIN_OIDC_LEEWAY=30
ADMIN_OIDC_ALLOW_HTTP=true

# "user" open id connect configuration variables
USER_OIDC_METADATA_URL=USER_APP_METADATA_URL
USER_OIDC_CLIENT_ID=USER_APP_CLIENT_ID
USER_OIDC_CLIENT_SECRET=USER_APP_CLIENT_SECRET

# cli redirect url 
OIDC_CLI_LOGIN_ROUTE=OIDC_CLI_LOGIN_ROUTE
###< itk-dev/openid-connect-bundle ###

将实际的值设置到您的 env.local 文件中,以确保它们不会被提交到 Git。

/config/routes/ 中,您需要类似的 itkdev_openid_connect.yaml 文件来配置路由

itkdev_openid_connect:
  resource: "@ItkDevOpenIdConnectBundle/src/Resources/config/routes.yaml"
  prefix: "/openidconnect" # Prefix for bundle routes

无需为扩展包路由添加前缀,但如果您想添加另一个 /login 路由,则可以更轻松地区分它们。

在调用登录控制器动作(路由 itkdev_openid_connect_login)时,必须在 provider 参数中设置提供者的密钥,例如

  <a href="{{ path('itkdev_openid_connect_login', {provider: 'user'}) }}">{{ 'Sign in'|trans }}</a>
  $router->generate('itkdev_openid_connect_login', ['provider => 'user']);

请确保允许登录控制器路由的匿名访问,即类似以下内容

# config/packages/security.yaml
security:
  #
  access_control:
    #
    - { path: ^/openidconnect/login(/.+)?$, role: IS_AUTHENTICATED_ANONYMOUSLY }

CLI 登录

为了使用 CLI 登录功能,必须设置以下环境变量,以便 Symfony 能够在命令中生成 URL

DEFAULT_URI=

有关更多信息,请参阅 Symfony 文档:在命令中生成 URL

您还必须在 security.yaml 文件中添加扩展包的 CliLoginTokenAuthenticator

security:
  firewalls:
    main:
      custom_authenticators:
        - ItkDev\OpenIdConnectBundle\Security\CliLoginTokenAuthenticator

最后,配置用于登录链接的 Symfony 路由:cli_login_options: route。如果您有多个针对不同 URL 模式激活的防火墙,请确保您将 LoginTokenAuthenticator 添加到此处指定的路由的激活防火墙。

创建认证器

该扩展包可以帮助您获取授权者收到的声明 - 需要实现的唯一函数是 authenticate()onAuthenticationSuccess()start()

<?php

namespace App\Security;

use ItkDev\OpenIdConnect\Exception\ItkOpenIdConnectException;
use ItkDev\OpenIdConnectBundle\Security\OpenIdLoginAuthenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;

class SomeAuthenticator extends OpenIdLoginAuthenticator
{

    public function authenticate(Request $request): Passport
    {
        // Get the OIDC claims.
        try {
            $claims = $this->validateClaims($request);
            
            // Authentication success
            
            // TODO: Implement authenticate() method.
            
        } catch (ItkOpenIdConnectException $exception) {
            // Authentication failed
            throw new CustomUserMessageAuthenticationException($exception->getMessage());
        }
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        // TODO: Implement onAuthenticationSuccess() method.
    }

    public function start(Request $request, AuthenticationException $authException = null)
    {
        // TODO: Implement start() method.
    }
}

请参阅以下 完整的认证器示例

请确保将您的认证器添加到 security.yaml 文件 - 如果您有多个要添加的入口点。

security:
  firewalls:
    main:
        custom_authenticators:
          - App\Security\ExampleAuthenticator
          - ItkDev\OpenIdConnectBundle\Security\LoginTokenAuthenticator
        entry_point: App\Security\ExampleAuthenticator

示例认证器函数

以下是一个使用具有名称和电子邮件属性的 User 的示例。首先,我们从声明中提取数据,然后检查此用户是否已存在,最后根据是否存在来更新/创建它。

<?php

namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use ItkDev\OpenIdConnect\Exception\ItkOpenIdConnectException;
use ItkDev\OpenIdConnectBundle\Exception\InvalidProviderException;
use ItkDev\OpenIdConnectBundle\Security\OpenIdConfigurationProviderManager;
use ItkDev\OpenIdConnectBundle\Security\OpenIdLoginAuthenticator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;

class AzureOIDCAuthenticator extends OpenIdLoginAuthenticator
{
    /**
     * AzureOIDCAuthenticator constructor
     *
     * @param EntityManagerInterface $entityManager
     * @param RequestStack $requestStack
     * @param UrlGeneratorInterface $router
     * @param OpenIdConfigurationProviderManager $providerManager
     */
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly RequestStack $requestStack,
        private readonly UrlGeneratorInterface $router,
        private readonly OpenIdConfigurationProviderManager $providerManager
    ) {
        parent::__construct($providerManager, $requestStack);
    }

    /** @inheritDoc */
    public function authenticate(Request $request): Passport
    {
        try {
            // Validate claims
            $claims = $this->validateClaims($request);

            // Extract properties from claims
            $name = $claims['name'];
            $email = $claims['upn'];

            // Check if user exists already - if not create a user
            $user = $this->entityManager->getRepository(User::class)
                ->findOneBy(['email'=> $email]);
            if (null === $user) {
                // Create the new user and persist it
                $user = new User();
                $this->entityManager->persist($user);
            }
            // Update/set user properties
            $user->setName($name);
            $user->setEmail($email);

            $this->entityManager->flush();

            return new SelfValidatingPassport(new UserBadge($user->getUserIdentifier()));
        } catch (ItkOpenIdConnectException|InvalidProviderException $exception) {
            throw new CustomUserMessageAuthenticationException($exception->getMessage());
        }
    }

    /** @inheritDoc */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return new RedirectResponse($this->router->generate('homepage_authenticated'));
    }

    /** @inheritDoc */
    public function start(Request $request, AuthenticationException $authException = null): Response
    {
        return new RedirectResponse($this->router->generate('itkdev_openid_connect_login', [
            'provider' => 'user',
        ]));
    }
}

通过命令行登录

您可以通过提供用户名从命令行获取登录 URL,而不是通过 OpenID Connect 登录。请确保配置 OIDC_CLI_REDIRECT_URL。运行

bin/console itk-dev:openid-connect:login <username>

bin/console itk-dev:openid-connect:login --help

获取详细信息。

请注意,登录令牌只能在它被移除之前使用一次,如果您将电子邮件用作您的用户提供者属性,则电子邮件将进入 username 参数。

开发设置

此项目包含一个 docker-compose.yml 文件,其中包含 PHP 8.1 映像。要安装依赖项,可以运行

docker compose up -d
docker compose exec phpfpm composer install

单元测试

本库包含 PhpUnit 设置。要运行单元测试

docker compose exec phpfpm composer install
docker compose exec phpfpm ./vendor/bin/phpunit

Psalm 静态分析

我们使用 Psalm 进行静态分析。要运行 Psalm,请执行

docker compose exec phpfpm composer install
docker compose exec phpfpm ./vendor/bin/psalm

检查编码标准

以下命令可让您测试代码是否遵循项目的编码标准。

  • PHP 文件(PHP-CS-Fixer)

    docker compose exec phpfpm composer coding-standards-check
  • Markdown 文件(markdownlint 标准规则)

    docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app node:18 yarn install
    docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app node:18 yarn coding-standards-check

应用编码标准

尝试自动修复编码风格

  • PHP 文件(PHP-CS-Fixer)

    docker compose exec phpfpm composer coding-standards-apply
  • Markdown 文件(markdownlint 标准规则)

    docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app node:18 yarn install
    docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app node:18 yarn coding-standards-apply

CI

GitHub Actions 用于在所有 PR 上运行测试套件和代码风格检查。

如果您想在本地上测试作业,您可以安装 act。然后执行

act -P ubuntu-latest=shivammathur/node:latest pull_request

版本控制

我们使用 SemVer 进行版本控制。有关可用版本,请参阅此存储库的 标签

许可证

本项目采用 MIT 许可证 - 详细信息请参阅 LICENSE.md 文件