itk-dev / openid-connect-bundle
用于 OpenID Connect 的 Symfony 扩展包
Requires
- php: ^8.1
- ext-json: *
- ext-openssl: *
- doctrine/orm: ^2.8
- itk-dev/openid-connect: ^3.1
- symfony/cache: ^5.4|^6.0
- symfony/framework-bundle: ^5.4|^6.0
- symfony/security-bundle: ^5.4|^6.0
- symfony/uid: ^5.4|^6.0
- symfony/yaml: ^5.4|^6.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.28
- escapestudios/symfony2-coding-standard: ^3.12
- friendsofphp/php-cs-fixer: ^3.11
- kubawerlos/php-cs-fixer-custom-fixers: ^3.11
- phpunit/phpunit: ^9.5
- psalm/plugin-symfony: ^5.0
- rector/rector: ^0.14.2
- vimeo/psalm: ^5.0
README
通过 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 文件