Top > MyPage
 

srutsとacegi(ちょっと書き足し)

strutsでacegiをつかいたい

strutsには認証の機能がないため、用意する必要がある。そこでAcegi Securityを使うことにする。

ダウンロード

Acegi Security公式サイトより圧縮ファイルをダウンロードダウンロードする。

ダウンロードしたファイルを、任意の場所に展開する。でてきたフォルダにあるjarファイルをAcegiを使いたいアプリケーションのlibディレクトリにコピーする。

今回の自分の場合だと「JBBS/target/JBBS/WEB-INF/lib」であった。

そして、次にSpringをダウンロードする。

Spring Framework公式サイトのダウンロードページにすすみ、

「with-dependencies」の方の圧縮ファイルをダウンロードする。このパッケージはSpring Framework単体ではなく、

必要になると思われるライブラリも同梱されている。

ダウンロードした圧縮ファイルを任意の場所に展開して、出てきたフォルダ内に「dist」というフォルダがある。そこにあるjarファイルを、「JBBS/target/JBBS/WEB-INF/lib」にコピーする。(spring.jarだけをコピーでOKかもしれない)

これでライブラリは設置完了。

XMLセッティング

フィルタとしてAcegiを使うように、「web.xml」に書き出す必要がある。今回はXDocletを使っているので、必要なマージファイルを用意する。

に以下の部分を書き足し

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/acegi-config.xml</param-value>
	</context-param>

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

	<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>

これで、それぞれのマージファイルの内容が「web.xml」に追加される。

