itk-dev/openid-connect-bundle 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

itk-dev/openid-connect-bundle

Composer 安装命令:

composer require itk-dev/openid-connect-bundle

包简介

Symfony bundle for openid-connect

README 文档

README

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

Symfony bundle for authorization via OpenID Connect.

Note

Symfony Native OIDC Support

Since this bundle was created Symfony has added support for OpenID Connect as documented in "Using OpenID Connect (OIDC)".

Symfony's native OIDC support has improved significantly in recent releases:

  • OIDC discovery was added in Symfony 7.3 (May 2025), removing the need for manual keyset configuration. Keys are fetched and cached automatically from the provider's .well-known/openid-configuration endpoint.
  • OAuth2 Token Introspection (RFC 7662) support was added in Symfony 7.3, useful when access tokens are opaque (not JWTs).
  • JWE (encrypted token) support was added in Symfony 7.3 for OIDC token handlers.

However, Symfony's native OIDC support is designed for stateless bearer token validation (the access_token authenticator) only. It validates tokens that are already present on the request (e.g. in an Authorization: Bearer header).

It does not implement the authorization code flow — the browser-based login where the application redirects to the IdP, handles the callback with an authorization code, exchanges it for tokens, and establishes a session. This is tracked upstream in symfony/symfony#50896.

This means the following features of this bundle have no native Symfony equivalent:

Feature This bundle Symfony native
Authorization code flow
Session-based browser login
Multiple named OIDC providers ❌ ¹
CLI login tokens
OIDC discovery
Bearer token validation (API)
OAuth2 token introspection

¹ Symfony's access_token handler accepts multiple issuers for token validation, but this is not the same as this bundle's named provider model with distinct client credentials, redirect URIs, and selectable login routes per provider.

If your application needs browser-based OIDC login, this bundle is still required.

Installation

To install run

composer require itk-dev/openid-connect-bundle

Usage

Before being able to use the bundle, you must have your own User entity and database setup.

Once you have this, you need to

  • Configure variables for OpenId Connect
  • Create an Authenticator class that extends the bundle authenticator, OpenIdLoginAuthenticator
  • Configure LoginTokenAuthenticator in order to use CLI login.

Variable configuration

In /config/packages/ you need the following itkdev_openid_connect.yaml file for configuring OpenId Connect variables

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: Cache duration (seconds) for the OIDC discovery document and JWKS
        #           Defaults to 86400 (24 hours)
        cache_duration: '%env(int:ADMIN_OIDC_CACHE_DURATION)%'
        # Optional: Allow (non-secure) http requests (used for mocking a IdP). NOT RECOMMENDED FOR PRODUCTION.
        #           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 }

With the following .env environment variables

###> 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_CACHE_DURATION=86400
ADMIN_OIDC_ALLOW_HTTP=false

# "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 ###

Set the actual values your env.local file to ensure they are not committed to Git.

Configuring the HTTP client

