Apache Pluto による簡易ポータル開発(ユーザ認証編)
前回の続き。
Plutoについてくるポータルアプリケーションのページ構成は、 src/main/webapp/WEB-INF/pluto-portal-driver.config.xml の静的な記述から構築されているけど、どんなユーザでログインしても全員同じページ構成ではポータルの名が泣くので、ユーザごととはいかないがログインしたユーザのロールごとに異なるページ構成になるよう改造したい。
まずは、その準備として Spring Security を使用してDBテーブルを認証ソースにしたユーザ認証を行うようにする。
データベースの準備
認証と、後ほどページ構成の取得もデータベースからおこなうために、こんな感じのテーブルを設計した。
SQLite3 をインストールした上で、以下のコマンドで「portal.sqlite3」ファイルを新しいDBとして作成し、SQLを流してDBを初期化する。
sqlite3 portal.sqlite3
CREATE TABLE USERS ( ID INTEGER NOT NULL, NAME TEXT NOT NULL UNIQUE, PASSWORD TEXT NOT NULL, ENABLED BOOLEAN NOT NULL, ROLE_ID INTEGER NOT NULL, PRIMARY KEY (ID) ); CREATE TABLE ROLES ( ID INTEGER NOT NULL, NAME TEXT NOT NULL UNIQUE, PRIMARY KEY (ID) ); INSERT INTO "ROLES" VALUES(1,'ROLE_USER'); INSERT INTO "ROLES" VALUES(2,'ROLE_ADMIN'); INSERT INTO "USERS" VALUES(1,'user','password',1,1); INSERT INTO "USERS" VALUES(2,'admin','password',1,2);
依存ライブラリの追加
pom.xml に以下の通り依存ライブラリを追加する。
<!-- Spring Framework / Spring Security --> <!-- Pluto が依存しているバージョンが古いため、新しいバージョンを手動指定 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>2.5.6.SEC01</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>2.5.6.SEC01</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>2.5.6.SEC01</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>2.5.6.SEC01</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>2.5.6.SEC01</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>2.5.6.SEC01</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core-tiger</artifactId> <version>2.0.5.RELEASE</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- web.xml が 2.3 設定のため 2.3 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.3</version> <scope>provided</scope> </dependency> <!-- SQLite用JDBCドライバ --> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.6.20</version> </dependency> <!-- ORMライブラリ(iBATIS) --> <dependency> <groupId>org.apache.ibatis</groupId> <artifactId>ibatis-sqlmap</artifactId> <version>2.3.4.726</version> </dependency>
Spring Security による認証の設定
現在はサーブレットのFORM認証で tomcat-users.xml を認証ソースにした認証を行っているが、これをデータベースの USERS/ROLES テーブルをもとに行うようにする。
修正するファイルと内容は以下の通り。
src/main/webapp/WEB-INF/web.xml
以下のように contextConfigLocation に applicationContext.xml ファイルも読むよう設定を追加する。
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/pluto-portal-driver-services-config.xml, classpath:/applicationContext.xml</param-value> </context-param>
また、元々あった plutoPortalDriver フィルタに加えて Spring Security 用のフィルタ設定を追加する。
<!-- 追加 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter> <filter-name>plutoPortalDriver</filter-name> <filter-class>org.apache.pluto.driver.PortalDriverFilter</filter-class> </filter> <!-- 追加 --> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>plutoPortalDriver</filter-name> <url-pattern>/about.jsp</url-pattern> </filter-mapping>
そして、不要なFORM認証用の設定(
src/main/resources/applicationContext.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: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.4.xsd "> <sec:http auto-config="true"> <sec:intercept-url pattern="/portal" access="ROLE_USER,ROLE_ADMIN" /> <sec:intercept-url pattern="/portal/**" access="ROLE_USER,ROLE_ADMIN" /> <sec:form-login authentication-failure-url="/login.jsp?error=1" login-page="/login.jsp" default-target-url="/portal" /> <sec:logout logout-success-url="/login.jsp" /> </sec:http> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.sqlite.JDBC" /> <!-- portal.sqlite3 のパスは上でDBを作成したときのパスに変更 --> <property name="url" value="jdbc:sqlite:C:/workspace/portal-example/portal.sqlite3" /> </bean> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation" value="classpath:sqlmap-config.xml" /> <property name="dataSource" ref="dataSource" /> </bean> <bean id="userDetailsService" class="com.example.pluto.UserDetailsServiceImpl"> <property name="sqlMapClient" ref="sqlMapClient" /> </bean> <sec:authentication-provider user-service-ref="userDetailsService"> </sec:authentication-provider> </beans>
src/main/resources/sqlmap-config.xml
以下の内容で作成。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-config-2.dtd"> <sqlMapConfig> <settings useStatementNamespaces="true" /> <sqlMap resource="sqlmap-portal.xml"/> </sqlMapConfig>
src/main/resources/sqlmap-portal.xml
以下の内容で作成。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd"> <sqlMap namespace="portal"> <select id="getUserByName" parameterClass="string" resultClass="java.util.HashMap"> SELECT users.* FROM users WHERE name = #value# </select> <select id="findRoleNamesByUserName" parameterClass="string" resultClass="string"> SELECT roles.name FROM roles JOIN users ON users.role_id = roles.id WHERE users.name = #value# </select> </sqlMap>
src/main/java/com/example/pluto/UserDetailsServiceImpl.java
Eclispe で src/main/java を新しいソース・フォルダとして作成した後、以下の内容で作成する。
package com.example.pluto; import java.util.List; import java.util.Map; import org.springframework.dao.DataAccessException; import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport; import org.springframework.security.GrantedAuthorityImpl; import org.springframework.security.userdetails.User; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsService; import org.springframework.security.userdetails.UsernameNotFoundException; public class UserDetailsServiceImpl extends SqlMapClientDaoSupport implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { @SuppressWarnings("unchecked") Map<String, Object> user = (Map<String, Object>) getSqlMapClientTemplate() .queryForObject("portal.getUserByName", username); if (user == null) { throw new UsernameNotFoundException( String.format("user %s not found", username)); } @SuppressWarnings("unchecked") List<String> roles = (List<String>) getSqlMapClientTemplate() .queryForList("portal.findRoleNamesByUserName", username); GrantedAuthorityImpl[] authorities = new GrantedAuthorityImpl[roles.size()]; int i = 0; for (String role : roles) { authorities [i++] = new GrantedAuthorityImpl(role); } return new User( (String) user.get("NAME"), (String) user.get("PASSWORD"), ((Integer) user.get("ENABLED")) == 1, true, true, true, authorities); } }
src/main/webapp/WEB-INF/themes/pluto-default-theme.jsp
ログアウト用URLを Spring Security 用のものにする。
<!-- これをコメントアウト --> <!-- <a href="<c:url value='/Logout'/>">Logout</a> --> <!-- これを追加 --> <a href="<c:url value='/j_spring_security_logout'/>">Logout</a>
src/main/webapp/login.jsp
ログイン用URLを Spring Security のものにする。
<!-- これをコメントアウト --> <!-- <form method="POST" action="j_security_check"> --> <!-- これを追加 --> <form method="POST" action="j_spring_security_check">
実行確認
最初にDBに設定した user/password, admin/password の2つのユーザ名・パスワードの組でログインできることを確認する。