tohokuaikiのチラシの裏

技術的ネタとか。

Apacheのmod_cacheが効かなかったので色々と調べた件

WordPressの負荷軽減にmod_cacheを使おうと思った。

で、こんな感じ

# a2enmod cache_disk
# systemctl start apache-htcacheclean
# systemctl restart apache2

で、/etc/apache2/site-availables/vhosts.confに

CacheRoot /tmp/apache/cache
CacheIgnoreCacheControl On
CacheIgnoreHeaders Set-Cookie
CacheEnable disk /
CacheDefaultExpire 300

を加える。/tmp/apache/cacheは、Apacheユーザーにするか777に。

ところが、効かない…

キャッシュされる感じが全くない。/tmp/apache/cacheにもファイルができない。

ん~~~~~~と、ログ見たりapacheの mod_cache の設定を復習した - うまいぼうぶろぐ見たりして気付いた。

Cacheが有効になるのは以下の条件がある。 http://httpd.apache.org/docs/2.4/caching.html

What Can be Cached?
The full definition of which responses can be cached by an HTTP cache is defined in RFC2616 Section 13.4 Response Cacheability, and can be summed up as follows:

  • Caching must be enabled for this URL. See the CacheEnable and CacheDisable directives.
  • If the response has an HTTP status code other than 200, 203, 300, 301 or 410 it must also specify an "Expires" or "Cache-Control" header.
  • The request must be a HTTP GET request.
  • If the response contains an "Authorization:" header, it must also contain an "s-maxage", "must-revalidate" or "public" option in the "Cache-Control:" header, or it won't be cached.
  • If the URL included a query string (e.g. from a HTML form GET method) it will not be cached unless the response specifies an explicit expiration by including an "Expires:" header or the max-age or s-maxage directive of the "Cache-Control:" header, as per RFC2616 sections 13.9 and 13.2.1.
  • If the response has a status of 200 (OK), the response must also include at least one of the "Etag", "Last-Modified" or the "Expires" headers, or the max-age or s-maxage directive of the "Cache-Control:" header, unless the CacheIgnoreNoLastMod directive has been used to require otherwise.
  • If the response includes the "private" option in a "Cache-Control:" header, it will not be stored unless the CacheStorePrivate has been used to require otherwise.
  • Likewise, if the response includes the "no-store" option in a "Cache-Control:" header, it will not be stored unless the CacheStoreNoStore has been used.
  • A response will not be stored if it includes a "Vary:" header containing the match-all "*".
  • CacheEnableとCacheDisable の指定
  • HTTPステータスが200/203/300/410以外なら"Expires" or "Cache-Control"で指定する
  • GETメソッドであること
  • Authorizationヘッダーが含まれる場合(Basic認証など)、Cache-Controlヘッダーにs-maxage/must-revalidate/publicが入っていること
  • Queryパラメータがある場合は、Expiresヘッダがで指定するかCache-Controlヘッダでs-maxageかmax-ageが入っていること
  • "Etag"か"Last-Modified"か"Expires"が入っていること。または、 max-age or s-maxageが入っているCache-Controlヘッダがあること。ただし、CacheIgnoreNoLastMod を使っている場合は除く
  • Cache-Controlでprivateを指定している場合、CacheStorePrivate オプションが有効になっていること。
  • Cache-Controlでno-storeを指定している場合、CacheStoreNoStore オプションが有効になっていること。
  • Vary: *があるとキャッシュされない

キャッシュされなかった理由は

Basic認証のページを見ていたから。

正確には、

  1. Basic認証のページを開く
  2. httpd.confでBasic認証を解除
  3. Apache再起動
  4. ブラウザでアクセス

としてたんだけど、ブラウザはBasic認証の情報を自動送信するのでブラウザを再起動させなければRequestHeaderにAuthorizationがそのまま記憶されて送られてしまうんだな。ハマった。

上記には、「 If the response contains an "Authorization:" header,」ってあるけど、これ「If the request」の間違いじゃないかな?文中には

Requests with an "Authorization" header (for example, HTTP Basic Authentication) are neither cacheable nor served from the cache when mod_cache is running in this phase.

ともあって、Basic認証を掛けている場合はキャッシュされない模様。

なんか、全部同じページになるんだけど…

なんだこれは…違うURLにアクセスしても全部同じコンテンツになっとる…

これか。

RewriteRule . /index.php [L]

RewriteRule . /index.php?cache_key=%{REQUEST_URI} [QSA,L]

にする。

