2012年5月24日木曜日

SpringSecurityユーザをDBで管理、JPAでログインする

はじめに

Spring Securityを使ってデータベースに保存されたユーザ、パスワードでログインする機能を作ってみたいと思います。
Spring Securityで規定されたusersテーブルのスキーマをそのまま使う分には、
Springの定義ファイルを編集するするだけでよいのですが
このusersテーブルに他の項目を追加してログインセッションに置きたい場合は、カスタマイズが必要になります。
ログインが成功した場合、セッションキー「SPRING_SECURITY_CONTEXT」にusersテーブルの情報が置かれます。

カスタマイズとしては、UserDetailsインターフェイス(org.springframework.security.core.userdetails)実装クラスと
UserDetailsServiceインターフェイス(org.springframework.security.core.userdetails)実装クラスの作成になります。
UserDetailsインターフェイス実装クラスは、ログイン成功時、セッションに置かれるログインユーザ情報を保持するクラスです。
今回は、EntityBeanで作成します。
UserDetailsServiceインターフェイス実装クラスは、ユーザ情報取得メソッド(loadUserByUsername)を実装します。
今回は、EntityManagerを使ってユーザ情報を取得します。

今回は、usersテーブルに連絡先(contact)を追加したいと思います。
ではでは!Getting Started!!

環境

OS Windows XP
データベース MySQL 5.5.8
アプリケーションサーバー Tomcat 7.0.27
Java JDK 1.7.0
フレームワーク Spring Framework 3.1.0.RELEASE
Spring Security 3.1.0.RELEASE
Hibernate 3.6.10.Final
ビルドツール Maven 3.0.4
※mvnコマンドが実行できるように設定しておきます。
Spring Security について

「3.1.0.RELEASE」でMavenビルドすると「invalid LOC header」エラーなるものが 発生していたのですが、Springframeworkのローカルのリポジトリ「C:\Documents and Settings\{user}\.m2\repository\org\springframework」を削除して 再度ビルドするとうまくいきました。



プロジェクト構成

Cドライブ直下に「studying-spring-security」フォルダを作成し各ファイルを以下のように配置します。

  • C:\studying-spring-security
    • src
      • main
        • java
          • com
            • mydomain
              • bizlogic
                • MyUserDetailsService.java
              • entity
                • MyAuthority.java
                • MyUser.java
        • resources
          • META-INF
            • spring
              • applicationContext.xml
              • applicationContext-locale.xml
              • applicationContext-security.xml
            • persistence.xml
          • log4j.properties
        • webapp
          • resources
            • style.css
          • WEB-INF
            • i18n
              • messages_ja.properties
            • spring
              • webmvc-config.xml
            • views
              • index.jspx
              • login.jspx
            • web.xml
    • pom.xml
プロジェクト作成について

Spring Rooでプロジェクトを作成し、今回使わないもの(Tiles、Themeなど)を削除しました。



データベースの作成

DROP DATABASE IF EXISTS studying_spring_security;
CREATE DATABASE IF NOT EXISTS studying_spring_security DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

※データベースユーザとして、パスワードなしのrootユーザが作成してあるとします。



テーブルの作成とログインユーザの登録

ログインユーザとして、adminとuserを作成します。
ログインID/パスワードはそれぞれ
admin/admin、user/user
としています。

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `username` varchar(50)  NOT NULL,
  `password` varchar(255) NOT NULL,
  `enabled`  tinyint(1)   NOT NULL,
  `contact`  varchar(255) DEFAULT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities` (
  `username`  varchar(50) NOT NULL,
  `authority` varchar(50) NOT NULL,
  UNIQUE KEY `ix_auth_username` (`username`,`authority`),
  CONSTRAINT `fk_authorities_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `persistent_logins`;
