文書の過去の版を表示しています。


Spring rememeber-meログイン

ログインを維持してくれるremember-meログイン機能をSpring securityを用いて実装する方法を紹介します。
実装形態は次節で説明しますが、ここでは、persistence token実装形態を選択しています。

remember-me実装形態

remember-me実装形態について、以下の2つがあります。
1)cookie-base実装
remember-meクッキーにusernameとpassword(MD5hashコード)保持 1)

2)persistence token実装
persistent_loginsテーブルにランダムなserial, tokenを保持、clientのremember-meクッキーと照合します。
remember-meクッキーには一意なserialを保持していてアクセスの度にtokenを変える仕組みなのでクッキーがcaptureされてもアクセスされる可能性は低いです。
パスワードが変更になってもtokenが有効であれば使用可能です。
logoutしない限り、有効期限切れになったtokenはpersistent_loginsテーブルにレコードが残ります。
有効期限切れになったtokenを削除する機能はSpring securityに実装されてないので、自前実装が必要です。

persistent_loginsテーブル

テーブル定義SQLを下記に示します。

CREATE TABLE "PERSISTENT_LOGINS"
(	"USERNAME" VARCHAR2(100 BYTE) NOT NULL,
	"SERIES" VARCHAR2(64 BYTE),
	"TOKEN" VARCHAR2(64 BYTE) NOT NULL,
	"LAST_USED" TIMESTAMP (6) NOT NULL,
	CONSTRAINT "PERSISTENT_LOGINS_PK" PRIMARY KEY("SERIES")
)

以下はテーブルについてのメモです。
・テーブル名、カラム名は変更できない。
・同一usernameでも別端末からアクセスすると別レコード(series)が生成される。
・ログアウトするとレコードは削除される。
・同一端末でも開発環境、検証環境にrememeber-meログインする場合、2レコードが作成される。

persistent remember-me実装 必要ライブラリ

以下にpersistent rembember-me実装で必要なライブラリを示します。

ライブラリ説明
jcl-over-slf4j-1.7.36.jarSpring security logging関連
log4j-slf4j-impl-2.17.1.jarlog4j2 bridgeライブラリ
slf4j-api-1.7.32.jarloggingライブラリ
spring-web-4.3.30.RELEASE.jarspring-web
spring-core-4.3.30.RELEASE.jarspring-webの依存ライブラリ
spring-aop-4.3.30.RELEASE.jarspring-webの依存ライブラリ
spring-beans-4.3.30.RELEASE.jarspring-webの依存ライブラリ
spring-context-4.3.30.RELEASE.jarspring-webの依存ライブラリ
spring-expression-4.3.30.RELEASE.jarspring-contextの依存ライブラリ
spring-jdbc-4.3.30.RELEASE.jarspring-jdbcライブラリ
spring-ldap-core-2.3.3.RELEASE.jarspring-security-ldap依存ライブラリ
spring-security-config-4.2.20.RELEASE.jarspring-security configライブラリ
spring-security-core-4.2.20.RELEASE.jarspring-security-ldap依存ライブラリ
spring-security-ldap-4.2.20.RELEASE.jarspring-secruity ldapライブラリ
spring-security-web-4.2.20.RELEASE.jarspring-security webライブラリ
spring-tx-4.3.30.RELEASE.jarspring-jdbc依存ライブラリ

実装編

以下のセクションは実装について説明します。

web.xml

web.xmlに追加する設定についてです。
下記、追加コード部分を抜粋して表示します。

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
 
	<context-param>
		<description>
		</description>
		<param-name>contextClass</param-name>
		<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
	</context-param>
	<context-param>
		<description>
		</description>
		<param-name>contextConfigLocation</param-name> ★1
		<param-value>com.contoso.web.spring</param-value>
	</context-param>
 
	<filter>
		<description>
		</description>
		<display-name>springSecurityFilterChain</display-name>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> ★2
	</filter>
        <filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<servlet-name>action</servlet-name>
	</filter-mapping>

★1
Spring Security関連Annotationが設定されているクラスのパッケージを指定します。
★2
Filter Chainの中でSpring Securityをinterceptする部分です。

PersistenceConfig.java

persitent_loginsテーブルへのデータソース接続情報を読み込ませるためのBeanを定義しているクラスになります。

package com.contoso.web.spring;
 
import javax.sql.DataSource;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
/**
 * <H3>
 * Spring Database Configuration.
 * </H3>
 * @author ri-su
 */
