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

舞台裏

Qiita が表でこっちが裏。こっそりやっていく。

Remember-Me のログイン時のパラメータの謎について追う

Remember-Me 認証でログインするときに、フォームには <input type="checkbox" name="remember-me" /> を追加しておけばいい。

けど、 value 属性を省略した場合って、なんの値が渡っているのだろう?

実際に動かしたら、 on という値が渡っていた。

<input type="checkbox"> - HTML | MDN

value
(略)省略された場合は、要素の value プロパティの取得結果は文字列の “on” になります。

一応 value を省略したら on がデフォルトになるらしい。

でも、携帯とかだと機種によって値が違う みたいな情報もあった。

Spring Security はどうやって値を取得しているのだろう?

ログイン時に Remember-Me を有効にするかどうかを判定しているところを探す。

<remember-me> タグの remember-me-parameter 属性でリクエストパラメータの値を変更できるので、これを使っているところを追えばおのずとログイン時の処理の場所を特定できそう。

spring-security-config-4.2.1.RELEASE.jar の中を remember-me-parameterGREP 検索する(IntelliJ だと jar の中を GREP できるので便利)

RememberMeBeanDefinitionParser というのが唯一引っ掛かった。

class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
    ...
    static final String ATT_FORM_REMEMBERME_PARAMETER = "remember-me-parameter";

この定数を使っているところを追うと、同じクラスの以下の場所にたどり着いた。

           if (remembermeParameterSet) {
                services.getPropertyValues().addPropertyValue("parameter",
                        remembermeParameter);
            }

なんか、 servicesparameter プロパティに設定している。

services が何なのか上の方を辿ると、

        if (isPersistent) {
            Object tokenRepo;
            services = new RootBeanDefinition(
                    PersistentTokenBasedRememberMeServices.class);

            ...
        }
        else if (!servicesRefSet) {
            services = new RootBeanDefinition(TokenBasedRememberMeServices.class);
        }

となってた。
つまり、 PersistentTokenBasedRememberMeServicesTokenBasedRememberMeServices ということになる。

共通の親クラスである AbstractRememberMeServices を見ると、 parameter がいた。

public abstract class AbstractRememberMeServices implements RememberMeServices,
        ...
    public static final String DEFAULT_PARAMETER = "remember-me";

        ...
    private String parameter = DEFAULT_PARAMETER;

間違いなさそう。

この parameter を使ってる場所を見てみる。

   public final void loginSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication successfulAuthentication) {

        if (!rememberMeRequested(request, parameter)) {
            logger.debug("Remember-me login not requested.");
            return;
        }

        onLoginSuccess(request, response, successfulAuthentication);
    }

rememberMeRequested(request, parameter) ってところが、完全に Remember-Me を有効にすべきかどうか判定しているっぽい。

false なら何もせずに return し、 true なら onLoginSuccess() 呼んでるので、おそらく間違いない。

rememberMeRequested() を見てみる。

    protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
        if (alwaysRemember) {
            return true;
        }

        String paramValue = request.getParameter(parameter);

        if (paramValue != null) {
            if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
                    || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
                return true;
            }
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Did not send remember-me cookie (principal did not set parameter '"
                    + parameter + "')");
        }

        return false;
    }

泥臭いことしてた!

学び

  • Spring Security はチェックボックスvalue が省略された場合の値がブラウザによって異なることを知っているので、可能性を全て網羅する形で値をチェックしている!
  • 逆に言うと、あらゆるブラウザのチェックボックスのデフォルト value に対応しようと思った場合は、上のようにすればいい