Jira TrackerとSymfony2認証の統合

こんにちは、Habrosociety。 この記事では、よく知られているSymfony2フレームワークとそれほど有名ではないJiraトラッカーと友達を作る方法についてお話したいと思います。



JiraとSymfony2をバインドする理由



私が働いている会社では、顧客からのリクエストをチケットに簡単に変換できるように、サポートシステムとタスクトラッカーをAPI経由で接続する必要がありました。 私たちの邪魔をした主な問題は、Jira認証(基本認証メカニズムを使用)とSymfony2セキュリティシステムの統合でした。 フレームワークの認証および承認メカニズムを理解するには、公式ドキュメントhttp://symfony.com/doc/current/book/security.htmlに精通する必要があります。





Symfony2で新しい承認タイプを作成するには何が必要ですか?



  1. トークン。認証中にユーザーが入力した情報を保存します。
  2. ユーザーの承認を検証するためにリスナーが必要です。
  3. Jiraを介して認証を直接実装するプロバイダー。
  4. Symfony2 Securityがユーザー情報を照会するユーザープロバイダー。
  5. ファクトリ。新しい認証および承認方法を登録します。




トークンを作成



symfonyはAbstractTokenクラスから継承するトークンを使用して、ユーザー認証中に入力した情報を保存し、後で使用します。 検討中の問題では、2つのフィールドを保存する必要があります-これはユーザーのユーザー名とパスワードであり、これに基づいてJiraで承認がチェックされます。 トークンクラスの実装コードを以下に示します。



<?php namespace DG\JiraAuthBundle\Security\Authentication\Token; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; class JiraToken extends AbstractToken { protected $jira_login; protected $jira_password; public function __construct(array $roles = array('ROLE_USER')){ parent::__construct($roles); $this->setAuthenticated(count($roles) > 0); } public function getJiraLogin(){ return $this->jira_login; } public function setJiraLogin($jira_login){ $this->jira_login = $jira_login; } public function getJiraPassword(){ return $this->jira_password; } public function setJiraPassword($jira_password){ $this->jira_password = $jira_password; } public function serialize() { return serialize(array($this->jira_login, $this->jira_password, parent::serialize())); } public function unserialize($serialized) { list($this->jira_login, $this->jira_password, $parent_data) = unserialize($serialized); parent::unserialize($parent_data); } public function getCredentials(){ return ''; } }
      
      







リスナーの実装



ユーザーデータが保存されたので、正確性を確認できるはずです。 データが古い場合、フレームワークにこれを通知する必要があります。 これを行うには、AbstractAuthenticationListenerから継承したリスナーを実装する必要があります。



 <?php namespace DG\JiraAuthBundle\Security\Firewall; use DG\JiraAuthBundle\Security\Authentication\Token\JiraToken; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Psr\Log\LoggerInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener; class JiraListener extends AbstractAuthenticationListener { protected function attemptAuthentication(Request $request){ if ($this->options['post_only'] && 'post' !== strtolower($request->getMethod())) { if (null !== $this->logger) { $this->logger->debug(sprintf('Authentication method not supported: %s.', $request->getMethod())); } return null; } $username = trim($request->get($this->options['username_parameter'], null, true)); $password = $request->get($this->options['password_parameter'], null, true); $request->getSession()->set(SecurityContextInterface::LAST_USERNAME, $username); $request->getSession()->set('jira_auth', base64_encode($username.':'.$password)); $token = new JiraToken(); $token->setJiraLogin($username); $token->setJiraPassword($password); return $this->authenticationManager->authenticate($token); } }
      
      







Jiraでの承認。 プロバイダー



最も重要なこと、つまりJiraに直接データを送信する時が来ました。 REST APIトラッカーを使用するために、サービスとして接続する単純なクラスが作成されます。 Jira APIを使用するには、バズライブラリが使用されます。



 <?php namespace DG\JiraAuthBundle\Jira; use Buzz\Message; use Buzz\Client\Curl; class JiraRest { private $jiraUrl = ''; public function __construct($jiraUrl){ $this->jiraUrl = $jiraUrl; } public function getUserInfo($username, $password){ $request = new Message\Request( 'GET', '/rest/api/2/user?username=' . $username, $this->jiraUrl ); $request->addHeader('Authorization: Basic ' . base64_encode($username . ':' . $password) ); $request->addHeader('Content-Type: application/json'); $response = new Message\Response(); $client = new Curl(); $client->setTimeout(10); $client->send($request, $response); return $response; } }
      
      







