差分
このページの2つのバージョン間の差分を表示します。
| 両方とも前のリビジョン前のリビジョン次のリビジョン | 前のリビジョン | ||
| study:java:rememberme [2025/02/03 09:52] – [SecurityConfig.java] banana | study:java:rememberme [2025/02/07 10:06] (現在) – banana | ||
|---|---|---|---|
| 行 2: | 行 2: | ||
| ログインを維持してくれるremember-meログイン機能をSpring securityを用いて実装する方法を紹介します。\\ | ログインを維持してくれるremember-meログイン機能をSpring securityを用いて実装する方法を紹介します。\\ | ||
| 実装形態は次節で説明しますが、ここでは、persistence token実装形態を選択しています。 | 実装形態は次節で説明しますが、ここでは、persistence token実装形態を選択しています。 | ||
| + | {{keywords> | ||
| ====== remember-me実装形態 ====== | ====== remember-me実装形態 ====== | ||
| 行 37: | 行 38: | ||
| ^ライブラリ^説明^ | ^ライブラリ^説明^ | ||
| - | |jcl-over-slf4j-1.7.36.jar|Spring | + | |jcl-over-slf4j-1.7.36.jar|spring |
| - | |log4j-slf4j-impl-2.17.1.jar|log4j2 | + | |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 |
| |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の依存ライブラリ| | ||
| 行 221: | 行 222: | ||
| login-processing-url="/ | login-processing-url="/ | ||
| authentication-success-handler-ref=" | authentication-success-handler-ref=" | ||
| - | authentication-failure-url="/ | + | authentication-failure-url="/ |
| < | < | ||
| success-handler-ref=" | success-handler-ref=" | ||
| - | delete-cookies=" | + | delete-cookies=" |
| <!-- 86400 Seconds = 1day --> | <!-- 86400 Seconds = 1day --> | ||
| < | < | ||
| data-source-ref=" | data-source-ref=" | ||
| - | token-validity-seconds=" | + | token-validity-seconds=" |
| < | < | ||
| 行 237: | 行 238: | ||
| <!-- Persistent Remember Me Service --> | <!-- Persistent Remember Me Service --> | ||
| <bean id=" | <bean id=" | ||
| - | class=" | + | class=" |
| < | < | ||
| < | < | ||
| 行 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=" | <bean id=" | ||
| - | class=" | + | class=" |
| < | < | ||
| < | < | ||
| 行 251: | 行 252: | ||
| <!-- Authentication Manager(LDAP) --> | <!-- Authentication Manager(LDAP) --> | ||
| - | < | + | < |
| < | < | ||
| </ | </ | ||
| 行 257: | 行 258: | ||
| <!-- LDAP context source --> | <!-- LDAP context source --> | ||
| <bean id=" | <bean id=" | ||
| - | class=" | + | class=" |
| < | < | ||
| < | < | ||
| 行 271: | 行 272: | ||
| <!-- LDAP authentication provider | <!-- LDAP authentication provider | ||
| <bean id=" | <bean id=" | ||
| - | class=" | + | class=" |
| < | < | ||
| <bean class=" | <bean class=" | ||
| 行 282: | 行 283: | ||
| <!-- LDAP user search | <!-- LDAP user search | ||
| <bean id=" | <bean id=" | ||
| - | class=" | + | class=" |
| < | < | ||
| < | < | ||
| 行 290: | 行 291: | ||
| <!-- LDAP userDetailsService | <!-- LDAP userDetailsService | ||
| <bean id=" | <bean id=" | ||
| - | class=" | + | class=" |
| < | < | ||
| < | < | ||
| 行 307: | 行 308: | ||
| </ | </ | ||
| - | ☆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; | ||
| + | |||
| + | /** | ||
| + | * < | ||
| + | * Custom implementation of SavedRequestAwareAuthenticationSuccessHandler | ||
| + | * </ | ||
| + | * @author ri-su | ||
| + | */ | ||
| + | @Component(value = " | ||
| + | public class MySavedRequestAwareAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { | ||
| + | |||
| + | private static final Logger logger = LoggerFactory.getLogger(MySavedRequestAwareAuthenticationSuccessHandler.class); | ||
| + | // | ||
| + | private static final String DEFAULT_TARGET_URL = "/ | ||
| + | |||
| + | //***** public method ***** | ||
| + | |||
| + | /* (non-Javadoc) | ||
| + | * @see org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler# | ||
| + | */ | ||
| + | @Override | ||
| + | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) | ||
| + | throws ServletException, | ||
| + | // | ||
| + | doAuthorization(request, | ||
| + | //set defaultTargetUrl | ||
| + | setDefaultTargetUrl(request); | ||
| + | // | ||
| + | super.onAuthenticationSuccess(request, | ||
| + | } | ||
| + | |||
| + | //***** protected method ***** | ||
| + | //***** private method ***** | ||
| + | |||
| + | // | ||
| + | private void doAuthorization(final HttpServletRequest request, final Authentication authentication) { | ||
| + | // | ||
| + | String _username = ((LdapUserDetails)authentication.getPrincipal()).getUsername(); | ||
| + | logger.debug(" | ||
| + | |||
| + | //do something here. 例)権限付与、セッションにユーザー情報保持 | ||
| + | }// | ||
| + | |||
| + | private void setDefaultTargetUrl(final HttpServletRequest request) { | ||
| + | String _targetUrl = ""; | ||
| + | // | ||
| + | String _cachedUrl = request.getParameter(REDIRECT_URL_HIDDEN_NAME); | ||
| + | String _referer = request.getParameter(REFERER_URL_HIDDEN_NAME); | ||
| + | logger.debug(" | ||
| + | logger.debug(" | ||
| + | // | ||
| + | if (StringUtils.hasText(_referer)) { | ||
| + | _targetUrl = determineTargetUrlFromReferer(_referer); | ||
| + | } else if (StringUtils.hasText(_cachedUrl)) { | ||
| + | _targetUrl = _cachedUrl; ★3 | ||
| + | } else { | ||
| + | _targetUrl = DEFAULT_TARGET_URL; | ||
| + | } | ||
| + | logger.debug(" | ||
| + | |||
| + | //set defaultTargetUrl | ||
| + | super.setDefaultTargetUrl(_targetUrl); | ||
| + | //always redirect to the value of defaultTargetUrl | ||
| + | setAlwaysUseDefaultTargetUrl(true); | ||
| + | }// | ||
| + | |||
| + | // | ||
| + | private String determineTargetUrlFromReferer(String referer) { | ||
| + | String _targetUrl = ""; | ||
| + | //some logic here ★4 | ||
| + | return _targetUrl; | ||
| + | }// | ||
| + | |||
| + | //***** call back method ***** | ||
| + | //***** getter and setter ***** | ||
| + | |||
| + | } | ||
| + | |||
| + | </ | ||
| + | |||
| + | ★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; | ||
| + | |||
| + | /** | ||
| + | * < | ||
| + | * authentication-success-handler in remember-me | ||
| + | * </ | ||
| + | * @author ri-su | ||
| + | */ | ||
| + | @Component(value = " | ||
| + | public class RememberMeAuthenticationSuccessHandler implements AuthenticationSuccessHandler { | ||
| + | |||
| + | private static final Logger logger = LoggerFactory.getLogger(RememberMeAuthenticationSuccessHandler.class); | ||
| + | private static final String DEFAULT_TARGET_URL = "/ | ||
| + | private static final String LOGON_URL = "/ | ||
| + | private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); | ||
| + | |||
| + | //***** public method ***** | ||
| + | |||
| + | /* (non-Javadoc) | ||
| + | * @see org.springframework.security.web.authentication.AuthenticationSuccessHandler# | ||
| + | * (javax.servlet.http.HttpServletRequest, | ||
| + | */ | ||
| + | @Override | ||
| + | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) | ||
| + | throws IOException, | ||
| + | handle(request, | ||
| + | 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(" | ||
| + | return; | ||
| + | } | ||
| + | // | ||
| + | doAuthorization(request, | ||
| + | redirectStrategy.sendRedirect(request, | ||
| + | }// | ||
| + | |||
| + | /** | ||
| + | * determine target URL | ||
| + | * < | ||
| + | * @param request | ||
| + | * @return | ||
| + | */ | ||
| + | protected String determineTargetUrl(final HttpServletRequest request) { | ||
| + | String _targetUrl = request.getServletPath(); | ||
| + | logger.debug(" | ||
| + | String _redirectUrl = ""; | ||
| + | if (!Validator.isNullOrBlank(_targetUrl) && | ||
| + | _targetUrl.indexOf(LOGON_URL) != -1) { ★2 | ||
| + | _redirectUrl = DEFAULT_TARGET_URL; | ||
| + | } else { | ||
| + | _redirectUrl = _targetUrl; | ||
| + | } | ||
| + | return _redirectUrl; | ||
| + | }// | ||
| + | |||
| + | /** | ||
| + | | ||
| + | | ||
| + | | ||
| + | protected final void clearAuthenticationAttributes(final HttpServletRequest request) { | ||
| + | final HttpSession _session = request.getSession(false); | ||
| + | |||
| + | if (_session == null) { | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | _session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); | ||
| + | }// | ||
| + | |||
| + | //***** private method ***** | ||
| + | |||
| + | // | ||
| + | private void doAuthorization(final HttpServletRequest request, Authentication authentication) { | ||
| + | // | ||
| + | String _username = ((LdapUserDetails)authentication.getPrincipal()).getUsername(); | ||
| + | logger.debug(" | ||
| + | |||
| + | // | ||
| + | }// | ||
| + | |||
| + | //***** call back method ***** | ||
| + | //***** getter and setter ***** | ||
| + | |||
| + | } | ||
| + | |||
| + | </ | ||
| + | |||
| + | ★1\\ | ||
| + | onAuthenticationSuccessメソッドをオーバーライドします。\\ | ||
| + | ここでは、認証成功後の権限付与(authorization)、遷移先などの設定を行います。\\ | ||
| + | |||
| + | ★2\\ | ||
| + | ログイン画面にアクセスする場合の遷移先を指定します。\\ | ||
| + | |||
| + | ★3\\ | ||
| + | AuthenticationからLDAP認証のusernameが取得可能です。\\ | ||
| + | usernameを用いてアプリ側のauthorization処理を実装します。\\をを | ||
| + | ===== NewLogon.jsp ===== | ||
| + | Formログイン画面の例を以下に示します。 | ||
| + | < | ||
| + | <%@ page language=" | ||
| + | <%@ include file="/ | ||
| + | |||
| + | <form name=" | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | </ | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | </ | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | </ | ||
| + | < | ||
| + | < | ||
| + | </ | ||
| + | </ | ||
| + | <input type=" | ||
| + | <input type=" | ||
| + | <input type=" | ||
| + | </ | ||
| + | |||
| + | </ | ||
| + | |||
| + | ===== 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; | ||
| + | |||
| + | /** | ||
| + | * < | ||
| + | * Action for Remember-me Login | ||
| + | * </ | ||
| + | * @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(" | ||
| + | ActionErrors _errors = new ActionErrors(); | ||
| + | _errors.add(" | ||
| + | saveErrors(request, | ||
| + | return mapping.getInputForward(); | ||
| + | } | ||
| + | |||
| + | return mapping.findForward(SUCCESS); | ||
| + | } | ||
| + | |||
| + | //***** protected method ***** | ||
| + | //***** private method ***** | ||
| + | //***** call back method ***** | ||
| + | //***** getter and setter ***** | ||
| + | |||
| + | } | ||
| + | |||
| + | </ | ||
| + | |||
| + | ★1\\ | ||
| + | ログインエラー発生時、エラーメッセージをActionErrorにセットして画面に表示します。 | ||
| ====== 参考情報 ====== | ====== 参考情報 ====== | ||