dmstr / yii2-usuario-keycloak
Yii2 usuario keycloak 插件
Requires
- 2amigos/yii2-usuario: ^1.6.3
- web-token/jwt-library: ^3.4
- yiisoft/yii2-authclient: ^2.2.16
Suggests
- bizley/jwt: When using JWT for REST authentication.
- lcobucci/clock: JWT StrictValidAt check
This package is auto-updated.
Last update: 2024-09-22 08:33:44 UTC
README
安装
通过 composer 安装包
composer require dmstr/yii2-usuario-keycloak
有关 usuario 安装的说明,请参阅 usuario 文档
设置
要使用 Docker (compose) 运行 keycloak,请参阅 docker 文件夹中的 docker-compose.keycloak.yml
对于本地开发,您应该在 /etc/hosts 中添加 keycloak-local,如下所示:127.0.0.1 keycloak-local
您可能需要将 127.0.0.1 替换为您的 docker ip
配置
此部分配置是必须的。通过此配置,我们将 keycloak 添加为“社交网络”
KEYCLOAK_CLIENT_NAME=Keycloak KEYCLOAK_CLIENT_ID=app # See credentials tab in example realms app client KEYCLOAK_CLIENT_SECRET= KEYCLOAK_ISSUER_URL=http://keycloak-local:8080/realms/example
use yii\authclient\Collection; use Da\User\AuthClient\Keycloak; return [ 'components' => [ 'authClientCollection' => [ 'class' => Collection::class, 'clients' => [ 'keycloak' => [ 'class' => Keycloak::class, 'title' => getenv('KEYCLOAK_CLIENT_NAME'), 'clientId' => getenv('KEYCLOAK_CLIENT_ID'), 'clientSecret' => getenv('KEYCLOAK_CLIENT_SECRET'), 'issuerUrl' => getenv('KEYCLOAK_ISSUER_URL') ] ] ], 'user' => [ // So that the session do not get mixed up 'enableAutoLogin' => false ] ] ]
启用当用户在应用程序中注销时从 keycloak 的前端渠道注销
use dmstr\usuario\keycloak\controllers\SecurityController; return [ 'modules' => [ 'user' => [ 'controllerMap' => [ 'security' => [ 'class' => SecurityController::class ] ] ] ] ]
仅允许具有已验证电子邮件的用户登录
use Da\User\Event\SocialNetworkAuthEvent; use dmstr\usuario\keycloak\controllers\SecurityController; use yii\web\ForbiddenHttpException; return [ 'modules' => [ 'user' => [ 'controllerMap' => [ 'security' => [ 'class' => SecurityController::class, 'on ' . SocialNetworkAuthEvent::EVENT_BEFORE_AUTHENTICATE => function (SocialNetworkAuthEvent $event) { if (isset($event->getClient()->getUserAttributes()['email_verified']) && $event->getClient()->getUserAttributes()['email_verified'] === false) { throw new ForbiddenHttpException(Yii::t('usuario-keycloak', 'Account is not verified. Please confirm your registration email.')); } } ] ] ] ] ]
禁用当用户来自 keycloak 时发送欢迎消息
return [ 'modules' => [ 'user' => [ 'sendWelcomeMailAfterSocialNetworkRegistration' => false ] ] ]
如果您不想允许身份切换。这被推荐,因为 TokenRoleRule 可能无法正确处理潜在的角色 RBAC
return [ 'modules' => [ 'user' => [ 'enableSwitchIdentities' => false ] ] ]
当 keycloak 令牌过期时注销用户
这仅适用于 Web 应用程序,因此请根据需要添加您的配置并对用户组件进行一些轻微修改。您可以复制并使用此示例或扩展您现有的用户组件。
<?php namespace app\components; use Yii; use yii\base\InvalidConfigException; /** * @property-read string|null $authSource */ class User extends yii\web\User { protected const AUTH_SOURCE_CLIENT_ID_SESSION_KEY = 'authSourceClientId'; /** * @throws InvalidConfigException */ public function setAuthSource(string $clientId): void { Yii::$app->getSession()->set(self::AUTH_SOURCE_CLIENT_ID_SESSION_KEY, $clientId); } /** * Returns the name of the auth client with which the user has authenticated himself. * * - null means not authenticated. * - 'app' means, not authenticated via an auth client * * @return string|null */ public function getAuthSource(): ?string { if ($this->getIsGuest()) { return null; } return Yii::$app->getSession()->get(self::AUTH_SOURCE_CLIENT_ID_SESSION_KEY, 'app'); } } ?>
use app\components\User; use Da\User\AuthClient\Keycloak; use Da\User\Event\SocialNetworkAuthEvent; use dmstr\usuario\keycloak\controllers\SecurityController; use yii\base\Exception; use yii\base\InvalidArgumentException; use yii\web\Application; return [ 'on ' . Application::EVENT_BEFORE_REQUEST => function () { $user = Yii::$app->getUser(); $keycloakClientId = 'keycloak'; if ($user && !$user->getIsGuest() && Yii::$app->getUser()->getAuthSource() === $keycloakClientId) { try { $jwt = Yii::$app->jwt; /** @var Keycloak $keycloak */ $keycloak = Yii::$app->authClientCollection->getClient($keycloakClientId); // Check if token is valid if (!$jwt->validate($keycloak->getAccessToken()->getToken())) { // If token is invalid log out the user throw new Exception('Access token invalid.'); } } catch (Exception $exception) { Yii::error($exception->getMessage()); // Logout user if token cannot be revalidated or is revoked $user->logout(); } } }, 'components' => [ 'user' => [ 'class' => User::class ] ], 'modules' => [ 'user' => [ 'controllerMap' => [ 'security' => [ 'class' => SecurityController::class, 'on ' . SocialNetworkAuthEvent::EVENT_AFTER_AUTHENTICATE => function (SocialNetworkAuthEvent $event) { // Save the auth client info to differentiate afterward from which auth client the user was authenticated Yii::$app->getUser()->setAuthSource($event->getClient()->getId()); } ] ] ] ] ];
更改登录 URL 以使站点直接将您重定向到 keycloak 登录页面
return [ 'components' => [ 'user' => [ 'loginUrl' => '/user/security/auth?authclient=keycloak' ] ] ];
在 REST 调用中使用用户身份
我们建议使用来自 bizley/yii2jwt 的 JwtHttpBearerAuth
。您可以在您的用户中实现以下示例。
<?php namespace app\models; use bizley\jwt\JwtHttpBearerAuth; use Da\User\Model\SocialNetworkAccount; use Lcobucci\JWT\Token\Plain; use yii\base\NotSupportedException; use Yii; class User extends \Da\User\Model\User { /** * @inheritdoc */ public static function findIdentityByAccessToken($token, $type = null) { if ($type === JwtHttpBearerAuth::class) { /** @var Plain $jwtToken */ $jwtToken = Yii::$app->jwt->getParser()->parse((string)$token); $claims = $jwtToken->claims(); $userClientId = $claims->get('sub'); /** @var SocialNetworkAccount|null $socialAccount */ $socialAccount = SocialNetworkAccount::find()->andWhere([ 'provider' => 'keycloak', 'client_id' => $userClientId ])->one(); if ($socialAccount) { return static::find() ->whereId($socialAccount->user_id) ->andWhere(['blocked_at' => null]) ->andWhere(['NOT', ['confirmed_at' => null]]) ->andWhere(['gdpr_deleted' => 0]) ->one(); } return null; } throw new NotSupportedException("Type '$type' is not implemented."); } }
使用身份类
use app\models\User as UserModel; return [ 'components' => [ 'user' => [ 'identityClass' => UserModel::class ] ] ]
生成 jwt 的密钥
ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
# Don't add passphrase
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
KEYCLOAK_PRIVATE_KEY_FILE=file:///path/to/jwtRS256.key KEYCLOAK_PUBLIC_KEY_FILE=file:///path/to/jwtRS256.key.pub
use bizley\jwt\Jwt; use Lcobucci\JWT\Validation\Constraint\IssuedBy; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\LooseValidAt; use Lcobucci\Clock\SystemClock; return [ 'components' => [ 'jwt' => [ 'class' => Jwt::class, 'signer' => Jwt::RS256, 'signingKey' => [ 'key' => getenv('KEYCLOAK_PRIVATE_KEY_FILE'), 'method' => Jwt::METHOD_FILE, ], 'verifyingKey' => [ 'key' => getenv('KEYCLOAK_PUBLIC_KEY_FILE'), 'method' => Jwt::METHOD_FILE, ], 'validationConstraints' => function (Jwt $jwt) { $config = $jwt->getConfiguration(); return [ new SignedWith($config->signer(), $config->verificationKey()), new IssuedBy(getenv('KEYCLOAK_ISSUER_URL')), new LooseValidAt(SystemClock::fromUTC()), ]; } ] ] ];
如果您只想使用验证和解析,可以像这样配置 jwt 组件。
use bizley\jwt\JwtTools; use Lcobucci\JWT\Validation\Constraint\IssuedBy; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\LooseValidAt; use Lcobucci\Clock\SystemClock; return [ 'components' => [ 'jwt' => [ 'class' => JwtTools::class, 'validationConstraints' => function (JwtTools $jwt) { return [ new SignedWith($jwt->buildSigner(Jwt::RS256), InMemory::plainText(getenv('KEYCLOAK_PUBLIC_KEY_FILE'))), // You could also use this line if you do not want to use a separate public key file // new SignedWith($jwt->buildSigner(Jwt::RS256), InMemory::plainText(KeycloakHelper::publicKeyFromIssuer(getenv('KEYCLOAK_ISSUER_URL')))), new IssuedBy(getenv('KEYCLOAK_ISSUER_URL')), new LooseValidAt(SystemClock::fromUTC()), ]; } ] ] ];
与 Keycloak 结合使用时,KEYCLOAK_PUBLIC_KEY_FILE
的值应该是 Keycloak 公钥。
当使用 JwtHttpBearerAuth
时,请确保在控制器或模块的 behaviors
中将 cors 设置在 authenticator
之前,并将所有访问控制设置在之后。
自动提交社交账户注册确认表单
use Da\User\Controller\RegistrationController; use ActionEvent; return [ 'modules' => [ 'user' => [ 'controllerMap' => [ 'registration' => [ 'class' => RegistrationController::class, 'on ' . RegistrationController::EVENT_BEFORE_ACTION => function (ActionEvent $event) { if ($event->action->id === 'connect') { // You may need to change the form id but this is the default $event->action->controller->view->registerJs('if ($(".has-error").length === 0){$("form#User").submit()};'); } } ] ] ] ] ]
TokenRoleRule
此规则允许您根据用户在 keycloak 中的角色将角色分配给用户。如果您想将 keycloak 用作用户角色的单一来源,这将非常有用。请注意,keycloak 中的角色名称必须与角色匹配,并且应分配给任何已登录用户。