次にacegiの設定ファイル「acegi-config.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="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>
	</bean>

	<bean id="authenticationProcessingFilter"
		class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
		<property name="authenticationManager"
			ref="authenticationManager" />
		<property name="authenticationFailureUrl"
			value="/user_loginerror.jsp" />
		<property name="defaultTargetUrl"
			value="/action/Testinput" />
		<property name="filterProcessesUrl"
			value="/JBBS/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
				/**=IS_AUTHENTICATED_REMEMBERED
			</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="/accessDenied.jsp" />
			</bean>
		</property>
	</bean>
		
	<bean id="authenticationManager"
		class="org.acegisecurity.providers.ProviderManager">
		<property name="providers">
			<list>
				<ref local="daoAuthenticationProvider" />
			</list>
		</property>
	</bean>

	<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>
		
	<bean id="daoAuthenticationProvider"
		class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
		<property name="userDetailsService" ref="userDetailsService" />
		<!-- UserCache property will activate the cache, it is not 
		mandatory but increases performance by cacheing the user 
		details retrieved from user-base -->
		<property name="userCache" ref="userCache"/>
	</bean>
		
	<bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
		<property name="userProperties">
			<bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
				<property name="location"
					value="/WEB-INF/users.properties" />
			</bean>
		</property>
	</bean>
	
	<bean id="userCache" 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>
	
	<!-- 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>

このXMLは次に示すリンク先の記事のサンプルを修正したものである。

Acegi を使って Java アプリケーションをセキュアにする、第 1 回: アーキテクチャーの概要とセキュリティー・フィルター

以下、修正した箇所をあげていく

	<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>
	</bean>

ログアウト処理フィルターのログアウト完了後にリダイレクトするURLをvalue="/action/Login"としている。

	<bean id="authenticationProcessingFilter"
		class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
		<property name="authenticationManager"
			ref="authenticationManager" />
		<property name="authenticationFailureUrl"
			value="/user_loginerror.jsp" />
		<property name="defaultTargetUrl"
			value="/action/Testinput" />
		<property name="filterProcessesUrl"
			value="/JBBS/action/j_acegi_security_check" />
	</bean>

「authenticationFailureUrl」認証失敗の場合のURLを「/user_loginerror.jsp」とした。

「defaultTargetUrl」認証とアクセス許可が成功した場合のURLを「/action/Testinput」とした。

「filterProcessesUrl」AcegiはこのURL要求を受信した時点で認証処理フィルターを呼び出す。始めはここの値を「/j_acegi_security_check」として、ログインフォームのactionをj_acegi_security_checkとしてやってみたがうまく行かなかった。filterProcessesUrlの値を「/JBBS/action/j_acegi_security_check」や「/action/j_acegi_security_check」としても同様であった。

	<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
				/**=IS_AUTHENTICATED_REMEMBERED
			</value>
		</property>
	</bean>

インターセプターフィルターの設定。アクセス許可。「objectDefinitionSource」の値に二つ設定。・/action/login=IS_AUTHENTICATED_ANONYMOUSLYログインページは誰でも・/**=IS_AUTHENTICATED_REMEMBERED BASE64でクッキーに保存されたユーザ?使い方間違いのような

	<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="/accessDenied.jsp" />
			</bean>
		</property>
	</bean>

例外変換フィルターの設定。

AuthenticationEntryPointはログインページ。

accessDeniedHandlerはアクセス拒否ページ。

ユーザー情報は「/WEB-INF/users.properties」に保存しておく.サンプルの書式をまねて以下のように設定

chikkun=kazukun,admin
guest=guest,user
kaz=fushiana,admin

ログイン画面のurl「/action/Login」をたたくと、以下のようなjspが呼び出されて表示される。

<%@ page language="java" pageEncoding="Windows-31J" contentType="text/html;charset=Windows-31J" %>

<?xml version="1.0" encoding="Windows-31J"?>
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=Windows-31J" />
  <title>ログイン</title>
  <meta name="generator" content="User-Agent: ee2e" />
</head> 
<body>

<form method="post" action="j_acegi_security_check">
<table>
  <tr>
    <td>ID</td>
    <td> <input type="text" name="j_username"></td>
  </tr>
  <tr>
    <td>Pass</td>
    <td><input type="password" name="j_password"></td>
  </tr>
</table>
<br>
<input type="submit" value="Login" name="submit">
</form>

</body>

</html>

そうして、mvn packageでwarファイルにして実行したところ、http://localhost:8080/JBBS/と打ち込むと

http://localhost:8080/JBBS/action/Loginにちゃんとリダイレクトされた。

が、ログインできずにこの画面しか表示されない。

filterProcessesUrlがまずいのかと思い、ここの値とformのactionをかえたりしたが、成功はしていない。

あと、もしかしたらと思っている場所は、インターセプターのobjectDefinitionSourceであるが・・・。

書き足し

結論から言うと、ログインできない理由はfilterProcessesUrlにあった。

今回の場合、フォームのactionをj_acegi_security_checkとすると、filterProcessesUrlは「/action/j_acegi_security_check」とする必要があった。

「/j_acegi_security_check」や「/JBBS/action/j_acegi_security_check」ではスルーしてしまう。

ただしい記述が分かるまでに随分かかってしまった。

ユーザー情報をDBから

ユーザーの情報をDBより取り出して認証したい。

pom.xmlにdependency追加

<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.3-SNAPSHOT</version>
</dependency>

アパッチプロジェクトのDatabase connection pooling services

追加したクラスcom.chikkun.common.BbsUser

package com.chikkun.common;

import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.userdetails.User;

public class BbsUser extends User {


  public BbsUser(String username, String password, boolean enabled,
      boolean accountNonExpired, boolean credentialsNonExpired,
      boolean accountNonLocked, GrantedAuthority[] authorities)
      throws IllegalArgumentException {

    super(username, password, enabled,
        accountNonExpired, credentialsNonExpired,
        accountNonLocked, authorities);
  }

}

次のクラスでこいつのインスタンスが必要になる。

package com.chikkun.common.BbsDaoImpl

package com.chikkun.common;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;

import javax.sql.DataSource;

import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.MappingSqlQuery;

import com.chikkun.common.BbsUser;

public class BbsDaoImpl extends JdbcDaoImpl{
//
  public static final String DEF_USERS_BY_USERNAME_QUERY = "SELECT USERNAME, PASSWORD FROM USERS WHERE USERNAME=?";
  public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "SELECT USERNAME,ROLE FROM USER_ROLES WHERE USERNAME = ?";
  private static final Log logger = LogFactory.getLog(BbsDaoImpl.class);

  //~ Instance fields ========================================================

  protected MappingSqlQuery authoritiesByUsernameMapping;
  protected MappingSqlQuery usersByUsernameMapping;
  private String authoritiesByUsernameQuery;
  private String rolePrefix = "ROLE_";
  private String usersByUsernameQuery;
  private boolean usernameBasedPrimaryKey = false;

  public BbsDaoImpl() {
    usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;
    authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;
}

  public UserDetails loadUserByUsername(String username)
  throws UsernameNotFoundException, DataAccessException {
  List users = usersByUsernameMapping.execute(username);
  System.err.println("kitaaaaaaaaaa");
  if (users.size() == 0) {
      throw new UsernameNotFoundException("User not found");
  }
  System.err.println("User is found ");
  BbsUser user = (BbsUser) users.get(0); // contains no GrantedAuthority[]

  List dbAuths = authoritiesByUsernameMapping.execute(user.getUsername());

  if (dbAuths.size() == 0) {
      throw new UsernameNotFoundException("User has no GrantedAuthority");
  }
  System.err.println("User has GrantedAuthority");
  GrantedAuthority[] arrayAuths = {};

  addCustomAuthorities(user.getUsername(), dbAuths);

  arrayAuths = (GrantedAuthority[]) dbAuths.toArray(arrayAuths);

  String returnUsername = user.getUsername();


  return new BbsUser(returnUsername, user.getPassword(), true, true, true, true, arrayAuths);
}

  protected void initMappingSqlQueries() {
    this.usersByUsernameMapping = new UsersByUsernameMapping(getDataSource());
    this.authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping(getDataSource());
}

  protected class AuthoritiesByUsernameMapping extends MappingSqlQuery {
    protected AuthoritiesByUsernameMapping(DataSource ds) {
        super(ds, authoritiesByUsernameQuery);
        declareParameter(new SqlParameter(Types.VARCHAR));
        compile();
    }

    protected Object mapRow(ResultSet rs, int rownum)
        throws SQLException {
        String roleName = rolePrefix + rs.getString(2);
        GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName);
        System.err.println("It is rolename");
        System.err.println(roleName);
        return authority;
    }
}

/**
 * Query object to look up a user.
 */