しかし、なんかまだchromeでアクセスしたときにDeveloper Toolsを開けてるとキャッシュを取りに行き、そうでないとキャッシュ使わないとか挙動が不穏…

ということで、とりあえず現状で

.htaccess

RewriteRule . /index.php?cache_key=%{REQUEST_URI} [QSA,L]
#RewriteRule . /index.php [L]

テーマのfunctions.php

<?php
/* mod_cacheのための */
unset($_GET['cache_key']);
unset($_REQUEST['cache_key']);

// キャッシュ制御のためのLast-Modifiedヘッダ
add_action("template_redirect", function (){
    if( (is_home() || is_singular()) && !$_GET) {
        $span = 300;
        header('Expires: '. date('r', time() + $span));
        header('Cache-Control: public, max-age='.$span.', s-maxage='.$span);
        header(sprintf("Last-Modified: %s", get_the_modified_time("r")) );
    }
});

httpd.conf

    <IfModule mod_cache.c>
    <IfModule mod_cache_disk.c>
          CacheEnable disk /
          CacheRoot /tmp/var/apache2/cache
          CacheIgnoreHeaders Set-Cookie
          CacheIgnoreCacheControl On
          CacheIgnoreNoLastMod On
          CacheMaxExpire 600
          CacheDetailHeader On
    </IfModule>
    </IfModule>

うーん、CacheIgnoreCacheControl とかCacheIgnoreNoLastMod あたりはOffにしたいなぁ…とは思うけど、ブラウザ側の挙動に依存させたくないのでこれでいいか。

あと、CacheDetailHeader On これは良い。HTTPヘッダにキャッシュを使ったかどうかが分かる。そもそもサーバー側のキャッシュポリシーはCache-Controlヘッダで相手にはわかっているのでこれがユーザーに漏れたところで実害は無いと思う。

Debian10でApache2.4+PHP-fpmを動かす

PHP5.6を使いたい。参考にしたサイト

aptでパッケージ先を追加

普通にapt installできないので、https://packages.sury.org/php/ をパッケージ先に追加する。

GPG認証するパッケージを入れて、キーを追加して、設定ファイルを作る。

# apt install ca-certificates apt-transport-https 
# wget -q https://packages.sury.org/php/apt.gpg -O- | apt-key add -
# echo "deb https://packages.sury.org/php/ stretch main" | tee /etc/apt/sources.list.d/php.list

で、パッケージ情報を追加

# apt update
# apt upgrade

適当にPHP5.6周りをインストール

dpkg -l |grep php5.6 で出てきたのから適当に必要そうなのをインストール

#  apt install -y php5.6-mysql php5.6-mbstring php5.6-xml php5.6-zip php5.6-cli

PHP5.6がApacheからモジュールとして実行されないようにphp5.6はインストールしない。

次にphp5.6-fpmをインストール。さっきのと同時でもいいのだけど。

# apt install -y php5.6-fpm

次にPHPモジュールも入れる。PHPモジュールはFastCGIApache SAPI も同じである。当たり前か。

# apt install -y php5.6 php5.6-mysql php5.6-mbstring php5.6-xml php5.6-zip php5.6-cli

VirtualHostごとに実行するPHPを変更する。

php-fpm5.6がserviceとして起動しているのを確認。

# systemctl status php5.6-fpm.service

/etc/php/5.6/fpm/pool.d/www.conf を見ると、

listen = /run/php/php5.6-fpm.sock

Unixソケットで接続するようになっているので、sites-available/hogehoge.conf で

<VirtualHost *:80>
        <Directory /path/to/document/root>
            require all granted
        </Directory>
        <FilesMatch \.(html|php)$>
            SetHandler "proxy:unix:/run/php/php5.6-fpm.sock|fcgi://localhost"

としておく。HTMLとPHP拡張子で動く。

require all granted は、いつものように「あー、AH01797: client denied by server configuration」で403を何度も繰り返すため…何度設定しても1度はミスる。

Apacheのモジュールを適宜IN/OUTしておく

これやり忘れてて「SetHandlerのproxyが効いてくれない・・・」とハマりました。

 a2dismod mpm_prefork
 a2enmod mpm_event
 a2enmod proxy_fcgi

.htaccessphp_value, php_flagの書き方を変える。

php_value auto_prepend_file /path/to/document/root/auto_prepend.php
php_flag error_log Off

とあるのを

SetEnv PHP_VALUE "auto_prepend_file=/path/to/document/root/auto_prepend.php"
SetEnv PHP_FLAG "error_log=Off"

