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
↑わかりやすいですね。このおかげですぐわかりました。
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とかの役割がまだ完全には理解できてないです。。