プロバイダーはAuthenticationProviderInterfaceインターフェイスを実装する必要があり、次のようになります。



 <?php namespace DG\JiraAuthBundle\Security\Authentication\Provider; use DG\JiraAuthBundle\Entity\User; use DG\JiraAuthBundle\Jira\JiraRest; use DG\JiraAuthBundle\Security\Authentication\Token\JiraToken; use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserProviderInterface; class JiraProvider implements AuthenticationProviderInterface { private $userProvider; private $jiraRest; public function __construct(UserProviderInterface $userProvider, $providerKey, JiraRest $jiraRest) { $this->userProvider = $userProvider; $this->jiraRest = $jiraRest; } public function supports(TokenInterface $token) { return $token instanceof JiraToken; } public function authenticate(TokenInterface $token) { $user = $this->checkUserAuthentication($token); $token->setUser($user); return $token; } public function checkUserAuthentication(JiraToken $token){ $response = $this->jiraRest->getUserInfo($token->getJiraLogin(), $token->getJiraPassword()); if(!in_array('HTTP/1.1 200 OK', $response->getHeaders())){ throw new AuthenticationException( 'Incorrect email and/or password' ); } $userInfo = json_decode($response->getContent()); $user = new User(); $user->setUsername($userInfo->name); $user->setBase64Hash(base64_encode($token->getJiraLogin() . ':' . $token->getJiraPassword())); $user->setEmail($userInfo->emailAddress); $user->addRole('ROLE_USER'); return $user; } }
      
      







実装からわかるように、ユーザーデータはUserエンティティに格納されます。 これは、Doctrineがデータベースに余分なテーブルを作成しないようにすることができますが、将来、トラッカーの一時的なアクセス不能から保護するために、Jiraからのユーザーに関する情報をこのテーブルに追加できます。 このような「保険」は記事の範囲を超えていますが、非常に有用です。



許可ユーザー情報の提供



フレームワークのセキュリティシステムは、承認を確認するためにユーザー情報を要求します。 そのような情報がJiraにあることは明らかなので、トラッカーから受信する必要があります。 もちろん、Jiraからの回答をキャッシュすることはできますが、現時点ではこれを考慮しません。 プロバイダーコードは次のとおりです。



 <?php namespace DG\JiraAuthBundle\User; use DG\JiraAuthBundle\Entity\User; use DG\JiraAuthBundle\Jira\JiraRest; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\SecurityContextInterface; class JiraUserProvider implements UserProviderInterface { private $jiraRest; public function __construct(JiraRest $jiraRest){ $this->jiraRest = $jiraRest; } public function loadUserByUsername($username) { } public function refreshUser(UserInterface $user) { if (!$user instanceof User) { throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); } $decodedUserData = base64_decode($user->getBase64Hash()); list($username, $password) = explode(':', $decodedUserData); $userInfoResponse = $this->jiraRest->getUserInfo($username, $password); $userInfo = json_decode($userInfoResponse->getContent()); $user = new User(); $user->setUsername($user->getUsername()); $user->setEmail($userInfo->emailAddress); $user->setBase64Hash($user->getBase64Hash()); $user->addRole('ROLE_USER'); return $user; } public function supportsClass($class) { return $class === 'DG\JiraAuthBundle\Entity\User'; } }
      
      







構成の充填



