powerdee.com
Google
 
このサイト内 Web
 
カウンタ

AcegiSecurityで認証する

Acegi Securityとは

・宣言的なセキュリティサービスを実現
 (ソースコードに修正を加えずに認証・認可機能を実装可能)
・Springフレームワークの公式サブプロジェクト

機能(一部のみ抜粋)

・認証はデータベース、LDAP等に対応。
・メソッドやURLに対する認可機能(AOP、Filterで実装している)
・JSPサポート。タグライブラリで権限毎に設定可能。

Acegi Security設定ファイルのサンプル

/WEB-INF/appContext-security.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC 
   "-//SPRING//DTD BEAN//EN" 
   "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  
  <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
    <property name="providers">
      <list>
        <ref local="daoAuthenticationProvider"/>
        <bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
          <property name="key" value="changeThis"/>
        </bean>
        <bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
          <property name="key" value="changeThis"/>
        </bean>
      </list>
    </property>
  </bean>

  <bean id="daoAuthenticationProvider" 
    class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
    <property name="userDetailsService" ref="userDetailsService"/>
    <property name="userCache">
      <bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
        <property name="cache">
          <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
            <property name="cacheManager">
              <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
            </property>
            <property name="cacheName" value="userCache"/>
          </bean>
        </property>
      </bean>
    </property>
  </bean>

  <bean id="userDetailsService"
    class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
    <property name="dataSource" ref="dataSource"/>
    <property name="usersByUsernameQuery">  ・・・・・・ ※ usersByUsernameQuery
      <value>
        select admin_id, password, '1'
        from   admin_user
        where  delete_date is null and admin_id = ?
      </value>
    </property>
    <property name="authoritiesByUsernameQuery">  ・・・・・・ ※ authoritiesByUsernameQuery
      <value>
        select admin_id, 'ROLE_'||auth_id
        from   user_auth
        where  delete_date is null and admin_id = ?
      </value>
    </property>
  </bean>

  <bean id="filterChainProxy" 
    class="org.acegisecurity.util.FilterChainProxy">  ・・・・・・ ※ FilterChainProxy
    <property name="filterInvocationDefinitionSource">
      <value>
          CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
          PATTERN_TYPE_APACHE_ANT
          /**=httpSessionContextIntegrationFilter,  <-- 実際は一行で記述
             logoutFilter,authenticationProcessingFilter,
             securityContextHolderAwareRequestFilter,
             rememberMeProcessingFilter,
             anonymousProcessingFilter,
             exceptionTranslationFilter,
             filterInvocationInterceptor
      </value>
    </property>
  </bean>

  <bean id="httpSessionContextIntegrationFilter" 
    class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>

  <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
    <constructor-arg value="/index.jsp"/> <!-- URL redirected to after logout -->
    <constructor-arg>
      <list>
        <ref bean="rememberMeServices"/>
        <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
      </list>
    </constructor-arg>
  </bean>

  <bean id="authenticationProcessingFilter"   ・・・・・・ ※ AuthenticationProcessingFilter
    class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="authenticationFailureUrl" value="/admin/auth/LoginForm.jsp?login_error=1"/>
    <property name="defaultTargetUrl" value="/"/>
    <property name="filterProcessesUrl" value="/admin/auth/j_acegi_security_check"/>
    <property name="rememberMeServices" ref="rememberMeServices"/>
  </bean>
   
  <bean id="securityContextHolderAwareRequestFilter" 
    class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>

  <bean id="rememberMeProcessingFilter" 
    class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="rememberMeServices" ref="rememberMeServices"/>
  </bean>

  <bean id="anonymousProcessingFilter" 
    class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
    <property name="key" value="changeThis"/>
    <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
  </bean>

  <bean id="exceptionTranslationFilter" 
    class="org.acegisecurity.ui.ExceptionTranslationFilter">   ・・・・・・ ※ ExceptionTranslationFilter
    <property name="authenticationEntryPoint">
      <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
        <property name="loginFormUrl" value="/admin/auth/LoginForm.jsp"/>
        <property name="forceHttps" value="false"/>
      </bean>
    </property>
    <property name="accessDeniedHandler">
      <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
        <property name="errorPage" value="/admin/auth/failure.jsp"/>
      </bean>
    </property>
  </bean>

  <bean id="filterInvocationInterceptor" 
    class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="accessDecisionManager">  ・・・・・・ ※ accessDecisionManager
      <bean class="org.acegisecurity.vote.AffirmativeBased">
        <property name="allowIfAllAbstainDecisions" value="false"/>
        <property name="decisionVoters">
          <list>
            <bean class="org.acegisecurity.vote.RoleVoter"/>
            <bean class="org.acegisecurity.vote.AuthenticatedVoter"/>
          </list>
        </property>
      </bean>
    </property>
    <property name="objectDefinitionSource">  ・・・・・・ ※ objectDefinitionSource
      <value>
        CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
        PATTERN_TYPE_APACHE_ANT
        /admin/member/**=ROLE_member-adm
        /admin/campaign/**=ROLE_campaign-adm
        /admin/auth/**=IS_AUTHENTICATED_ANONYMOUSLY
      </value>
    </property>
  </bean>

  <bean id="rememberMeServices" 
    class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
    <property name="userDetailsService" ref="userDetailsService"/>
    <property name="key" value="changeThis"/>
  </bean>

  <!-- This bean is optional; it isn't used by any other bean as it only listens and logs -->
  <bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>

</beans>

設定内容の説明

※ usersByUsernameQuery
PreparedStatement形式でwhere句でログインIDをバインド変数に指定する。
select句はログインID、パスワード、有効フラグ(true|false or 1|0)

※ authoritiesByUsernameQuery
PreparedStatement形式でwhere句でログインIDをバインド変数に指定する。
select句はログインID、ロール名(但しプレフィックス「ROLE_」で始まる)

※ FilterChainProxy
フィルターの実行順序。FilterChainProxyは、web.xmlのFilterToBeanProxyクラスから呼び出される。

※ AuthenticationProcessingFilter
authenticationFailureUrlは、ログイン失敗時に遷移するURL
defaultTargetUrlは、ログイン成功時のデフォルトの遷移先URL
filterProcessesUrlは、ログイン画面のフォーム送信先URL

※ ExceptionTranslationFilter
AccessDeniedExceptionをキャッチし、AuthenticationException(匿名ユーザであった場合)を検出したら、
authenticationEntryPointを開始する。この設定では、ログインフォームに遷移させる。
既に認証済みのユーザであった場合、AccessDeniedExceptionに委任するが、
後述のAccessDeniedHandlerImplにより、アクセスエラーページに遷移するようにしている。
(AuthenticationProcessingFilterを継承して独自の認証処理を実装したときなどに上記例外をスローさせるのかな?)

※ accessDecisionManager
指定されたリソースへのアクセス権限を判定する。
実装はいくつかあるが、サンプルでは AffirmativeBasedクラスを設定している。
AccessDecisionVoterは、RoleVoterとAuthenticatedVoterが実装。

・AffirmativeBased
認可不可と判定したAccessDecisionVoterオブジェクトが1つでもある場合は、認可不可となる。
AccessDecisionVoterオブジェクト内での判定処理では、Authenticationオブジェクトが保持する
ロールの中にAccessDecisionVoterオブジェクトに指定したロールが1つでもあれば認可可能になる。

・UnanimousBased
認可不可と判定したAccessDecisionVoterオブジェクトが1つでもある場合は、認可不可となる。
AccessDecisionVoterオブジェクト内での判定処理では、AccessDecisionVoterオブジェクトに
指定した全ロールが、Authenticationオブジェクトに保持されている場合に限り認可可能になる。

・ConsensusBased
各AccessDecisionVoterオブジェクトに対して認可可能か否かを問合せ、多数決にて決定する。
AccessDecisionVoterオブジェクト内での判定処理では、Authenticationオブジェクトが保持するロールの中に、
AccessDecisionVoterオブジェクトに指定したロールが1つでもあれば認可可能となる。

※ objectDefinitionSource
保護対象となるリソースを設定する。
下記の指定により大文字小文字を区別せず、Antで使用可能なパターン文字が使えるようになる。

CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT

コンテキストパス以下のパスに対して、必要なロールを定義する。

(サンプル)
/secure/extreme/**=ROLE_SUPERVISOR
/secure/**=IS_AUTHENTICATED_REMEMBERED
/**=IS_AUTHENTICATED_ANONYMOUSLY

IS_AUTHENTICATED_REMEMBEREDは、BASE64でクッキーに保存されたユーザ
IS_AUTHENTICATED_ANONYMOUSLYは、匿名ユーザ


web.xml設定サンプル

以下は、最低限必要な設定個所のみ。

<web-app>

  <!-- ContextLoaderListenerで読み込まれるBean定義ファイル。-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      /WEB-INF/appContext-security.xml
    </param-value>
  </context-param>

  <!-- filterの設定 -->
  <filter>
    <filter-name>Acegi Filter Chain Proxy</filter-name>
    <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
    <init-param>
      <param-name>targetClass</param-name>
      <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>Acegi Filter Chain Proxy</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- Spring ContextLoaderLister -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

</web-app>

ログインフォームサンプル

/admin/auth/LoginForm.jsp

<%@ page contentType="text/html;charset=Shift_JIS" %>
<%@ page import="org.acegisecurity.ui.AbstractProcessingFilter" %>
<%@ page import="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter" %>
<%@ page import="org.acegisecurity.AuthenticationException" %>

<html>
  <head>
    <title>ログイン画面</title>
  </head>

  <body>
    <h1>ログイン画面サンプル</h1>

    Reason: <%= ((AuthenticationException) session.getAttribute
                (AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %>

    <form action="j_acegi_security_check" method="POST">
      <table>
        <tr><td>User:</td><td><input type="text" name="j_username" size="30" ></td></tr>
        <tr><td>Password:</td><td><input type="password" name="j_password"></td></tr>
        <tr><td><input type="checkbox" name="_acegi_security_remember_me"></td>
            <td>パスワードを保存(2週間)</td></tr>
        <tr><td colspan='2'><input name="submit" type="submit"></td></tr>
        <tr><td colspan='2'><input name="reset" type="reset"></td></tr>
      </table>

    </form>

  </body>
</html>

認証処理のカスタマイズ

ID、パスワードによる認証処理はAuthenticationProcessingFilterで行われていますが、 独自の処理を追加したい場合は、親クラスであるorg.acegisecurity.ui.AbstractProcessingFilterを継承したクラスで実装可能です。

以下のサンプルはカスタマイズされたログインID、パスワードおよびURLをパラメータで受け取り、 ログインID、パスワードで認証後にパラメータで指定したURLに遷移します。

import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.ui.AbstractProcessingFilter;
import org.apache.commons.validator.GenericValidator;

import com.powerdee.exception.CustomAuthenticationException;

public class HotspotAuthenticationProcessingFilter extends
        AbstractProcessingFilter {

    public static final String LOGIN_ID_KEY = "loginid";
    public static final String PASSWORD_KEY = "password";
    public static final String TARGET_URL = "url";
    public static final String ACEGI_SECURITY_LAST_USERNAME_KEY 
        = "ACEGI_SECURITY_LAST_USERNAME";

    public Authentication attemptAuthentication(HttpServletRequest request)
        throws AuthenticationException, AccessDeniedException {
        String loginId = request.getParameter(LOGIN_ID_KEY);
        String password = request.getParameter(PASSWORD_KEY);
        String targetUrl = request.getParameter(TARGET_URL);

        // 必須パラメータチェック
        if (GenericValidator.isBlankOrNull(loginId) ||
                GenericValidator.isBlankOrNull(password) ||
                GenericValidator.isBlankOrNull(targetUrl)) {
            throw new CustomAuthenticationException("必須パラメータが設定されていません。 ");
        }

        // ログインID、パスワードによる認証チェック
        UsernamePasswordAuthenticationToken authRequest 
            = new UsernamePasswordAuthenticationToken(loginId, password);

        // Place the last username attempted into HttpSession for views
        request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, loginId);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        
        // 遷移先URLをセットする。
        setDefaultTargetUrl(targetUrl);

        return this.getAuthenticationManager().authenticate(authRequest);
    }
    
    /**
     * /custom_security_checkがリクエストされると当フィルターが応答します。
     *
     * @return the default
     */
    public String getDefaultFilterProcessesUrl() {
        return "/custom_security_check";
    }
    
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    /**
     * Provided so that subclasses may configure what is put into 
     * the authentication request's details property.
     *
     * @param request that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details set
     */
    protected void setDetails(HttpServletRequest request, 
        UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
}

