差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
study:java:rememberme [2025/02/03 09:51] – [PersistenceConfig.java] bananastudy:java:rememberme [2025/02/07 10:06] (現在) banana
行 2: 行 2:
 ログインを維持してくれるremember-meログイン機能をSpring securityを用いて実装する方法を紹介します。\\ ログインを維持してくれるremember-meログイン機能をSpring securityを用いて実装する方法を紹介します。\\
 実装形態は次節で説明しますが、ここでは、persistence token実装形態を選択しています。 実装形態は次節で説明しますが、ここでは、persistence token実装形態を選択しています。
 +{{keywords>Spring rememeber-me login}}
  
 ====== remember-me実装形態 ====== ====== remember-me実装形態 ======
行 37: 行 38:
  
 ^ライブラリ^説明^ ^ライブラリ^説明^
-|jcl-over-slf4j-1.7.36.jar|Spring security logging関連| +|jcl-over-slf4j-1.7.36.jar|spring security logging関連| 
-|log4j-slf4j-impl-2.17.1.jar|log4j2 bridgeライブラリ+|log4j-slf4j-impl-2.17.1.jar|spring security loggingをlog4j2.xmlにて設定可能にする
-|slf4j-api-1.7.32.jar|loggingライブラリ|+|slf4j-api-1.7.32.jar|spring security logging関連|
 |spring-web-4.3.30.RELEASE.jar|spring-web| |spring-web-4.3.30.RELEASE.jar|spring-web|
 |spring-core-4.3.30.RELEASE.jar|spring-webの依存ライブラリ| |spring-core-4.3.30.RELEASE.jar|spring-webの依存ライブラリ|
行 173: 行 174:
  */  */
 @Configuration @Configuration
