Top > MyPage
 

Spring Securityをつかってみる

Spring Security とは

Spring Frameworkのサブプロジェクトである「The Acegi Security System for Spring」(以下、Acegi Securityとする)がバージョン2から名前を変えた。それが「Spring Security」である。

今まで、言語にJavaを用いたWEBアプリの構築では認証フレームワークとしてAcegi Securityを用いてきたが、今やっているプロジェクトは複雑な認証は要らないので、今後のことも考え、Acegi SecurityではなくSpring Securityを使ってみることにした。

今回、参考にしたサイト

EclipseにSpring IDEを導入

Spring Securityになって大きく変わったのが、bean定義ファイルの記述である。Spring Security独自のXMLスキーマを用いるため、以前の面影を若干は残しつつも、もう、ほとんど別物といってよいほどかわった。

なんの手助けもなく、それを記述するのは非常に難儀なので、Spring IDEを導入して補完機能を利用する。これを書いている時点でのSpring IDEの最新版は2.2.4であった。

Spring IDE Update SiteをEclipseのソフトウェア更新のサイトに追加して、そのサイトのソフトウェアをインストールしてください。

柴田がインストールを試みたところ(Eclipseのバージョン3.4と3.3)、Integrationsパッケージが必須フィーチャー不足でインストールできなかったので、その他のパッケージを全てインストールした。

インストールがすんでから、Spring IDEを利用したいプロジェクトの右クリックメニューをだすと次の図のように[Spring Tools→Add Spring Project Nature]という項目があるはずである(日本語化されてると違うかもしれない)。

Spring プロジェクト・ネーチャーの追加

これを実行すると、そのプロジェクトがSpring IDEに認識される。ここで、プロジェクトのプロパティを表示すると、左側のツリーに[Spring]という項目が現れているはずである。

プロジェクトのプロパティ

[Beans Support]を選択して表示された画面で、bean定義ファイルを追加すると、そのファイルの編集で補完機能を使うことが出来るようになる。

bean定義ファイルを追加

maven のpomにdependencyを追加

プロジェクト構築のサポートにmavenを使い、依存関係をそちらで解決してもらう。

Spring Securityのプロジェクトはmaven2のセントラルリポジトリで見つけられる。

dependencyは次のように記述した。

        <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-core</artifactId>
          <version>2.0.4</version>
        </dependency>
        <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-core-tiger</artifactId>
          <version>2.0.4</version>
        </dependency>
        <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-taglibs</artifactId>
          <version>2.0.4</version>
        </dependency>
        <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-catalina</artifactId>
          <version>2.0.4</version>
        </dependency>

[spring-security-core-tiger]はjava1.5の場合は必要との記述を見かけたのだが、柴田が試したときは、java1.6、tomcat6.0だったが、dependencyに[spring-security-core-tiger]が無いとアプリケーションを起動することが出来なかった。

これら全てが必要ではないだろうが、とりあえず、このように記述したところ、問題なく動作した。

依存関係はmavenが解決してくれるので、これらが必要とするライブラリを気にする必要は無い。

bean定義ファイル

Spring Securityのbean定義ファイルとしてapplicationContext-security.xmlというファイルを用意した。

全文を次に示す。

<?xml version="1.0" encoding="Windows-31J"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:sec="http://www.springframework.org/schema/security"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">

  
  <sec:http auto-config="true" access-denied-page="/WEB-INF/template/accessDenied.vm">
     <sec:intercept-url pattern="/auth/**" access="IS_AUTHENTICATED_FULLY"/>
     <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

     <sec:form-login login-page="/login" 
      authentication-failure-url="/login?num=1"
      default-target-url='/showStaff'/>
      
     <sec:logout logout-url="/j_spring_security_logout"
      logout-success-url="/login" invalidate-session="true"/>
   </sec:http>
   <sec:global-method-security secured-annotations="enabled" jsr250-annotations="enabled"/>

   <sec:authentication-provider user-service-ref="jdbcDaoImpl" >
        <sec:password-encoder hash="md5"></sec:password-encoder>
   </sec:authentication-provider>

   <bean id="jdbcDaoImpl" class="org.springframework.security.userdetails.jdbc.JdbcDaoImpl">
        <property name="dataSource">
            <ref bean="dataSource"/>
        </property>
        <property name="usersByUsernameQuery">
            <value>
                SELECT staff_name,password,enabled 
                FROM staff 
                WHERE staff_name = ?
            </value>
        </property>
        <property name="authoritiesByUsernameQuery">
            <value>
               SELECT staff_name,role_name
               FROM staff_role 
               WHERE staff_name = ?
            </value>
        </property>
   </bean>