CREATE TABLE `persistent_logins` (
  `username`  varchar(64) NOT NULL,
  `series`    varchar(64) NOT NULL,
  `token`     varchar(64) NOT NULL,
  `last_used` timestamp   NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `users` VALUES ('admin', '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918', '1', 'admin@mydomain.com');
INSERT INTO `users` VALUES ('user',  '04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb', '1', 'user@mydomain.com');

INSERT INTO `authorities` VALUES ('admin', 'ROLE_ADMIN');
INSERT INTO `authorities` VALUES ('admin', 'ROLE_USER');
INSERT INTO `authorities` VALUES ('user',  'ROLE_USER');


ServletとLoggerの設定

web.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<web-app 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>studying-spring-security</display-name>
  
  <context-param>
    <param-name>defaultHtmlEscape</param-name>
    <param-value>true</param-value>
  </context-param>
  
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
  </context-param>
  
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <filter>
    <filter-name>HttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>HttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <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>
  
  <servlet>
    <servlet-name>studying-spring-security</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>WEB-INF/spring/webmvc-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>studying-spring-security</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  
  <session-config>
    <session-timeout>10</session-timeout>
    <tracking-mode>COOKIE</tracking-mode>
  </session-config>
</web-app>
log4j.properties
log4j.rootLogger=error, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

# Print the date in ISO 8601 format
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=application.log

log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n


データベース接続の設定

persistence.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence version="2.0"
  xmlns="http://java.sun.com/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
    http://java.sun.com/xml/ns/persistence
    http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

  <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
      <property name="hibernate.hbm2ddl.auto" value="validate" />
      <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.DefaultNamingStrategy" />
      <property name="hibernate.connection.charSet" value="UTF-8" />
    </properties>
  </persistence-unit>
</persistence>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans
  xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:p="http://www.springframework.org/schema/p"
 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.1.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.1.xsd">

  <bean id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/studying_spring_security"/>
    <property name="username" value="root"/>
    <property name="password" value=""/>
    <property name="testOnBorrow" value="true"/>
    <property name="testOnReturn" value="true"/>
    <property name="testWhileIdle" value="true"/>
    <property name="timeBetweenEvictionRunsMillis" value="1800000"/>
    <property name="numTestsPerEvictionRun" value="3"/>
    <property name="minEvictableIdleTimeMillis" value="1800000"/>
    <property name="validationQuery" value="SELECT 1"/>
  </bean>

  <context:annotation-config/>
  <bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    p:persistenceUnitName="persistenceUnit" p:dataSource-ref="dataSource" />
</beans>
データベース接続について

persistence.xmlファイルにデータベース接続設定を記述したいところなのですが
Spring Securityの標準の実装では、RememberMeトークンをデータベースに保存する場合、
JDBCを使用している(org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl)みたいで
DataSourceをSpringコンテナに登録しておく必要があるようです。
この部分もカスタマイズすればよいのですが、今回はパスで…



Entityクラスの作成

MyUser.java
package com.mydomain.entity;

import java.util.Collection;

import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.JoinColumn;

import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Entity
@Table(name = "users")
public class MyUser implements UserDetails, CredentialsContainer{

  private static final long serialVersionUID = 1L;
  

  @Id
  private String username;
  
  private String password;
  
  private boolean enabled;

  private String contact;

  @ElementCollection(targetClass=MyAuthority.class, fetch=FetchType.EAGER)
  @CollectionTable(
    name="authorities",
    joinColumns={@JoinColumn(name="username", referencedColumnName="username")})
  @MapKeyColumn(name="authority")
  private Collection<GrantedAuthority> authorities;

  @Transient
  private boolean accountNonExpired = true;

  @Transient
  private boolean accountNonLocked = true;

  @Transient
  private boolean credentialsNonExpired = true;


  @Override
  public void eraseCredentials() {
    password = null;
  }

  @Override
  public boolean equals(Object rhs) {
    if (rhs instanceof User) {
      return username.equals(((User) rhs).username);
    }
    return false;
  }

  @Override
  public int hashCode() {
    return username.hashCode();
  }


  public String getContact() {
    return contact;
  }

  public void setContact(String contact) {
    this.contact = contact;
  }

  @Override
  public String getUsername() {
    return this.username;
  }

  public void setUsername(String username) {
    this.username = username;
  }
  
  @Override
  public String getPassword() {
    return this.password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  @Override
  public boolean isEnabled() {
    return this.enabled;
  }

  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
  }
  
  @Override
  public boolean isAccountNonExpired() {
    return this.accountNonExpired;
  }
  
  public void setAccountNonExpired(boolean accountNonExpired) {
    this.accountNonExpired = accountNonExpired;
  }

  @Override
  public boolean isAccountNonLocked() {
    return this.accountNonLocked;
  }

  public void setAccountNonLocked(boolean accountNonLocked) {
    this.accountNonLocked = accountNonLocked;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return this.credentialsNonExpired;
  }

  public void setCredentialsNonExpired(boolean credentialsNonExpired) {
    this.credentialsNonExpired = credentialsNonExpired;
  }

  @Override
  public Collection<GrantedAuthority> getAuthorities() {
    return this.authorities;
  }
  
  public void setAuthorities(Collection<GrantedAuthority> authorities) {
    this.authorities = authorities;
  }
}
MyUserクラスについて

equals()メソッド、hashCode()メソッドは、User(org.springframework.security.core.userdetails)クラスを参考に実装しました。
CredentialsContainerインターフェイスの実装も同様に参考にしました。
…toString()メソッドはパスです。

MyAuthority.java
package com.mydomain.entity;

import javax.persistence.Embeddable;

import org.springframework.security.core.GrantedAuthority;

@Embeddable
public class MyAuthority implements GrantedAuthority{

  private static final long serialVersionUID = 1L;
  
  private String authority;

  @Override
  public String getAuthority() {
    return this.authority;
  }

  public void setAuthority(String authority) {
    this.authority = authority;
  }

  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }

    if (obj instanceof MyAuthority) {
      return authority.equals(((MyAuthority) obj).authority);
    }

    return false;
  }

  public int hashCode() {
    return this.authority.hashCode();
  }

  public String toString() {
    return this.authority;
  }
}
MyAuthorityクラスについて