Each provider accepts an optional http_client_options block that is forwarded to the underlying Guzzle HTTP client used by league/oauth2-client. The bundle applies a sensible default timeout of 30 seconds so a slow IdP cannot block worker processes indefinitely (Guzzle's own default is 0, i.e. wait forever). Override it per provider, or set it to 0 to opt back into Guzzle's behaviour.

itkdev_openid_connect:
  openid_providers:
    user:
      options:
        # ... existing keys ...
        # @see https://docs.guzzlephp.org/en/stable/request-options.html
        http_client_options:
          # Float describing the total timeout of the request in seconds. Defaults to 30; set to 0 to wait indefinitely.
          timeout: 5.0 
          # Pass a string to specify an HTTP proxy, or an array to specify different proxies for different protocols. (Default: none)
          proxy: "%env(string:HTTP_PROXY)%"
          # Describes the SSL certificate verification behavior of a request. (Default: true)
          verify: true 

The bundle accepts only timeout, proxy, and verify under http_client_options — these are the keys league/oauth2-client forwards to Guzzle (verify is consulted only when proxy is set). Any other key causes an InvalidConfigurationException at container compile time.

Why Guzzle and not Symfony HttpClient? league/oauth2-client, which the underlying itk-dev/openid-connect library extends, hard-types its HTTP client as GuzzleHttp\ClientInterface. Symfony HttpClient implements PSR-18 / HTTPlug, not Guzzle's interface, and no maintained adapter goes Symfony → Guzzle. Configure Guzzle via the options above; full transport replacement is not currently possible without a custom adapter we are not yet shipping.

In /config/routes/ you need a similar itkdev_openid_connect.yaml file for configuring the routing

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

It is not necessary to add a prefix to the bundle routes, but in case you want i.e. another /login route, it makes distinguishing between them easier.

When invoking the login controller action (route itkdev_openid_connect_login) the key of a provider must be set in the provider parameter, e.g.

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

Make sure to allow anonymous access to the login controller route, i.e. something along the lines of

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

CLI login

In order to use the CLI login feature the following environment variable must be set in order for Symfony to be able to generate URLs in commands:

DEFAULT_URI=

See Symfony documentation: Generating URLs in Commands for more information.

You must also add the bundles CliLoginTokenAuthenticator to the security.yaml file:

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

Finally, configure the Symfony route to use for login links: cli_login_options: route. If yoy have multiple firewalls that are active for different url patterns you need to make sure you add LoginTokenAuthenticator to the firewall active for the route specified here.

Creating the Authenticator

The bundle can help you get the claims received from the authorizer – the only functions that need to be implemented are authenticate(), onAuthenticationSuccess() and 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.
    }
}

See below for a full authenticator example.

Make sure to add your authenticator to the security.yaml file - and if you have more than one to add an entry point.

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

Example authenticator functions

Here is an example using a User with a name and email property. First we extract data from the claims, then check if this user already exists and finally update/create it based on whether it existed or not.

<?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',
        ]));
    }
}

Sign in from command line

Rather than signing in via OpenId Connect, you can get a sign in url from the command line by providing a username. Make sure to configure OIDC_CLI_REDIRECT_URL. Run

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

or

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

for details.

Be aware that a login token only can be used once before it is removed, and if you used email as your user provider property the email goes into the username argument.

Development Setup

A docker-compose.yml file with a PHP 8.3+ image is included in this project. A Taskfile is used to run common development tasks.

To set up the project:

task setup

This starts the Docker containers and installs Composer dependencies.

Running All CI Checks

To run all checks locally (coding standards, static analysis, tests):

task pr:actions

Unit Testing

task test

Test Matrix

Run the test suite across all supported PHP versions (8.3, 8.4, 8.5) with both lowest and stable dependencies, mirroring the CI matrix:

task test:matrix

This runs PHPUnit with coverage for each combination and prints a summary of pass/fail results.

Mutation Testing

Line coverage shows which code the tests execute; mutation testing shows which code they actually verify. Infection applies small changes (mutants) to the source code — flipping a comparison, removing a method call — and runs the test suite against each one. If the tests still pass, the mutant "escaped": a potential bug the tests would not catch.

task test:mutation

The minimum mutation score (minCoveredMsi) is defined in infection.json5 and enforced both locally and in CI — no command line flags needed. CI annotates escaped mutants inline on pull requests, and results for develop are published to the Stryker dashboard, which also feeds the mutation score badge above. Detailed reports are written to infection.log and infection.html on each run.

PHPStan Static Analysis

task analyze

Coding Standards

Check all coding standards:

task lint

Fix PHP coding standards (php-cs-fixer):

task lint:php:fix

Fix Markdown files:

task lint:markdown:fix

Fix YAML files:

task lint:yaml:fix

Available Tasks

Run task --list to see all available tasks.

CI

GitHub Actions are used to run the test suite, mutation tests and code style checks on all PRs.

Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

License

This project is licensed under the MIT License - see the LICENSE.md file for details

itk-dev/openid-connect-bundle 适用场景与选型建议

itk-dev/openid-connect-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 49.08k 次下载、GitHub Stars 达 2, 最近一次更新时间为 2021 年 09 月 16 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 itk-dev/openid-connect-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 itk-dev/openid-connect-bundle 我们能提供哪些服务?
定制开发 / 二次开发

基于 itk-dev/openid-connect-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 49.08k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 28
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 2
  • Watchers: 2
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2021-09-16