とかする。

またAccess Denied

ってエラーがindex.htmlにアクセスすると出る。

 AH01071: Got error 'Access to the script '/var/www/htdocs/index.html' has been denied (see security.limit_extensions)\n'

/etc/php/5.6/fpm/pool.d/www.conf で

security.limit_extensions = .php .html

として、/etc/init.d/php5.6-fpm restart で動いた。

で、またハマる

VirtualHostにして、他のサイトでinclude_pathやauto_prepend_fileを設定すると、他のサイトでもこの指定が残る。

んー、なんやねんこれは。

結局、

<VirtualHost>
SetEnv PHP_VALUE "auto_prepend_file=/path/to/document/root/auto_prepend.php"
</VirtualHost>

<VirtualHost>
SetEnv PHP_VALUE "auto_prepend_file="
</VirtualHost>

みたいにして逐一書き直す羽目になった…なんやねんこれは…

ミライコンパスを使ってる学校のイベント情報をRSSで取得する

全国の私立中高校で説明会などの予約に使われているミライコンパスというのがあるんだけど、これを学校をまとめてRSS化するようにした。

Nodejsで作ってる。

$ node -v
v10.16.0

コード

これを

$ node mirai-compass-rss-index.js

として、見たいのが

学校 イベントURL
女子美術大学付属中学校(東京都) https://mirai-compass.net/usr/joshibij/event/evtIndex.jsf
成立学園中学校(東京都) https://mirai-compass.net/usr/seiritzj/event/evtIndex.jsf
かえつ有明中学校(東京都) https://mirai-compass.net/usr/kariakej/event/evtIndex.jsf
国士舘高等学校(東京都) https://mirai-compass.net/usr/kokushih/event/evtIndex.jsf
常総学院中学校(茨城県 https://mirai-compass.net/usr/josogj/event/evtIndex.jsf
淑徳与野中学校(埼玉県) https://mirai-compass.net/usr/shukuynj/event/evtIndex.jsf

だったら、
http://localhost:3000/?school=seiritzj&school=kariakej&school=kokushih&school=josogj&school=shukuynj
というURLにアクセスする。

最初、どっかのサーバーにおいてサービス提供するかなと思ったけど、「サービス落ちてて説明会予約できなかった!(怒)」とか言われるとヤダなーとか思いつつ作った。

Laravel-AdminLTEを使ってみたメモ

AdminLTEを使いたかったのでこちらを使った。以前は違うの使ってた

インストールとかは簡単なので略

@extends('adminlte::master')

adminlte::は、ServiceProviderで付け加えてるっぽい。

vendor/jeroennoten/laravel-adminlte/src/ServiceProvider.php
<?php
    private function loadViews()
    {
        $viewsPath = $this->packagePath('resources/views');

        $this->loadViewsFrom($viewsPath, 'adminlte');

のadminlteがこのパッケージ(jeroennoten/Laravel-AdminLTE)のresources/viewsを使うPrefixになるみたい。

リンク先とかFormのAction先の変更

あとは、URLとかrouteを変えたら

{{ url(config('adminlte.dashboard_url', 'home')) }}

とかあるんだけど、これは固定なのでポチポチと変更をしていく。んだけど、これって

{{ route('user.login') }}

とかのRouteのname使った方が良いんじゃないかな?

Laravelのauthを追ってみたメモ

$artisan make:auth
したという前提で。

Laravel5.4でマルチ認証(userとadmin)を実装する方法 | 大分のITコンサルタント | 高橋商店 というのをやってみて、認証の中で振り分けをしたかったというケース。

普通にファーストアクセスで認証必要なURLにアクセスした場合

URL→routes→Controllerなので…

最初はController

認証を掛けたいController内でAuthを呼ぶ。

<?php
class UserController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:user,user1,user2,user3');
    }

ってやると、MiddleWareで登録されたauthが起動する。

app/Http/Kernel.php

app/Http/Kernel.php
<?php
protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,

のauthにあるauthとさっきの$this->middleware('authのauthが対応している。auth:user, user1,user2,user3のuser1,user2,user3はMiddleWare(この場合Authenticate)の$guards引数になる。

ちなみに、$routeMiddleWareというのは$middleWareと違ってControllerでこうやって$this->middleware()して任意に起動させるもので、$middleWareは全てのrouteで起動する。

Authenticate.php

で、この起動するインスタンスは app/Http/Middleware/Authenticate.php なので、このインスタンスのhandleメソッドが実行される。

これは、Illuminate\Auth\Middleware\Authenticateを継承しているので、

Illuminate\Auth\Middleware\Authenticate
<?php
    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($request, $guards);

        return $next($request);
    }

がメソッド。で、この$guardsがさっきの$this->middleware('auth:user,user1,user2,user3')した引数になってて、

<?php
$guards = ['user', 'user1', 'user2', 'user3', ];

となっている。

Illuminate\Auth\Middleware\Authenticate

この $guardsはIlluminate\Auth\Middleware\Authenticate->authenticate()で使われて、

Illuminate\Auth\Middleware\Authenticate
<?php
    protected function authenticate($request, array $guards)
    {
        if (empty($guards)) {
            $guards = [null];
        }

        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }
        }

        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