-@ComponentScan("com.contoso.web.security"1+@ComponentScan("com.contoso.web.security"1
 @EnableWebSecurity @EnableWebSecurity
-@ImportResource({"classpath:/WEB-INF/webSecurityConfig.xml"}) 2+@ImportResource({"classpath:/WEB-INF/webSecurityConfig.xml"}) 2
 public class SecurityConfig extends WebSecurityConfigurerAdapter { public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
行 221: 行 222:
  login-processing-url="/logon.do"  login-processing-url="/logon.do"
  authentication-success-handler-ref="mySavedRequestAwareAuthenticationSuccessHandler"  authentication-success-handler-ref="mySavedRequestAwareAuthenticationSuccessHandler"
- authentication-failure-url="/logon.do?error=true" /> 1+ authentication-failure-url="/logon.do?error=true" /> 1
  
  <sec:logout logout-url="/logout.do"  <sec:logout logout-url="/logout.do"
  success-handler-ref="simpleUrlLogoutSuccessHandler"  success-handler-ref="simpleUrlLogoutSuccessHandler"
- delete-cookies="JSESSIONID" /> 2+ delete-cookies="JSESSIONID" /> 2
  
  <!-- 86400 Seconds = 1day -->  <!-- 86400 Seconds = 1day -->
  <sec:remember-me authentication-success-handler-ref="rememberMeAuthenticationSuccessHandler"  <sec:remember-me authentication-success-handler-ref="rememberMeAuthenticationSuccessHandler"
  data-source-ref="dataSource"  data-source-ref="dataSource"
- token-validity-seconds="2592000" /> 3+ token-validity-seconds="2592000" /> 3
  
  <sec:csrf disabled="true" />  <sec:csrf disabled="true" />
行 237: 行 238:
  <!-- Persistent Remember Me Service -->  <!-- Persistent Remember Me Service -->
  <bean id="rememberMeAuthenticationProvider"  <bean id="rememberMeAuthenticationProvider"
- class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices"> 4+ class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices"> 4
  <constructor-arg value="myAppKey" />  <constructor-arg value="myAppKey" />
  <constructor-arg ref="ldapUserDetailsService" />  <constructor-arg ref="ldapUserDetailsService" />
行 245: 行 246:
  <!-- Uses a database table to maintain a set of persistent login data -->  <!-- Uses a database table to maintain a set of persistent login data -->
  <bean id="jdbcTokenRepository"  <bean id="jdbcTokenRepository"
- class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl"> 5+ class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl"> 5
  <property name="createTableOnStartup" value="false" />  <property name="createTableOnStartup" value="false" />
  <property name="dataSource" ref="dataSource" />  <property name="dataSource" ref="dataSource" />
行 251: 行 252:
  
  <!-- Authentication Manager(LDAP) -->  <!-- Authentication Manager(LDAP) -->
- <sec:authentication-manager alias="authenticationManager"> 6+ <sec:authentication-manager alias="authenticationManager"> 6
  <sec:authentication-provider ref="ldapAuthProvider" />  <sec:authentication-provider ref="ldapAuthProvider" />
  </sec:authentication-manager>  </sec:authentication-manager>
行 257: 行 258:
  <!-- LDAP context source -->  <!-- LDAP context source -->
  <bean id="contextSource"  <bean id="contextSource"
- class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> 7+ class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> 7
  <constructor-arg index="0">  <constructor-arg index="0">
  <list>  <list>
行 271: 行 272:
  <!-- LDAP authentication provider  -->  <!-- LDAP authentication provider  -->
  <bean id="ldapAuthProvider"  <bean id="ldapAuthProvider"
- class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> 8+ class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> 8
  <constructor-arg>  <constructor-arg>
  <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">  <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
行 282: 行 283:
  <!-- LDAP user search  -->  <!-- LDAP user search  -->
  <bean id="userSearch"  <bean id="userSearch"
- class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> 9+ class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> 9
  <constructor-arg index="0" value="" />  <constructor-arg index="0" value="" />
  <constructor-arg index="1" value="(&amp;(objectCategory=Person)(sAMAccountName={0}))" />  <constructor-arg index="1" value="(&amp;(objectCategory=Person)(sAMAccountName={0}))" />
行 290: 行 291:
  <!-- LDAP userDetailsService  -->  <!-- LDAP userDetailsService  -->
  <bean id="ldapUserDetailsService"  <bean id="ldapUserDetailsService"
- class="org.springframework.security.ldap.userdetails.LdapUserDetailsService"> 10+ class="org.springframework.security.ldap.userdetails.LdapUserDetailsService"> 10
  <constructor-arg ref="userSearch" />  <constructor-arg ref="userSearch" />
  <property name="userDetailsMapper" ref="ldapUserDetailsMapper" />  <property name="userDetailsMapper" ref="ldapUserDetailsMapper" />
行 307: 行 308:
 </code> </code>
  
-1\\ +1\\ 
-ログインUrl、認証成功Handlerクラス、認証失敗時の遷移Url等を設定します。\\+ログインUrl、ログイン成功Handlerクラス、認証失敗時の遷移Url等を設定します。\\
 認証成功HandlerクラスであるmySavedRequestAwareAuthenticationSuccessHandlerについては後ほど説明します。\\ 認証成功HandlerクラスであるmySavedRequestAwareAuthenticationSuccessHandlerについては後ほど説明します。\\
  
-2\\+2\\
 ログアウトUrl、ログアウト成功Handlerクラス等を設定します。\\ ログアウトUrl、ログアウト成功Handlerクラス等を設定します。\\
 ログアウト成功HadlerクラスはSpringのSimpleUrlLogoutSuccessHandlerを利用しています。\\ ログアウト成功HadlerクラスはSpringのSimpleUrlLogoutSuccessHandlerを利用しています。\\
  
-3\\+3\\
 remember-me関連設定を行います。token有効期限は秒(sec)で指定します。\\ remember-me関連設定を行います。token有効期限は秒(sec)で指定します。\\
 SuccessHandlerについては後ほど説明します。\\ SuccessHandlerについては後ほど説明します。\\
  
-4\\+4\\
 remember-me実装形態としてpersistent token実装を設定しています。\\ remember-me実装形態としてpersistent token実装を設定しています。\\
 constructorのパラメータ1に指定しているmyAppKeyはクッキーに含まれる署名に使用されるキーです。\\ constructorのパラメータ1に指定しているmyAppKeyはクッキーに含まれる署名に使用されるキーです。\\
行 325: 行 326:
 アプリケーションを再起動してもクライアントが持つクッキーを有効にする場合は、任意の固定文字列を指定します。\\ アプリケーションを再起動してもクライアントが持つクッキーを有効にする場合は、任意の固定文字列を指定します。\\
  
-5\\+5\\
 tokenを保存するデータソース関連設定を行います。\\ tokenを保存するデータソース関連設定を行います。\\
 dataSourceが参照しているBeanクラスはPersistenceConfig.javaです。 dataSourceが参照しているBeanクラスはPersistenceConfig.javaです。
  
-6\\+6\\
 認証マネージャーを設定しています。 認証マネージャーを設定しています。
  
-7\\+7\\
 ログイン時、LDAPを通して認証を行うことを設定しています。\\ ログイン時、LDAPを通して認証を行うことを設定しています。\\
 LDAPドメインは複数指定が可能です。\\ LDAPドメインは複数指定が可能です。\\
  
-8\\+8\\
 認証ProviderとしてLdapAuthenticationProviderを指定しています。\\ 認証ProviderとしてLdapAuthenticationProviderを指定しています。\\
 contextSourceには☆7を参照しています。\\ contextSourceには☆7を参照しています。\\
  
-9\\+9\\
 LDAP検索方法を指定しています。Person、SAMアカウント名で検索すると指定しています。\\ LDAP検索方法を指定しています。Person、SAMアカウント名で検索すると指定しています。\\
  
-10\\ +10\\ 
-4で設定しているLdapUserDetailsServiceについて定義しています。+4で設定しているLdapUserDetailsServiceについて定義しています。 
 + 
 +===== MySavedRequestAwareAuthenticationSuccessHandler.java ===== 
 +webSecurityConfig.xmlにて参照名mySavedRequestAwareAuthenticationSuccessHandlerのクラスを以下に示します。 
 +<code java> 
 +package com.contoso.web.security; 
 + 
 +import java.io.IOException; 
 + 
 +import javax.servlet.ServletException; 
 +import javax.servlet.http.HttpServletRequest; 
 +import javax.servlet.http.HttpServletResponse; 
 + 
 +import org.slf4j.Logger; 
 +import org.slf4j.LoggerFactory; 
 +import org.springframework.security.core.Authentication; 
 +import org.springframework.security.ldap.userdetails.LdapUserDetails; 
 +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; 
 +import org.springframework.stereotype.Component; 
 +import org.springframework.util.StringUtils; 
 + 
 +/** 
 + * <H3> 
 + * Custom implementation of SavedRequestAwareAuthenticationSuccessHandler 
 + * </H3> 
 + * @author ri-su 
 + */ 
 +@Component(value = "mySavedRequestAwareAuthenticationSuccessHandler"
 +public class MySavedRequestAwareAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { 
 + 
 + private static final Logger logger = LoggerFactory.getLogger(MySavedRequestAwareAuthenticationSuccessHandler.class); 
 + //ログイン画面直アクセスの場合の遷移先 
 + private static final String DEFAULT_TARGET_URL = "/sample/Main.do"; 
 + 
 + //***** public method ***** 
 + 
 + /* (non-Javadoc) 
 + * @see org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.security.core.Authentication) 
 + */ 
 + @Override 
 + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) 
 + throws ServletException, IOException { ★1 
 + //authorization 
 + doAuthorization(request, authentication); 
 + //set defaultTargetUrl 
 + setDefaultTargetUrl(request); 
 + //delegate to super method 
 + super.onAuthenticationSuccess(request, response, authentication); 
 +
 + 
 + //***** protected method ***** 
 + //***** private method ***** 
 + 
 + //authorization 
 + private void doAuthorization(final HttpServletRequest request, final Authentication authentication) { 
 + //username 
 + String _username = ((LdapUserDetails)authentication.getPrincipal()).getUsername(); ★2 
 + logger.debug("username: {}", _username); 
 + 
 +                //do something here. 例)権限付与、セッションにユーザー情報保持 
 + }//doAuthorization 
 + 
 + private void setDefaultTargetUrl(final HttpServletRequest request) { 
 + String _targetUrl = ""; 
 + //REQUEST_CACHEのredirectUrlをログイン画面のhiddenから取得 
 + String _cachedUrl = request.getParameter(REDIRECT_URL_HIDDEN_NAME); 
 + String _referer = request.getParameter(REFERER_URL_HIDDEN_NAME); 
 + logger.debug("cached redirect url: {}", _cachedUrl); 
 + logger.debug("referer url: {}", _referer); 
 + //determine targetUrl 
 + if (StringUtils.hasText(_referer)) { 
 + _targetUrl = determineTargetUrlFromReferer(_referer); 
 + } else if (StringUtils.hasText(_cachedUrl)) { 
 + _targetUrl = _cachedUrl; ★3 
 + } else { 
 + _targetUrl = DEFAULT_TARGET_URL; 
 +
 + logger.debug("target url: {}", _targetUrl); 
 + 
 + //set defaultTargetUrl 
 + super.setDefaultTargetUrl(_targetUrl); 
 + //always redirect to the value of defaultTargetUrl 
 + setAlwaysUseDefaultTargetUrl(true); 
 + }//setDefaultTargetUrl 
 + 
 +        //refererUrl(ログアウト時)からtargetUrl判断 
 + private String determineTargetUrlFromReferer(String referer) { 
 + String _targetUrl = ""; 
 + //some logic here ★4 
 + return _targetUrl; 
 + }//determineTargetUrlFromReferer 
 + 
 + //***** call back method ***** 
 + //***** getter and setter ***** 
 + 
 +
 + 
 +</code> 
 + 
 +★1\\ 
 +onAuthenticationSuccessメソッドをオーバーライドします。\\ 
 +ここでは、認証成功後の権限付与(authorization)、遷移先などの設定を行います。\\ 
 + 
 +★2\\ 
 +AuthenticationからLDAP認証のusernameが取得可能です。\\ 
 +usernameを用いてアプリ側のauthorization処理を実装します。\\ 
 + 
 +★3\\ 
 +REQUEST_CACHEのredirectUrlをログイン画面のhiddenから取得します。\\ 
 +REQUEST_CACHEに残っていれば、そこに遷移します。\\ 
 + 
 +★4\\ 
 +ログアウト時のurlがログイン画面のhiddenから取得できれば、ログアウト時の画面に遷移します。 
 + 
 +===== RememberMeAuthenticationSuccessHandler.java ===== 
 +webSecurityConfig.xmlにて参照名rememberMeAuthenticationSuccessHandlerのクラスを以下に示します。 
 +<code java> 
 +package com.contoso.web.security; 
 + 
 +import java.io.IOException; 
 + 
 +import javax.servlet.ServletException; 
 +import javax.servlet.http.HttpServletRequest; 
 +import javax.servlet.http.HttpServletResponse; 
 +import javax.servlet.http.HttpSession; 
 + 
 +import org.slf4j.Logger; 
 +import org.slf4j.LoggerFactory; 
 +import org.springframework.security.core.Authentication; 
 +import org.springframework.security.ldap.userdetails.LdapUserDetails; 
 +import org.springframework.security.web.DefaultRedirectStrategy; 
 +import org.springframework.security.web.RedirectStrategy; 
 +import org.springframework.security.web.WebAttributes; 
 +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 
 +import org.springframework.stereotype.Component; 
 + 
 +/** 
 + * <H3> 
 + * authentication-success-handler in remember-me 
 + * </H3> 
 + * @author ri-su 
 + */ 
 +@Component(value = "rememberMeAuthenticationSuccessHandler"
 +public class RememberMeAuthenticationSuccessHandler implements AuthenticationSuccessHandler { 
 + 
 + private static final Logger logger = LoggerFactory.getLogger(RememberMeAuthenticationSuccessHandler.class); 
 + private static final String DEFAULT_TARGET_URL = "/sample/Main.do"; 
 + private static final String LOGON_URL = "/logon.do"; 
 + private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); 
 + 
 + //***** public method ***** 
 + 
 + /* (non-Javadoc) 
 + * @see org.springframework.security.web.authentication.AuthenticationSuccessHandler#onAuthenticationSuccess 
 + * (javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.security.core.Authentication) 
 + */ 
 + @Override 
 + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) 
 + throws IOException, ServletException { ★1 
 + handle(request, response, authentication); 
 + clearAuthenticationAttributes(request); 
 +
 + 
 + //***** protected method ***** 
 + 
 + protected void handle(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException { 
 + //--- determine targetUrl 
 + final String _targetUrl = determineTargetUrl(request); 
 + 
 + if (response.isCommitted()) { 
 + logger.debug("Response has already been committed. Unable to redirect to " + _targetUrl); 
 + return; 
 +
 + //authorization 
 + doAuthorization(request, authentication); 
 + redirectStrategy.sendRedirect(request, response, _targetUrl); 
 + }//handle 
 + 
 + /** 
 + * determine target URL 
 + * <br> 
 + * @param request 
 + * @return 
 + */ 
 + protected String determineTargetUrl(final HttpServletRequest request) { 
 + String _targetUrl = request.getServletPath(); 
 + logger.debug("targetUrl: {}", _targetUrl); 
 + String _redirectUrl = ""; 
 + if (!Validator.isNullOrBlank(_targetUrl) && 
 + _targetUrl.indexOf(LOGON_URL) != -1) { ★2 
 +     _redirectUrl = DEFAULT_TARGET_URL; 
 + } else { 
 + _redirectUrl = _targetUrl; 
 +
 + return _redirectUrl; 
 + }//determineTargetUrl 
 + 
 +      /** 
 +       * Removes temporary authentication-related data which may have been stored in the session 
 +       * during the authentication process. 
 +       */ 
 + protected final void clearAuthenticationAttributes(final HttpServletRequest request) { 
 + final HttpSession _session = request.getSession(false); 
 + 
 + if (_session == null) { 
 + return; 
 +
 + 
 + _session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); 
 + }//clearAuthenticationAttributes 
 + 
 + //***** private method ***** 
 + 
 + //authorization 
 + private void doAuthorization(final HttpServletRequest request, Authentication authentication) { 
 + //username 
 + String _username = ((LdapUserDetails)authentication.getPrincipal()).getUsername(); ★3 
 + logger.debug("username: {}", _username); 
 +  
 +        //do something here. 例)権限付与、セッションにユーザー情報保持 
 + }//doAuthorization 
 + 
 + //***** call back method ***** 
 + //***** getter and setter ***** 
 + 
 +
 + 
 +</code> 
 + 
 +★1\\ 
 +onAuthenticationSuccessメソッドをオーバーライドします。\\ 
 +ここでは、認証成功後の権限付与(authorization)、遷移先などの設定を行います。\\ 
 + 
 +★2\\ 
 +ログイン画面にアクセスする場合の遷移先を指定します。\\ 
 + 
 +★3\\ 
 +AuthenticationからLDAP認証のusernameが取得可能です。\\ 
 +usernameを用いてアプリ側のauthorization処理を実装します。\\をを 
 +===== NewLogon.jsp ===== 
 +Formログイン画面の例を以下に示します。 
 +<code> 
 +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 
 +<%@ include file="/Taglibs.jsp" %> 
 + 
 +<form name="f" action="${pageContext.request.contextPath}/logon.do" method="POST"> 
 + <table> 
 + <tr> 
 + <td><bean:message key="label.mothers" /></td> 
 + <td><input type="text" name="username" size="9" maxlength="7"></td> 
 + </tr> 
 + <tr> 
 + <td><bean:message key="label.passWord" /></td> 
 + <td><input type="password" name="password" size="20" maxlength="25" /></td> 
 + </tr> 
 + <tr> 
 + <td>Remember Me:</td> 
 + <td><input type="checkbox" name="remember-me" /></td> 
 + </tr> 
 + <tr> 
 + <td><input name="submit" type="submit" value="<bean:message key='label.login' />" /></td> 
 + </tr> 
 + </table> 
 + <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> 
 + <input type="hidden" name="CACHED_REQUEST_URL" value="${sessionScope.SPRING_SECURITY_SAVED_REQUEST.redirectUrl}" /> 
 + <input type="hidden" name="REFERER_URL" value="${header['Referer']}" /> 
 +</form> 
 + 
 +</code> 
 + 
 +===== NewLogonAction.java ===== 
 +ログイン画面Actionの例を以下に示します。\\ 
 +ここではStruts1を利用したActionクラスの例です。 
 +<code java> 
 +package com.contoso.web; 
 + 
 +import javax.servlet.http.HttpServletRequest; 
 +import javax.servlet.http.HttpServletResponse; 
 + 
 +import org.apache.struts.action.Action; 
 +import org.apache.struts.action.ActionError; 
 +import org.apache.struts.action.ActionErrors; 
 +import org.apache.struts.action.ActionForward; 
 +import org.apache.struts.action.ActionMapping; 
 + 
 +/** 
 + * <H3> 
 + * Action for Remember-me Login 
 + * </H3> 
 + * @author ri-su 
 + */ 
 +public class NewLogonAction extends Action { 
 + 
 + //***** public method ***** 
 + 
 + @Override 
 + public ActionForward execute(ActionMapping mapping, AbstractActionForm form, HttpServletRequest request, 
 + HttpServletResponse response) throws Exception 
 + if (request.getParameter("error") != null) { ★1 
 + ActionErrors _errors = new ActionErrors(); 
 + _errors.add("username", new ActionError("errors.logon.fail")); 
 + saveErrors(request, _errors); 
 + return mapping.getInputForward(); 
 +
 + 
 + return mapping.findForward(SUCCESS); 
 +
 + 
 + //***** protected method ***** 
 + //***** private method ***** 
 + //***** call back method ***** 
 + //***** getter and setter ***** 
 + 
 +
 + 
 +</code> 
 + 
 +★1\\ 
 +ログインエラー発生時、エラーメッセージをActionErrorにセットして画面に表示します。
  
 ====== 参考情報 ====== ====== 参考情報 ======

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