AcegiプラグインのRememberMeで敗者復活を果たしたセッションの認可にはIS_AUTHENTICATED_REMEMBEREDを使う

ちょっとハマった。

RememberMeでの敗者復活セッションはちょっとランクが低い

RememberMeで復活したセッション状態(長いので以降ではRememberMeセッションと呼ぶ)に対して認可するには

  • IS_AUTHENTICATED_REMEMBERED

というAuthenticatedVoterをRequestMapに追加しなければならなかったようです。

□AuthenticatedVoter:

most strict checking
 ↑
IS_AUTHENTICATED_FULLY
IS_AUTHENTICATED_REMEMBERED
IS_AUTHENTICATED_ANONYMOUSLY
 ↓
least strict checking

http://8318.blog100.fc2.com/blog-category-15.html

↑わかりやすいですね。このおかげですぐわかりました。


RememberMeでの敗者復活セッションはちょっとランクが下がります。
通常のログイン状態に対応するIS_AUTHENTICATED_FULLYにはパスできなくなるので、RememberMe導入前に「とりあえずログインしたら使っていいよ」っていう意味で、RequestMapでIS_AUTHENTICATED_FULLYとしてに登録していたリクエストパスは、RememberMeセッションでは全滅になります。
この場合、そのパスにリクエストすると内部でAccess Denied判定されて、SecurityConfig.groovyのerrorPageにリダイレクトされます*1


RememberMeセッションでも通常のログインと同じように扱いたければ、たとえばSecurityConfig.groovy上のrequestMapStringを使っている場合は、

/hoge/**=IS_AUTHENTICATED_FULLY
  ↓↓↓
/hoge/**=IS_AUTHENTICATED_FULLY,IS_AUTHENTICATED_REMEMBERED

とカンマ区切りでくっつけてあげればOKです。
これでRememberMeセッションも仲間はずれにならなくなります。


この仕組みをうまく使えば、購買処理の場合などで、カート追加はRememberMeセッションでもOKだけど、最終的にもう一度ログイン認証を受けないとだめ、みたいなことを実現できますね。というか、そのための仕様ですね。


気がついてみれば至極まっとうな仕様です。
でも、これを知らないと、「なんでやねん!Acegiのセッション判定くさってんちゃうか!」と逆ギレになりかねないので注意しましょう。

Acegiのログ出力

この辺りをデバッグするために、Acegiプラグインのロガーをdebugレベルにしましたので、その方法を書いておきます。

grails-app/conf/SecurityConfig.groovy

以下を追加します。元々書いてあるなら、false→trueに。

    /** LoggerListener 
     * ( add 'log4j.logger.org.springframework.security=info,stdout'
     * to log4j.*.properties to see logs )
     */
    useLogger = true
grails-app/conf/Config.groovy

上記のコメントにある通り

'log4j.logger.org.springframework.security=info,stdout'

を追加します。
今回だと、debugレベルにすれば色々ログ出力されてきます。

2009-3-01 11:57:20,986 [DEBUG] (intercept.AbstractSecurityInterceptor) Secure object: FilterInvocation: URL: /hoge/delete; ConfigAttributes: [IS_AUTHENTICATED_FULLY]
2009-3-01 11:57:20,987 [DEBUG] (intercept.AbstractSecurityInterceptor) Previously Authenticated: org.springframework.security.providers.rememberme.RememberMeAuthenticationToken@46ec9b4e: Principal: org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserImpl@2f10c00: Username: my_user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Password: [PROTECTED]; Authenticated: true; Details: org.springframework.security.ui.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_USER
2009-3-01 11:57:20,987 [DEBUG] (ui.ExceptionTranslationFilter) Access is denied (user is not anonymous); delegating to AccessDeniedHandler
org.springframework.security.AccessDeniedException: Access is denied
        at org.springframework.security.vote.AffirmativeBased.decide(AffirmativeBased.java:68)
        at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:262)
〜以下略〜

↑RememberMeセッションがAccess Deinied判定されるの図。

[追記]

RequestMapでロール名を直接指定していれば、RememberMeセッションであっても問題なく認可にパスできますね。
ロール名が少ない場合はこっちがわかりやすくておすすめかも。

/hoge/**=ROLE_USER,ROLE_ADMIN,ROLE_HOGE
/foo/**=ROLE_ADMIN,ROLE_FOO
/**=IS_AUTHENTICATED_ANONYMOUSLY

という感じで。

*1:たぶん。実はLoginControllerとかLogoutControllerとかの役割がまだ完全には理解できてないです。。