CustomAuthenticationExceptionは、org.acegisecurity.AuthenticationExceptionを継承した独自クラスです。 認証失敗時には、この例外をスローさせると、設定ファイルで指定したauthenticationFailureUrlへフォワードされます。

フィルターの初期化処理

filterChainProxyより呼び出される各フィルターはデフォルトだと、Springより生成されるためinitメソッドが実行されません。 これを回避するには、以下のようにweb.xmlでlifecycleパラメータを指定することでコンテナでのライフサイクルとなり、initメソッドも実行されるようになります。

    <!-- filter -->
    <filter>
        <filter-name>Acegi Filter Chain Proxy</filter-name>
        <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
        <init-param>
            <param-name>targetClass</param-name>
            <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
        </init-param>
        <init-param>
            <param-name>lifecycle</param-name>    <--このパラメータを追加する
            <param-value>servlet-container-managed</param-value>
        </init-param>
    </filter>


おすすめ書籍


SpringによるWebアプリケーションスーパーサンプル

著者:村山 雅彦、阪田 浩一、奥 清隆
出版社:ソフトバンククリエイティブ(2006-07-29)
価格:¥3,990(税込)
Light Weight Java―JSF/Hibernate/SpringによるフレームワークでWebアプリケーションの開発効率向上

著者:岡本 隆史、金子 崇之、吉田 英嗣、権藤 夏男
出版社:毎日コミュニケーションズ(2005-04)
価格:¥3,360(税込)
Apache Maven 2.0入門 Java・オープンソース・ビルドツール

著者:野瀬 直樹、横田 健彦
出版社:技術評論社(2006-12-13)
価格:¥2,499(税込)
Spring2.0入門 Java・オープンソース・Web開発自由自在

著者:株式会社豆蔵、長谷川 裕一、岩永 寿来、伊藤 清人、大野 渉、麻野 耕一
出版社:技術評論社(2006-12-28)
価格:¥3,654(税込)


ページTopへ / ▲Homeへ