読者です 読者をやめる 読者になる 読者になる

フォーム認証・はじめの一歩

Symfony

Symfony2のセキュリティー機構を使ってフォーム認証をやってみる。

security.configを設定する

参考記事。

app/config/config.ymlにsecurity.configを設定する。

security.config:
    encoders:
        default:
            class:     Symfony\Component\Security\User\User
            algorithm: plaintext

    providers:
        default:
            users:
                scott: { password: tiger, roles: ROLE_USER }

    firewalls:
        default:
            pattern:    /.*
            anonymous:  true
            form-login: true
            logout:     true

この設定の前提とポイント。

  • /loginというURLでフォームを表示するため、/loginというURLに認証がかかっているとリダイレクトループが発生する。そのため、該当のURLをanonymous: trueにするか、security: falseにしなくてはいけない。
  • providersの中にplaintextでパスワードを書くのはもちろん良くない。今回はちょっと試してみるだけということだから、こう書いているが、実際はsha1でdigestするか、Doctrineを使ったより現実的なproviderを使うことになるだろう。

今後の調査が必要なこと。

  • app/config/security.ymlに設定するようなことが書いてあるが、最初それでやってみたら動いている気配がなかったので深追いしなかった。
  • encodersを明示する必要があるとはドキュメントに書いてあるように見えないが、これがないと動かなかった。

フォームを表示するページをつくる。

参考記事。

適当なBundleにControllerとResourceを用意してフォームを表示するページを作る。汎用的に使いたいならばSecurityBundleみたいな独立したBundleを作るのだろうが、今回はテスト用のアプリケーションのBundle(MyBundle)の中に入れてしまった。

<?php

namespace Application\MyBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\SecurityContext;

class SecurityController extends Controller
{
    public function loginAction()
    {
        $req = $this->get('request');
        if ($req->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
            $error = $req->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
        } else {
            $error = $req->getSession()->get(SecurityContext::AUTHENTICATION_ERROR);
        }

        $args = array('last_username' => $req->getSession()->get(SecurityContext::LAST_USERNAME),
                      'error'         => $error,
                      );
        return $this->render('MyBundle:Security:login.twig', $args);
    }
}

テンプレートは、

{% extends "MyBundle::layout.twig" %}

{% block content %}
{% if error %}
    <div>{{ error }}</div>
{% endif %}

<form action="{{ path('_security_check') }}" method="post">
    <label for="username">Username:</label>
    <input type="text" id="username" name="_username" value="{{ last_username }}" />

    <label for="password">Password:</label>
    <input type="password" id="password" name="_password" />

    <input type="submit" name="login" />
</form>
{% endblock %}

routing.ymlに以下を追加。

_security_login:
    pattern:  /login
    defaults: { _controller: MyBundle:Security:login }

_security_check:
    pattern:  /login_check

_security_logout:
    pattern:  /logout

/loginというURLはMyBundleのSecurityControllerのloginAction()メソッドにルーティングされるのは自明だからすぐ分かるのだが、/login_check, /logoutというURLがどのコントローラーで処理されるのか、自分で書く必要があるのかが分からなくて、ちょっと困った。

この点についてもそれほど深追いしている訳ではないが、

  • FormAuthenticationListenerというイベントリスナーの中で処理される
  • したがって自分でコントローラー内にloginCheckというようなメソッドを書く必要がない
  • イベントリスナーで処理するURLを/login_checkから変更したい時は、security.configの設定で変更する。

ということらしい。

ControllerからSecurityContext/Userにアクセスする

このあたりの記事を参考にする。

Controllerのメソッド内からは、

$user = $this->container->get('security.context')->getUser();

でも、

$user = $this->get('security.context')->getUser();

でも行けたが、containerを介してアクセスするのとどっちが推奨なのかは調べていない。