equals()メソッド、hashCode()メソッド、toString()メソッドは、
SimpleGrantedAuthority(org.springframework.security.core.authority)クラスを参考に実装しました。



UserDetailsServiceクラスの作成

MyUserDetailsService.java
package com.mydomain.bizlogic;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.mydomain.entity.MyUser;

@Service
public class MyUserDetailsService implements UserDetailsService {
  
  @PersistenceContext
  private EntityManager em;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {

    try {
      String jpql = "SELECT u FROM User u Where username = :username";
      Query query = this.em.createQuery(jpql, MyUser.class);
      query.setParameter("username", username);
      MyUser user = (MyUser) query.getSingleResult();

      return user;
    } catch(NoResultException exp) {
      throw new UsernameNotFoundException("ユーザが見つかりません");
    }
  }
}


Spring Securityの設定

/login、/resourcesには、アクセス制限を設定せず、それ以外のURLにアクセス制限を設定します。

<authentication-provider>タグと<remember-me>タグのuser-service-ref属性に
作成したUserDetailsServiceインターフェイス実装クラスを指定します。

RememberMeトークンをデータベースに保存する場合、<remember-me>タグのdata-source-ref属性に
Springコンテナに登録されたDataSourceを指定します。

applicationContext-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
  xmlns="http://www.springframework.org/schema/security" 
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:p="http://www.springframework.org/schema/p"
  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.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security.xsd">

  <http auto-config="true" use-expressions="true">
    <form-login login-processing-url="/resources/j_spring_security_check"
      login-page="/login" authentication-failure-url="/login?login_error=t" 
      default-target-url="/" always-use-default-target="true"/>
    <logout logout-url="/resources/j_spring_security_logout" logout-success-url="/"/>

    <intercept-url pattern="/login*" access="permitAll" />
    <intercept-url pattern="/resources/**" access="permitAll" />
    <intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
        
    <remember-me data-source-ref="dataSource" user-service-ref="myUserDetailsService"/>
  </http>

  <authentication-manager alias="authenticationManager">
    <authentication-provider user-service-ref="myUserDetailsService">
      <password-encoder hash="sha-256"/>
    </authentication-provider>
  </authentication-manager>
    
  <beans:bean id="myUserDetailsService" class="com.mydomain.bizlogic.MyUserDetailsService"/>
</beans:beans>
applicationContext-locale.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:p="http://www.springframework.org/schema/p"
  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.1.xsd">

  <bean id="messageSource"
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
    p:basenames="WEB-INF/i18n/messages" p:fallbackToSystemLocale="false"
    p:fileEncodings="UTF-8" p:defaultEncoding="UTF-8" />