となっている。このforeach($guardsに $guards = ['user', 'user1', 'user2', 'user3', ]; が入ってくる。この場合4つのGuardsを使うことになる。

で、この$this->authは Illuminate/Auth/AuthManager.phpインスタンス

Illuminate\Auth\AuthManager と config/auth.php

ここで、check()メソッドを持つインスタンスをconfig/auth.phpに基づいて返す。

auth.phpでは、'guards' => に入っているもので

config/auth.php
<?php
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],
   ///////////////////////
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'user' => [
          'driver' => 'session',
          'provider' => 'custom_users',
        ],
   ///////////////////////
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

となっているので、$this->middleware('auth:user')とすると、driverにsession/providerにcustom_usersを使ったものがcheck()を実行する。
ちなみに、$this->middleware('auth')だと、defaultsのguardは'web'なのでこれまたdriverにsession/providerにusersを使ったものが実行される。

AuthのProviderは、Modelとつながっている

と、ここまでで、普通にControllerで$this->middleware('auth')としたときにはguardsのwebが呼ばれて、そのコードはproviderで示されるusersなのである。

で、Illuminate\Auth\Middleware\Authenticate::authenticate()の

Illuminate\Auth\Middleware\Authenticate::authenticate()
<?php
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }

では、$this->authがIlluminate\Auth\AuthManagerなので、そのguardメソッドを見るとAuthManager::resolve()で$guardに沿ったインスタンスを作っている。

'web'の場合、driverがsessionなのでAuthManager::createSessionDriver()である。これは$this->auth->guard($guard) がIlluminate\Auth\SessionGuardを作っている。

このSessionGuardインスタンスは、以下の経緯で作られる$providerを持っている。

Illuminate\Auth\CreatesUserProviders::createUserProvider($provider)   
で、$providerがeloquentであり、特に$providerに対するカスタムなCreatorが無い   
  ↓  
Illuminate\Auth\CreatesUserProviders::createEloquentProvider($config)   
これはEloquentUserProviderインスタンスをnewする。  
  ↓  
new EloquentUserProvider($this->app['hash'], $config['model']);  

また、SessionGuardインスタンスは、識別子$name(この場合は'user')と$sessionと(Http)Request $requestを持っている。

Illuminate\Auth\SessionGuard のcheck()

は、Illuminate\Auth\GuardHelpers::check()のtraitで、SessionGuard::user()の返り値がNULLでないかをチェックしている。
このuser()メソッドは

  • ログアウトのタイミングならnull
  • SessionGuard::$user (Illuminate\Contracts\Auth\Authenticatable) があればそれを返す。これは既にログイン処理がされている場合にはSessionからRestoreされるっぽい。

とかまぁなんかそんな感じ。

ここまでで、このSessionGuard::$userが得られれば認証通過。

ログインが認可されているセッションCOOKIEを持っている場合

SessionGuard::user()で$this->userを作成する処理を行う。

Illuminate\Auth\SessionGuard::user()
<?php
        if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
            $this->fireAuthenticatedEvent($this->user);
        }

の部分。以降では逐一DBにアクセスする必要は無くなる。

「永続ログイン」のチェックを入れてログインしていると、

Illuminate\Auth\SessionGuard::user()
<?php
        if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
            $this->user = $this->userFromRecaller($recaller);

の部分で$this->userの復帰をしてくれる。

LaravelはSessionを自前で実装している。

…とここまで読んで、LaravelはPHPの生のセッション機能を使ってなくて独自でSession構築しているということに気が付いた。えー。秘密情報をCOOKIEに入れてるのかな?…あれ?これ以前も「すげー」って思った記憶あるな…いや、それはRailsだったっけ?Laravelのデフォルトだとセッション情報はファイルで storage/framework/sessions に保存してる。