@Configuration
@EnableTransactionManagement
@PropertySource({"classpath:/com/contoso/base/properties/db/persistence-oracle.properties"})1
public class PersistenceConfig {
 
	@Autowired
	private Environment env;2
 
	//***** public method *****
 
	@Bean
	public DataSource dataSource() {
		final DriverManagerDataSource _dataSource = new DriverManagerDataSource();3
		_dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
		_dataSource.setUrl(env.getProperty("jdbc.url"));
		_dataSource.setUsername(env.getProperty("jdbc.user"));
		_dataSource.setPassword(env.getProperty("jdbc.pass"));
		return _dataSource;
	}
 
	//***** protected method *****
	//***** private method *****
	//***** call back method *****
	//***** getter and setter *****
 
}

★1
persistence-oracle.propertiesのパスを指定します。
★2
Springの@Autowired機能で@PropertySourceで指定したPropertiesファイルにアクセスできます。
★3
アプリ起動時にBeanインスタンス化され、後ほど説明するwebSecurityConfig.xmlからデータソースを参照できます。
参照名はメソッド名であるdataSourceです。

SecurityConfig.java

Spring security設定xmlファイル(webSecurityConfig.xml)を指定するクラスです。
WebSecurityConfigurerAdapterクラスを拡張して作成します。

package com.contoso.web.spring;
 
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 
/**
 * <H3>
 * Spring Security Configuration.
 * </H3>
 * @author ri-su
 */
@Configuration
@ComponentScan("com.contoso.web.security")1
@EnableWebSecurity
@ImportResource({"classpath:/WEB-INF/webSecurityConfig.xml"})2
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
	//***** constructor *****
	public SecurityConfig() {
		super();
	}
 
	//***** public method *****
	//***** protected method *****
	//***** private method *****
	//***** call back method *****
	//***** getter and setter *****
 
}

☆1
webSecurityConfig.xmlからComponentとして参照したいクラスが位置しているパッケージを指定します。
☆2
webSecurityConfig.xmlのパスを指定します。

webSecurityConfig.xml

Spring security設定ファイルです。

<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"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd">
 
	<sec:http use-expressions="true">
		<sec:intercept-url pattern="/js/**" access="permitAll" />
  		<sec:intercept-url pattern="/css/**" access="permitAll" />
    		<sec:intercept-url pattern="/images/**" access="permitAll" />
    		<sec:intercept-url pattern="/logon.do" access="permitAll" />
    		<sec:intercept-url pattern="/**" access="authenticated" />
 
		<sec:form-login login-page="/logon.do"
			login-processing-url="/logon.do"
			authentication-success-handler-ref="mySavedRequestAwareAuthenticationSuccessHandler"
			authentication-failure-url="/logon.do?error=true" /> ★1
 
		<sec:logout logout-url="/logout.do"
			success-handler-ref="simpleUrlLogoutSuccessHandler"
			delete-cookies="JSESSIONID" /> ★2
 
		<!-- 86400 Seconds = 1day -->
		<sec:remember-me authentication-success-handler-ref="rememberMeAuthenticationSuccessHandler"
			data-source-ref="dataSource"
			token-validity-seconds="2592000" /> ★3
 
		<sec:csrf disabled="true" />
	</sec:http>
 
	<!-- Persistent Remember Me Service -->
	<bean id="rememberMeAuthenticationProvider"
		class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices"> ★4
		<constructor-arg value="myAppKey" />
		<constructor-arg ref="ldapUserDetailsService" />
		<constructor-arg ref="jdbcTokenRepository" />
	</bean>
 
	<!-- Uses a database table to maintain a set of persistent login data -->
	<bean id="jdbcTokenRepository"
		class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl"> ★5
		<property name="createTableOnStartup" value="false" />
		<property name="dataSource" ref="dataSource" />
	</bean>
 
	<!-- Authentication Manager(LDAP) -->
	<sec:authentication-manager alias="authenticationManager"> ★6
		<sec:authentication-provider ref="ldapAuthProvider" />
	</sec:authentication-manager>
 
	<!-- LDAP context source -->
	<bean id="contextSource"
		class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> ★7
		<constructor-arg index="0">
			<list>
				<value>ldap://ldap001.contoso.com:3268</value>
				<value>ldap://ldap002.contoso.com:3268</value>
			</list>
		</constructor-arg>
		<constructor-arg index="1" value="dc=contoso,dc=com" />
		<property name="userDn" value="xxxxxx@contoso.com" />
		<property name="password" value="yyyyy12345" />
	</bean>
 
	<!-- LDAP authentication provider  -->
	<bean id="ldapAuthProvider"
		class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> ★8
		<constructor-arg>
			<bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
				<constructor-arg ref="contextSource" />
				<property name="userSearch" ref="userSearch" />	
			</bean>
		</constructor-arg>
	</bean>
 
	<!-- LDAP user search  -->
	<bean id="userSearch"
		class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> ★9
		<constructor-arg index="0" value="" />
		<constructor-arg index="1" value="(&amp;(objectCategory=Person)(sAMAccountName={0}))" />
		<constructor-arg index="2" ref="contextSource" />
	</bean>
 
	<!-- LDAP userDetailsService  -->
	<bean id="ldapUserDetailsService"
		class="org.springframework.security.ldap.userdetails.LdapUserDetailsService"> ★10
		<constructor-arg ref="userSearch" />
		<property name="userDetailsMapper" ref="ldapUserDetailsMapper" />
	</bean>
 
	<!-- UserDetailsMapper -->
	<bean id="ldapUserDetailsMapper" class="org.springframework.security.ldap.userdetails.LdapUserDetailsMapper" />
 
	<!-- SimpleUrlLogoutSuccessHandler -->
	<bean id="simpleUrlLogoutSuccessHandler"
		class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler">
		<property name="useReferer" value="true" />
	</bean>