</beans>
エラーメッセージの日本語化について

SpringSecurity関連のクラスがSpringコンテナに登録される際に ReloadableResourceBundleMessageSourceクラスなどからmessages_ja.propertiesファイルが読み込めないと エラーメッセージが日本語化できないようです。
webmvc-config.xmlファイルが読み込まれる時点では、 SpringSecurity関連クラスのSpringコンテナへの登録が終わっているようなので このファイルでReloadableResourceBundleMessageSourceクラスなどの設定をしていると SpringSecurityのデフォルトのエラーメッセージを日本語で上書きされないみたいです。

messages_ja.properties
AbstractUserDetailsAuthenticationProvider.badCredentials=ログインID、パスワードが違います。
native2asciiについて

ReloadableResourceBundleMessageSourceクラスを使用するとnative2asciiで変換を行わなくてもよいようです。



Spring MVCの設定

webmvc-config.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xmlns:p="http://www.springframework.org/schema/p"
  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.1.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">

  <mvc:annotation-driven />
  <mvc:resources location="/resources/" mapping="/resources/**" />
  <mvc:view-controller path="/login" />
  <mvc:view-controller path="/" view-name="index" />

  <bean id="internalResourceViewResolver"
    class="org.springframework.web.servlet.view.InternalResourceViewResolver"
    p:viewClass="org.springframework.web.servlet.view.JstlView"
    p:prefix="/WEB-INF/views/" p:suffix=".jspx" />
</beans>


Viewファイルの作成

login.jspx
<html version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fn="http://java.sun.com/jsp/jstl/functions"
  xmlns:spring="http://www.springframework.org/tags"
  xmlns:form="http://www.springframework.org/tags/form">

  <jsp:output doctype-root-element="HTML" doctype-system="about:legacy-compat" />
  <jsp:directive.page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" />
  
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <c:url value="/resources/style.css" var="style"/>
    <link rel="stylesheet" type="text/css" href="${style}" />
    <title>ログイン</title>
  </head>
  <body>
    <div id="page">
      <h3>ログイン</h3>
      <c:if test="${not empty param.login_error}">
      <div class="errors">
        <p><c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" /></p>
      </div>
      </c:if>
        <spring:url value="/resources/j_spring_security_check" var="form_url" />
        <form name="f" action="${fn:escapeXml(form_url)}" method="POST">
        <table>
          <tbody>
            <tr>
              <th><label for="j_username">ログインユーザ</label></th>
              <td><input id="j_username" type='text' name='j_username' style="width:98%" /></td>
            </tr>
            <tr>
              <th><label for="j_password">パスワード</label></th>
              <td><input id="j_password" type='password' name='j_password' style="width:100px" />
              <input id="proceed" type="submit" value="ログイン" /></td>
            </tr>
            <tr>
              <td></td>
              <th>
                <input id="_spring_security_remember_me" 
                  type='checkbox' name='_spring_security_remember_me' value="true"/>
                <label for="_spring_security_remember_me">ログインしたままにする</label>
              </th>
            </tr>
          </tbody>
        </table>
        </form>
    </div>
  </body>
</html>
エラーメッセージの表示について

SpringSecurityのエラーメッセージはセッションに保存されているみたいなので <jsp:directive.page session="false" /> としているとエラーメッセージが表示されなくようです。

index.jspx
<html version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:sec="http://www.springframework.org/security/tags">

  <jsp:output doctype-root-element="HTML" doctype-system="about:legacy-compat" />
  <jsp:directive.page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" />
  
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <c:url value="/resources/style.css" var="style"/>
    <link rel="stylesheet" type="text/css" href="${style}" />
    <title>ようこそ</title>
  </head>
  <body>
    <div id="page">
      <h3>ようこそ</h3>
      <p>
        <sec:authentication property="principal.username" htmlEscape="true"/>さん<br />
        あなたの連絡先は<sec:authentication property="principal.contact" htmlEscape="true"/>です。<br />
        <sec:authorize access="hasRole('ROLE_ADMIN')">※管理者権限が設定されています。</sec:authorize>
      </p>
      <c:url value="/resources/j_spring_security_logout" var="logout_url"/>
      <a href="${logout_url}">ログアウト</a>
    </div>
  </body>