ログインの時の処理。

DBに接続する処理があるのはここの部分だけですね。

Auth\LoginController@login

メソッドとしてはこのメソッドなので
Illuminate\Foundation\Auth\AuthenticatesUsers::login()を見に行く。

Illuminate\Foundation\Auth\AuthenticatesUsers
<?php
   public function login(Request $request)
    {
        $this->validateLogin($request);

        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }

        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

validateLogin()

ここで、ユーザー名とパスワードの入力チェック。独自のチェックを入れたかったら、ControllerでOverrideすればいい。

attemptLogin()

hasTooManyLoginAttempts()は、多重ログインPOSTの防止かな。あまり読んでない。
ログイン処理のメインはattemptLogin()で

Illuminate\Foundation\Auth\AuthenticatesUsers
<?php
    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

で、guard()で得たインスタンス(SessionGuard)のattempt()を実行する。これは

Illuminate\Auth\SessionGuard
<?php
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

        if ($this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);

            return true;
        }

DBにつなぐところは、 $this->provider->retrieveByCredentials()の所。このproviderはlluminate\Auth\EloquentUserProviderである。このretrieveByCredentialsメソッドが

lluminate\Auth\EloquentUserProvider
<?php
   public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &&
            array_key_exists('password', $credentials))) {
            return;
        }

        // First we will add each credential element to the query as a where clause.
        // Then we can execute the query and, if we found a user, return it in a
        // Eloquent User "model" that will be utilized by the Guard instances.
        $query = $this->newModelQuery();

        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }

            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }

となっている。newModelQuery()でQueryを取っているが、このORMはauth.phpに記述した 'providers' => ['users' => [ のmodelのApp\User::classとなる。

ちなみにこの定義が無いとデフォルトのグローバルの\Illuminate\Database\Eloquent\Builderがnewされて作られる。…が、ほとんど意味が無いだろう。テーブル持って無いし。ちゃんと定義しろということですね。これで、メールアドレスを指定でDBから1行を抜き出す。

で、その後は更にhasValidCredentials()に流れて、

Illuminate\Auth\SessionGuard
<?php
    protected function hasValidCredentials($user, $credentials)
    {
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }

で、この$this->providerはIlluminate\Auth\EloquentUserProviderのインスタンスで、validateCredentialsでパスワードと比較している。

Illuminate\Auth\EloquentUserProvider
<?php
    public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }

ちなみに、この時UserNameとして何を使いたいかというと、Illuminate\Foundation\Auth\AuthenticatesUsersのusername()メソッドをControllerで上書きすればよい。デフォルトだと以下のようにemailが使われる。

Illuminate\Foundation\Auth\AuthenticatesUsers
<?php
    public function username()
    {
        return 'email';
    }

ログインに失敗した時

単純に権限のない所に入ってしまった場合

これは、Illuminate\Auth\Middleware\Authenticate->authenticate()で例外AuthenticationExceptionを投げられる。

これは、app\Exceptions\Handler::unauthenticated() でキャッチされる。

app\Exceptions\Handler
<?php
    protected function unauthenticated($request, AuthenticationException $exception)
    {
      if ($request->expectsJson()) {
        return response()->json(['error' => 'Unauthenticated.'], 401);
      }

      //追加
      if (in_array('admin', $exception->guards())) {
        return redirect()->guest('admin/login');
      }
      //追加

      return redirect()->guest('login');
    }

エラーハンドリング

で、なんでAuthenticationException エラーの時だけunauthenticatedメソッドが使われるかというと、最終的にはapp\Exceptions\Handler::render()でキャッチされるのだけど、 Illuminate\Foundation\Exceptions\Handlerで、

Illuminate\Foundation\Exceptions\Handler
<?php
    public function render($request, Exception $e)
    {
        if (method_exists($e, 'render') && $response = $e->render($request)) {
            return Router::toResponse($request, $response);
        } elseif ($e instanceof Responsable) {
            return $e->toResponse($request);
        }

        $e = $this->prepareException($e);

        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof AuthenticationException) {
            return $this->unauthenticated($request, $e);
        } elseif ($e instanceof ValidationException) {
            return $this->convertValidationExceptionToResponse($e, $request);
        }

        return $request->expectsJson()
                        ? $this->prepareJsonResponse($request, $e)
                        : $this->prepareResponse($request, $e);
    }