</beans>

Acegi Securityの頃と比べると、記述が大幅に少なくなり、スッキリしたことがわかる。

  • sec:httpでログインページのurlやログアウトのurl、認証後のデフォルト遷移先、認証失敗時の遷移先、認可のurlなどを設定している。auto-config="true"として自動設定を有効にしている。これがあるのでAcegi Securityの頃よりもbean定義ファイルの記述が簡便である。
  • sec:global-method-securityの設定で、メソッドやクラスに対して,アノテーションを利用して,権限のないユーザーからの直接アクセスを禁止できるようになる(らしいがまだ試していない)。
  • sec:authentication-providerの設定で、認証はDBを使い、パスワードチェックはmd5を使うようにしている。
  • jdbcDaoImplはAcegi Securityの頃からのおなじみである。usersByUsernameQueryとauthoritiesByUsernameQueryはテーブル名、カラム名がデフォルトどおりなら設定する必要は無い。

Acegi Securityのbean定義ファイル

passwordEncoderを独自定義して使用いているなど、構成が違うところがあるが、大まかには変わっていないので、参考までに、以前のプロジェクトのAcegi Securityのbean定義ファイルを次に示すので比較してみてほしい。

<?xml version="1.0" encoding="Windows-31J"?>

<!DOCTYPE beans PUBLIC
    "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">


<beans default-autowire="no" default-lazy-init="false" default-dependency-check="none">
  

    <!--Acegi Security-->
    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
                     CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                     PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,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="/action/login"/>
        <constructor-arg>
            <list>
                <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
            </list>
        </constructor-arg>
        <property name="filterProcessesUrl" value="/action/j_acegi_logout"/>
    </bean>



    <bean id="authenticationProcessingFilter"
        class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationFailureUrl" value="/action/login?num=1"/>
        <property name="defaultTargetUrl" value="/action/topMenu"/>
        <property name="filterProcessesUrl" value="/action/j_acegi_security_check"/>
    </bean>


    <bean id="filterInvocationInterceptor"
        class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="accessDecisionManager"/>
        <property name="objectDefinitionSource">
            <value>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /action/login=IS_AUTHENTICATED_ANONYMOUSLY
                /action/createuser=IS_AUTHENTICATED_ANONYMOUSLY
                /action/docreateuser=IS_AUTHENTICATED_ANONYMOUSLY
                /action/admin/**=ROLE_admin
                /action/admin/management/**=ROLE_super
                /action/admin/management/csv/*=ROLE_csv
                /admin/usrctl/csv/*=ROLE_csv
                /action/user/**=ROLE_user
                /action/*=ROLE_admin,ROLE_user
            </value>
        </property>
    </bean>

    <bean id="securityContextHolderAwareRequestFilter"
        class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>

    <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">
        <property name="authenticationEntryPoint">
            <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
                <property name="loginFormUrl" value="/action/login"/>
            </bean>
        </property>
        <property name="accessDeniedHandler">
            <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
                <property name="errorPage" value="/WEB-INF/template/accessDenied.vm"/>
            </bean>
        </property>
    </bean>

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref local="daoAuthenticationProvider"/>
            </list>
        </property>
    </bean>

    <bean id="daoAuthenticationProvider"
        class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
        <property name="userDetailsService">
            <ref local="jdbcDaoImpl"/>
        </property>
        <property name="userCache">
            <ref local="userCache"/>
        </property>
        <property name="passwordEncoder">
            <ref local="passwordEncoder"/>
        </property>
    </bean>
    <bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
        <property name="dataSource">
            <ref bean="dataSource"/>
        </property>
        <property name="usersByUsernameQuery">
            <value>
                SELECT staff_name,password,enabled 
                FROM staffs 
                WHERE staff_name = ?
            </value>
        </property>
        <property name="authoritiesByUsernameQuery">
            <value>
               SELECT staff_name,authority
               FROM authorities 
               WHERE staff_name = ?
            </value>
        </property>
    </bean>
 
    <bean id="passwordEncoder" class="com.chikkun.common.password.TripledesPasswordEncoder">

        <property name="util">
            <ref bean="desUtil"/>
        </property>
    </bean>
    <bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
        <property name="cache">
            <ref local="userCacheBackend"/>
        </property>
    </bean>
    <bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager">
            <ref local="cacheManager"/>
        </property>
        <property name="cacheName">
            <value>userCache</value>
        </property>
        <property name="timeToIdle">
            <value>5</value>
        </property>
    </bean>

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>


    <bean id="accessDecisionManager" 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>
    


</beans>