protected class UsersByUsernameMapping extends MappingSqlQuery {
    protected UsersByUsernameMapping(DataSource ds) {
        super(ds, usersByUsernameQuery);
        declareParameter(new SqlParameter(Types.VARCHAR));
        compile();
    }

    protected Object mapRow(ResultSet rs, int rownum)
        throws SQLException {
        String username = rs.getString(1);
        String password = rs.getString(2);
        UserDetails user = new BbsUser(username, password, true, true, true, true, new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});

        return user;
    }
}

}

ところどころにSystem.err.printlnをいれて、どこまで進んでいるか確認していた。

クラスの始めの方で、定数にsqlをいれている。DBのテーブルusersからusernameとpasswordを取り出すsql。テーブルuser_rolesからusernameとroleを取り出すsql。

当初は、user_rolesにはuser_idとrole_keyを対で保存してたが、JOINを使うと(本当にJOINが原因かははっきりしないが)警告が出て、処理が終了されるので、joinを使わないようにテーブルを組みなおした。

acegiの設定acegi-config.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="filterChainProxy"
		class="org.acegisecurity.util.FilterChainProxy">
		<property name="filterInvocationDefinitionSource">
			<value>
				CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
				PATTERN_TYPE_APACHE_ANT
				/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,logoutFilter,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>
	</bean>



	<bean id="authenticationProcessingFilter"
		class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
		<property name="authenticationManager"
			ref="authenticationManager" />
		<property name="authenticationFailureUrl"
			value="/user_loginerror.jsp" />
		<property name="defaultTargetUrl"
			value="/action/TestInput" />
		<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
				/**=ROLE_user,ROLE_admin
			</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="/accessDenied.jsp" />
			</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="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="com.chikkun.common.BbsDaoImpl">
        <property name="dataSource">
            <ref bean="dataSource"/>
        </property>
  </bean>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName">
            <value>org.postgresql.Driver</value>
        </property>
        <property name="url">
            <value>jdbc:postgresql://localhost/bbs</value>
        </property>
        <property name="username">
            <value>chikkun</value>
        </property>
        <property name="password">
            <value>kazukun</value>
        </property>
    </bean>

	<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>


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

大きく変わったのが、daoAuthenticationProvider、userDetailsServiceのあたり

ユーザー情報をDBから取得するようにしたため、大きく変わった。

これで、DBに登録されている、ユーザー名とパスワードをログインフォームで入力して、ログインしなければ他のページは開けない。

ログインすればauthenticationProcessingFilterで設定されているdefaultTargetUrlのurlに移動するはずなのだが、なぜかアプリケーションのルートへ遷移した・・・