となっていて、AuthenticationExceptionの例外の場合はunauthenticated()メソッドが実行されるため。

権限のないところに入ったら、所定のログイン場所に飛ばしたい

投げられる認証の例外AuthenticationExceptionのnewはapp\Http\Middleware\Authenticateの継承元である

Illuminate\Auth\Middleware\Authenticate
<?php
   protected function authenticate($request, array $guards)
    {
        // 省略
        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

なので、$this->redirectTo($request)がキーになるので、app\Http\Middleware\AuthenticateのredirectTo先を変更してやると良い。

…が、このMiddlewareの$thisは自分がadminなのかuserなのかが分からないので、どこにredirectするべきかが分からない…

というのは、

Illuminate\Auth\Middleware\Authenticate
<?php
    protected function authenticate($request, array $guards)
    {
        /// 省略
        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

とnewしているところで$guardsを渡してくれてない…

ということで、Http\Middleware\Authenticateにguardsを渡してその先頭を取る

どのguardsで引っかかったかというのはコアのAuthenticateでは取れないので、

app\Http\Middleware\Authenticate
<?php
    /**
     * @var Array
     */
    private $guards ;

    public function handle($request, Closure $next, ...$guards)
    {
        $this->guards = $guards;

として、handle時に持たせてやり、configのauthにredirectoToを生やして

config/auth.php
<?php
   'guards' => [
        'user' => [
            'driver' => 'session',
            'provider' => 'users',
            'redirectTo' => 'user.login'
        ],

redirectoToで使用した。

app\Http\Middleware\Authenticate
<?php
    protected function redirectTo($request)
    {
        $redirectTo = sprintf('auth.guards.%s.redirectTo', current($this->guards));
        $redirectTo = \Config::get($redirectTo) ?? '';
        if (!$request->expectsJson()) {
            return route($redirectTo);
        }
    }

んー。まぁ、こんな感じで。

Auth::routes()でのRouteの追加

routes/web.phpに Auth::routes(); を記載すると、Illuminate\Routing\Router->auth()が実行される。

Illuminate\Routing\Router
<?php
    public function auth(array $options = [])
    {
        // Authentication Routes...
        $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
        $this->post('login', 'Auth\LoginController@login');
        $this->post('logout', 'Auth\LoginController@logout')->name('logout');

        // Registration Routes...
        if ($options['register'] ?? true) {
            $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
            $this->post('register', 'Auth\RegisterController@register');
        }

        // Password Reset Routes...
        if ($options['reset'] ?? true) {
            $this->resetPassword(); // このメソッドで以下の4つ追加
//        $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
//        $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
//        $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
//        $this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update');
        }

        // Email Verification Routes...
        if ($options['verify'] ?? false) {
            $this->emailVerification(); // このメソッドで以下の3つ追加
//        $this->get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
//        $this->get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');
//        $this->get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
        }
    }

となっているので、手動で追加したい場合はこのrouteを記載する。

Login時のSessionGuardをうまく働かせる

ログインした状態で認証をパスする場合、Illuminate\Auth\SessionGuard->user()でセッションを見るんだけど、この場合

$this->middleware('auth:user')

で保存しなければならない。チェックする時が「auth:user」でなので。これを指定せずにいると、デフォルトのwebが使われてセッションに保存されてしまって認証状態ではないと判断してしまう。

SessionGuard::login() でSessionにデータを保存する処理を追ってみる

結局、SessionGuardをnewする所で$nameを与えているので、

Illuminate\Foundation\Auth\AuthenticatesUsers.php
<?php
    protected function guard()
    {
        return Auth::guard();
    }

ここで$nameを与えてないので仕方ない…

ということで、このAuth::gurad($name)っていう感じのClassを作ってOverrideする。

app\Http\Controllers\Auth\LoginController
<?php
// use Illuminate\Foundation\Auth\AuthenticatesUsers
class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $guard_name = 'user';

とControllerで$nameを指定できるようにしオリジナルのを使わないようにして、自前のAuthenticatesUsersを作る。

で、同じディレクトリに

app\Http\Controllers\Auth\AuthenticatesUsers
<?php
namespace App\Http\Controllers\Auth;

use Illuminate\Foundation\Auth\AuthenticatesUsers as AuthenticatesUsersOrig;
use Illuminate\Support\Facades\Auth;

trait AuthenticatesUsers
{
    use  AuthenticatesUsersOrig;

    protected function guard()
    {
        return Auth::guard($this->guard_name);
    }
}

とやっておく。