1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Yii2Advent Calendar 2014

Day 10

Yii2で暗号化クッキーとプレーンなクッキーを併用する

Last updated at Posted at 2014-12-10

4連投のつもりが5連投目です。

Yii 2 は標準で Cookie を暗号化してバリデーションします。ユーザーが Cookie の内容改ざんを行えなくなるのでセキュアで結構...なのですが...

RequestenableCookieValidation が効いていると、無条件にすべての Cookie が暗号化されます。つまり、いったん暗号化を選んだら、暗号化しない Cookie を送る方法がなくなってしまいます。そうすると、リバースプロキシやロードバランサーですら、アプリケーションで作った Cookie にはいっさいアクセスできなくなってしまいます。分散/キャッシュの仕組みによっては、アプリケーション内で決定される何かの値が、内部的な経路を決めるヒントになる場合もあるでしょう。

そこで、そんな特別な状況では、「例外的にこれは暗号化しない」という指定が可能な Request/Response を作ってそれを使います。

<?php
namespace app\utils\web;

use Yii;
use yii\base\InvalidConfigException;
use yii\web\Cookie;

/**
 * @inheritdoc
 */
class Request extends \yii\web\Request
{
    /**
     * Cookie names which ignores encryption and validation.
     * @var array
     */
    public $insecureCookies;

    /**
     * @inheritdoc
     */
    protected function loadCookies()
    {
        if ($this->enableCookieValidation) {
            if ($this->cookieValidationKey == '') {
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
            }
        }

        $cookies = [];
        foreach ($_COOKIE as $name => $value) {
            if ($this->enableCookieValidation && !in_array($name, $this->insecureCookies)) {
                if (is_string($value) && ($value = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey)) !== false) {
                    $cookies[$name] = new Cookie([
                        'name' => $name,
                        'value' => @unserialize($value),
                        'expire'=> null
                    ]);
                }
            } else {
                $cookies[$name] = new Cookie([
                    'name' => $name,
                    'value' => $value,
                    'expire'=> null
                ]);
            }
        }

        return $cookies;
    }
}
<?php
namespace app\utils\web;

use Yii;

/**
 * @inheritdoc
 */
class Response extends \yii\web\Response
{
    protected function sendCookies()
    {
        $request = Yii::$app->getRequest();
        if (!$request->enableCookieValidation) {
            parent::sendCookies();
            return;
        }

        $insecureCookieNames = $request->get('insecureCookies', []);

        // separate cookies if secure or insecure
        $secureCookies = [];
        $insecureCookies = [];
        foreach ($this->getCookies() as $cookie) {
            if (in_array($cookie->name, $insecureCookieNames)) {
                $insecureCookies[] = $cookie;
            } else {
                $secureCookies[] = $cookie;
            }
        }
        $this->getCookies()->removeAll();

        // send secure cookies
        foreach ($secureCookies as $cookie) {
            $this->getCookies()->add($cookie);
        }
        parent::sendCookies();

        // send insecure cookies
        foreach ($insecureCookies as $cookie) {
            setcookie($cookie->name, $cookie->value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
        }
    }
}

使い方

<?php
return [
    // ...
    'components' => [
        'request' => [
            'class' => 'app\utils\web\Request',
            'enableCookieValidation' => true,
            'cookieValidationKey' => 'xxxx',
            'insecureCookies' => [
                'insecure_cookie_name',
                'another_insecure_cookie_name',
            ],
        ],
        'response' => [
            'class' => 'app\utils\web\Response',
        ],
    ]
];

これで、 insecure_cookie_name および another_insecure_cookie_name と名付けられた Cookie はセキュリティの保護を受けない代わりに、経路上で読み取ったり、JavaScript でアクセスしたりできるようになります。

(Tips のつもりが案外長いコードになってしまいました)

ただし、こんな平文 Cookie は、JavaScript 連携用のワンタイムトークンや、ロードバランサーの振り分け根拠など、単一の値で表されるものだけにしましょう。Cookie が構造的になると、壊れた文字列を送ってパースエラーを起こさせる攻撃が可能になってしまいますから。もちろん、ユーザーにとって致命的でなく、かつ揮発性の高いデータであるという条件は言うまでもありません。

また、いったん使い始めたら、暗号化していたものを暗号化しない方針に途中で変更することはできません。暗号化した Cookie を持っているブラウザが送ってきた値を、暗号化していない Cookie を想定して受け取ると、アプリケーション内では「解読できないそういう文字列」になってしまいます。十分に注意してください。

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?