</beans>

★1
ログインUrl、認証成功Handlerクラス、認証失敗時の遷移Url等を設定します。
認証成功HandlerクラスであるmySavedRequestAwareAuthenticationSuccessHandlerについては後ほど説明します。

★2
ログアウトUrl、ログアウト成功Handlerクラス等を設定します。
ログアウト成功HadlerクラスはSpringのSimpleUrlLogoutSuccessHandlerを利用しています。

★3
remember-me関連設定を行います。token有効期限は秒(sec)で指定します。
SuccessHandlerについては後ほど説明します。

★4
remember-me実装形態としてpersistent token実装を設定しています。
constructorのパラメータ1に指定しているmyAppKeyはクッキーに含まれる署名に使用されるキーです。
省略する場合、SecureRandom関数でランダムに生成されますが、アプリケーション起動時に生成されるので再起動するとremember-meクッキーは全て無効になってしまいます。
アプリケーションを再起動してもクライアントが持つクッキーを有効にする場合は、任意の固定文字列を指定します。

★5
tokenを保存するデータソース関連設定を行います。
dataSourceが参照しているBeanクラスはPersistenceConfig.javaです。

★6
認証マネージャーを設定しています。

★7
ログイン時、LDAPを通して認証を行うことを設定しています。
LDAPドメインは複数指定が可能です。

★8
認証ProviderとしてLdapAuthenticationProviderを指定しています。
contextSourceには☆7を参照しています。

★9
LDAP検索方法を指定しています。Person、SAMアカウント名で検索すると指定しています。

★10
★4で設定しているLdapUserDetailsServiceについて定義しています。

参考情報

以下セクションで参考になる情報を載せます。

Spring securityデフォルトレスポンスヘッダ

Spring remember-me実装にはSpring securityを利用していますので、Spring securityについて理解する必要があります。
Spring security XMLにて設定しなくてもデフォルトでサポートしているレスポンスヘッダをいかに示します。
・Cache-Control(Pragma, Expires)2)
・X-Frame-Options
・X-Content-Type-Options
・X-XSS-Protection
・Strict-Transport-Security3)

デフォルト値について以下の表を参照してください。

Header namevalue(default)目的
Cache-Controlno-cacheコンテンツがキャッシュされるのを防ぐため
X-Frame-OptionsDENY<iframe>に表示されるのを防ぐため
X-Content-Type-Optionslnosniffブラウザがコンテンツの種類を判断する際、コンテンツの内容を見ないようにする
X-Xss-Protection1;mode=blockXSSフィルター機能を有効にして有害なスクリプトを検知するため
1)
クッキーがcaptureされる場合、username, passwordが変わらない限り、有効期限内であれば使用可能な脆弱性があります。
2)
Spring SecurityはHTTP1.0互換のブラウザもサポートするために、PragmaヘッダとExpiresヘッダも出力する。
3)
アプリケーションサーバに対してHTTPSを使ってアクセスがあった場合のみ出力される。

コメント

コメントを入力. Wiki文法が有効です:
X J J F P
 

QR Code
QR Code study:java:rememberme (generated for current page)