作成されたクラスを使用するには、それらをサービスとして構成に登録する必要があります。 services.ymlの例を以下に示します。 jira_urlパラメーターはparameters.ymlで定義し、Jiraの前にURLアドレスを含める必要があることに注意してください。

 parameters: dg_jira_auth.user_provider.class: DG\JiraAuthBundle\User\JiraUserProvider dg_jira_auth.listener.class: DG\JiraAuthBundle\Security\Firewall\JiraListener dg_jira_auth.provider.class: DG\JiraAuthBundle\Security\Authentication\Provider\JiraProvider dg_jira_auth.handler.class: DG\JiraAuthBundle\Security\Authentication\Handler\JiraAuthenticationHandler dg_jira.rest.class: DG\JiraAuthBundle\Jira\JiraRest services: dg_jira.rest: class: %dg_jira.rest.class% arguments: - '%jira_url%' dg_jira_auth.user_provider: class: %dg_jira_auth.user_provider.class% arguments: - @dg_jira.rest dg_jira_auth.authentication_success_handler: class: %dg_jira_auth.handler.class% dg_jira_auth.authentication_failure_handler: class: %dg_jira_auth.handler.class% dg_jira_auth.authentication_provider: class: %dg_jira_auth.provider.class% arguments: [@dg_jira_auth.user_provider, '', @dg_jira.rest] dg_jira_auth.authentication_listener: class: %dg_jira_auth.listener.class% arguments: - @security.context - @security.authentication.manager - @security.authentication.session_strategy - @security.http_utils - '' - @dg_jira_auth.authentication_success_handler - @dg_jira_auth.authentication_failure_handler - '' - @logger - @event_dispatcher
      
      







Symfonyに新しい認証と承認方法を登録します



上記のすべてを機能させるには、認証動作をファクトリーとして記述し、バンドルに登録する必要があります。



 <?php namespace DG\JiraAuthBundle\DependencyInjection\Security\Factory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AbstractFactory; use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Reference; class JiraFactory extends AbstractFactory { public function __construct(){ $this->addOption('username_parameter', '_username'); $this->addOption('password_parameter', '_password'); $this->addOption('intention', 'authenticate'); $this->addOption('post_only', true); } protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId) { $provider = 'dg_jira_auth.authentication_provider.'.$id; $container ->setDefinition($provider, new DefinitionDecorator('dg_jira_auth.authentication_provider')) ->replaceArgument(1, $id) ; return $provider; } protected function getListenerId() { return 'dg_jira_auth.authentication_listener'; } public function getPosition() { return 'form'; } public function getKey() { return 'jira-form'; } protected function createListener($container, $id, $config, $userProvider) { $listenerId = parent::createListener($container, $id, $config, $userProvider); if (isset($config['csrf_provider'])) { $container ->getDefinition($listenerId) ->addArgument(new Reference($config['csrf_provider'])) ; } return $listenerId; } protected function createEntryPoint($container, $id, $config, $defaultEntryPoint) { $entryPointId = 'security.authentication.form_entry_point.'.$id; $container ->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.form_entry_point')) ->addArgument(new Reference('security.http_utils')) ->addArgument($config['login_path']) ->addArgument($config['use_forward']) ; return $entryPointId; } }
      
      







バンドルに登録するには、バンドルクラスのビルドメソッドに行を追加する必要があります



 $extension->addSecurityListenerFactory(new JiraFactory());
      
      







最終紹介



すべて、これでJiraとの作業をテストする準備ができました。 security.ymlで作成されたJiraUserProviderを文字列としてプロバイダーセクションに追加します



  jira_auth_provider: id: dg_jira_auth.user_provider
      
      







次に、ファイアウォールに新しいセクションを追加する必要があります。アドレスが/ jira /で始まるすべてのページが、許可されていないユーザーに対してデフォルトで閉じられていると仮定します。



  jira_secured: provider: jira_auth_provider switch_user: false context: user pattern: /jira/.* jira_form: check_path: dg_jira_auth_check_path login_path: dg_jira_auth_login_path default_target_path: dg_jira_auth_private logout: path: dg_jira_auth_logout target: dg_jira_auth_public anonymous: true
      
      







最後に、ページを表示するために必要なユーザーロールを定義するaccess_controlsセクションに行を追加します。 線のおおよその形は



 - { path: ^/jira/public, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/jira/private/login$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/jira/private(.*)$, role: ROLE_USER }
      
      







PS



記事のすべてのコードは、composerまたはgithubを使用してdg / jira-auth-bundleパッケージからバンドルとしてインストールできます バンドルを機能させるには、AppKernel.phpに登録してセクションを追加する必要があります



 _jira_auth: resource: "@DGJiraAuthBundle/Resources/config/routing.yml" prefix: /jira/
      
      







routing.ymlで。 その後、ページ/ jira / publicにアクセスして、Jiraを介して認証をテストできます。



材料を固定するには



Symfony Cookbookには、サードパーティのWebサービスを介して認証を実装する方法に関する指示もあります。



この記事がお役に立てば幸いです!



All Articles