Struts2にSpringSecurityを適用する方法

# 認証機能をSpringSecurityに任せる

Struts2は特に認証用の機能はありません。
サーブレット標準の認証(JAAS)を使いログイン/ログアウトを構築できますが、それよりも高度な機構を備えるSpringSecurityを組み合わせることで、認証ならびにアクセス制御をSpringSecurityに任せ、ログイン後やログアウト後の追加処理をStruts2で実装することも可能です。

## 必要なjarファイルたち(mavenで設定)

以下の設定はSpringSecurity-4.0.0.CI版のものですが、正式リリース後はCIをRELEASEに置き換えるだけです。

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>archetype.group.id</groupId>
	<artifactId>artifactId</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<properties>
		<java.version>1.7</java.version>
		<spring-security.version>4.0.0.CI-SNAPSHOT</spring-security.version>
		<struts2.version>2.3.20</struts2.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-core</artifactId>
			<version>${struts2.version}</version>
		</dependency>
		<dependency>
 			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-convention-plugin</artifactId>
			<version>${struts2.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${spring-security.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${spring-security.version}</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<encoding>UTF-8</encoding>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<configuration>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<name>appname</name>
	<url>http://your.domain/appname</url>
	<description>descriptions....</description>
</project>
```

# 設定ファイルの修正・追加

SpringSecurityのサーブレットフィルタを有効にします。以下のxmlファイルを編集します。

* web.xml
* applicationContext-security.xml(ファイル名はweb.xmlに記載したもの)

## web.xml

```xml:web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="app_id" version="3.0"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
	<display-name>appname</display-name>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring/applicationContext.xml,classpath:spring/applicationContext-*.xml</param-value>
	</context-param>

	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<listener>
		<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
	</listener>
</web-app>
```
## applicationContext-security.xml

この例では別途 Springの定義で、JDBCのデータソースを dataSourceの名前で定義が必要です。web.xmlにて、配置名をclasspath:spring/applicationContext.xml,classpath:spring/applicationContext-*.xml と宣言しています。配置場所はお好みでどうぞ。

私の場合はspringディレクトリを作成し、その中に applicationContext-security.xml を配置します。

```xml:applicationContext-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
  xmlns:sec="http://www.springframework.org/schema/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/security
  http://www.springframework.org/schema/security/spring-security.xsd">

  <sec:http auto-config='true'>
    <!-- CSRF回避チェックを有効 -->
    <sec:csrf/>

    <!-- アクセス制御をしないURLを定義 -->
    <sec:intercept-url pattern="/login*" access="permitAll"/>
    <sec:intercept-url pattern="/css/**" access="permitAll" />
    <sec:intercept-url pattern="/fonts/**" access="permitAll"/>
    <sec:intercept-url pattern="/images/**" access="permitAll"/>
    <sec:intercept-url pattern="/js/**" access="permitAll"/>
    <sec:intercept-url pattern="/templates/**" access="permitAll"/>

    <!-- ロールに ROLE_ADMIN を持つユーザID(USERNAME)のみアクセス許可 -->
    <sec:intercept-url pattern="/*" access="hasRole('ROLE_ADMIN')" />

    <!-- FORM認証の振る舞い -->
    <sec:form-login
        login-page="/login.action"
        login-processing-url="/j_spring_security_check"
        username-parameter="j_username"
        password-parameter="j_password"
        authentication-failure-url="/loginFailure.action" />

    <!-- ログアウト時の振る舞い -->
    <sec:logout 
        logout-url="/logout"
        logout-success-url="/logout.action"
        invalidate-session="true" />
  </sec:http>

  <!-- データベースを使った認証管理 -->
  <sec:authentication-manager alias="authManager">
    <sec:authentication-provider>
      <sec:jdbc-user-service data-source-ref="dataSource"
        users-by-username-query="
          SELECT
            USERNAME
          , PASSWORD
          , ENABLED
          FROM
            USERTABLE
          WHERE
            USERNAME = ?"
        authorities-by-username-query="
          SELECT
            USERNAME 
          , AUTHORITY 
          FROM 
            ROLETABLE 
          WHERE 
            USERNAME = ?"
      />
    </sec:authentication-provider>
  </sec:authentication-manager>
  <beans:bean 
    id="sessionRegistry" 
    class="org.springframework.security.core.session.SessionRegistryImpl" />
</beans:beans>
```

認証するための情報を取得する先は、\<sec:authentication-manager alias="authManager">\で定義します。この例ではSQLを発行してデータベースに問い合わせます。データベース接続はSpring管理下にある dataSource の名前で管理されたSpring-beanです。
dataSourceの設定例をおまけで次に紹介します。

## applicationContext-dataSource.xml

Spring管理下でdataSourceを作ります。

```xml:applicationContext-dataSource.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"
default-autowire="byName"
>
    <!-- Springトランザクション管理クラス -->
    <bean id="transactionManager" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource"><ref bean="dataSource"/></property>
    </bean>

    <!-- トランザクション定義でSpringアノテーション利用を宣言する。 -->
	<tx:annotation-driven transaction-manager="transactionManager"
      proxy-target-class="true" />

    <!-- JNDIルックアップ -->
	<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/dbcon"></jee:jndi-lookup>
</beans>
```

この例では、データベース接続のソースをTomcatなどのコンテナから拝借します。その際、JNDIで定義した命名で指定します。

データソースを使う場合には、pom.xmlにて必要なSpring frameworkを追加します。
以下は追加例です。

```xml:pom.xml(断片)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
    <artifactId>artifactId</artifactId>
    <name>appname</name>
    <properties>
        <!-- ここから追加 -->
        <spring.version>4.1.3.RELEASE</spring.version>
        <!-- ここまで -->
    </properties>
    <dependencies>
        <!-- ここから追加 -->
        <dependency>
        	<groupId>org.springframework</groupId>
        	<artifactId>spring-core</artifactId>
        	<version>${spring.version}</version>
        </dependency>
        <dependency>
        	<groupId>org.springframework</groupId>
        	<artifactId>spring-web</artifactId>
        	<version>${spring.version}</version>
        </dependency>
        <dependency>
        	<groupId>org.springframework</groupId>
        	<artifactId>spring-beans</artifactId>
        	<version>${spring.version}</version>
        </dependency>
        <dependency>
        	<groupId>org.springframework</groupId>
        	<artifactId>spring-context</artifactId>
        	<version>${spring.version}</version>
        </dependency>
        <dependency>
        	<groupId>org.springframework</groupId>
        	<artifactId>spring-context-support</artifactId>
        	<version>${spring.version}</version>
        </dependency>
        <dependency>
        	<groupId>org.springframework</groupId>
        	<artifactId>spring-tx</artifactId>
        	<version>${spring.version}</version>
        </dependency>
        <!-- ここまで -->
    </dependencies>
</project>
```