</html>
style.css
body {
  margin: 0px;
  padding: 0px;
  font: 13px Arial, Helvetica, sans-serif;
  color: #212121;
}

h1 {
  margin-top: 0px;
  font-size: 2.4em;
}

p {
  margin-bottom: 1.8em;
  line-height: 160%;
}

table {
  margin: 0px auto;
}

th, td {
  text-align: left;
}

div.errors {
  background-color: #FFEBE8;
  border: solid 1px #DD3C10;
  width: 300px;
  margin: 3px auto;
  padding: 3px;
}

#page {
  width: 900px;
  margin: 0px auto;
  padding: 30px 0px;
  text-align: center;
}


POMファイルの作成

pom.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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/maven-v4_0_0.xsd">
    
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mydomain</groupId>
  <artifactId>studying-spring-security-02</artifactId>
  <packaging>war</packaging>
  <version>1.0</version>
  <name>studying-spring-security-02</name>
  
  <properties>
    <java.version>1.7</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>3.1.0.RELEASE</spring.version>
    <spring-security.version>3.0.7.RELEASE</spring-security.version>
  </properties>
  
  <repositories>
    <repository>
      <id>spring-maven-release</id>
      <name>Spring Maven Release Repository</name>
      <url>http://maven.springframework.org/release</url>
    </repository>
  </repositories>
  
  <pluginRepositories>
    <pluginRepository>
      <id>spring-maven-release</id>
      <name>Spring Maven Release Repository</name>
      <url>http://maven.springframework.org/release</url>
    </pluginRepository>    <pluginRepository>
      <id>apache.snapshots</id>
      <url>http://repository.apache.org/snapshots/</url>
    </pluginRepository>
  </pluginRepositories>
  
  <dependencies>     
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-core</artifactId>
      <version>${spring-security.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>
      <version>${spring-security.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-taglibs</artifactId>
      <version>${spring-security.version}</version>
    </dependency>

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>3.6.10.Final</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.5.6</version>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.3</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.18</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <finalName>studying-spring-security-02</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.0-SNAPSHOT</version>
      </plugin>
    </plugins>
  </build>
</project>


ビルドと実行

コマンドプロンプトを開きCドライブ直下の「studying-spring-security」フォルダに移動後、mvnコマンドを実行します。

cd c\studying-spring-security
mvn clean package
mvn tomcat7:run

Webブラウザを開き「http://localhost:8080/studying-spring-security/」にアクセスします。
ログインユーザ/パスワードは admin/admin と user/user になります。

tomcat7-maven-pluginについて

現時点での最新は「2.0-beta-1」だと思われるのですが、このVersionでtomcatを実行すると
「Unable to determine URL for WEB-INF/classes」的なエラー発生してしまいます。
これは、tomcat7-maven-pluginが使用しているtomcat(7.0.25)が原因とのことです。
エラーは出るもののとりあえずは動いてそうなので問題なさそうなのですが、
エラーが出ているのがなんとなく嫌な感じなのでSNAPSHOTにしてみました。
なにぶんSNAPSHOTなので日によって挙動が違うかもしれません…

おわりに

SpringSecurityは、もっとガチガチで入り込む余地があまりないような印象だったのですが
さわってみた感じ割と挟みどころがいろいろ用意されているようです。
全然使い込んではないのでなんとも言えませんが…
とりあえずSpringは期待できそうだ。!
Webの開発をJavaでとなるとSpringの今後に懸かってますね。!
…JavaでWebかぁ~
ないのかな?

参考URL
http://news.mynavi.jp/articles/2010/03/25/spring3/003.html
http://sites.google.com/site/soracane/home/springnitsuite/spring-security/spring-securityno-settei-houhou
http://ameblo.jp/spring-beginner/entry-10239724907.html
http://oajamfibia.wordpress.com/2011/07/05/writing-a-custom-jpa-userdetailservice/
http://krams915.blogspot.jp/2010/12/spring-security-mvc-integration_18.html
http://www.codercorp.com/blog/spring/security-spring/writing-custom-userdetailsservice-for-spring-security.html
http://stackoverflow.com/questions/9580031/javax-naming-namenotfoundexception-resource-web-inf-classes-not-found-with-mav