Skip to content

Instantly share code, notes, and snippets.

@minazou67
Created January 19, 2015 11:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save minazou67/9b610eb0ef7c293d72f5 to your computer and use it in GitHub Desktop.
Save minazou67/9b610eb0ef7c293d72f5 to your computer and use it in GitHub Desktop.
Spring Security 3.2.5 note

Spring Security 3.2.5 note

実装に苦労したのでメモ。

前提条件

  • Public なページと Secure なページを同一モジュールで実装
  • Session 管理は Spring Security 任せ
  • org.springframework.security.web.csrf.CsrfFilter を使用
  • spring-framework-4.1.4 と併用
  • XML で Bean 定義

Public ページからの認証情報へのアクセス

問題点

<sec:http pattern="/public/**" security="none"/>

公開ページに関しては上記のように設定し、Spring Security の Filter Chain の対象外としたいが、 対象外にすると Spring Security 管理の認証情報にアクセスできない。

解決方法

例えば、公開ページであってもヘッダー領域の Login・Logout ボタンの表示判定などには Spring 管理 の認証情報へのアクセスが必要になるが、その場合は必ず Filter Chain の対象とする。

Filter Chain の対象とすることで発生する Session 関連の問題に関しては後述する。

ログアウト時の Cookie 削除

問題点

<sec:logout logout-url="/logout" delete-cookies="JSESSIONID"/>

上記のようにログアウト時に Cookie から JSESSIONID を削除する設定を行っても、登録時と Path が異なるため削除されない。

Spring Security が Session を開始する際に Cookie に設定する JSESSIONID のパスは、request.getContextPath() + "/" であるのに 対して、org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler が設定するパスは request.getContextPath() である。

また、セッション管理を Spring Security へ任せている場合は、 ログイン画面表示時点でセッションが開始され CSRF 対策のトークンが発行される。

従って、ログイン画面表示後にヘッダーメニュー等から公開画面へ戻った場合には、 Cookie に JSESSIONID が残ったままとなり画面遷移時に意図しない Session Timeout が発生してしまう。

解決方法

特定条件下では Cookie から JSESSIONID を削除する Custom Filter を作成し、Filter Chain の最後に追加する。

<sec:custom-filter ref="cookieClearingFilter" after="FILTER_SECURITY_INTERCEPTOR"/>

<bean id="cookieClearingFilter" class="org.minazou67.samples.filter.CookieClearingFilter"/>

Custom Filter では、ログイン画面や認証済みの場合は Cookie 削除の対象外とする必要がある。

public class CookieClearingFilter extends GenericFilterBean {

  private String loginUrl = "/login";

  public void setLoginUrl(final String loginUrl) {
    this.loginUrl = loginUrl;
  }

  @Override
  public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
      throws IOException, ServletException {

    final HttpServletRequest httpRequest = (HttpServletRequest) request;
    final HttpServletResponse httpResponse = (HttpServletResponse) response;

    if (!(isClearing(httpRequest, httpResponse))) {
      chain.doFilter(request, response);
      return;
    }

    final Cookie cookie = new Cookie("JSESSIONID", null);
    cookie.setPath(httpRequest.getContextPath() + "/");
    cookie.setMaxAge(0);
    httpResponse.addCookie(cookie);

    chain.doFilter(request, response);
  }

  protected boolean isClearing(final HttpServletRequest request, final HttpServletResponse response) {

    if (request.getRequestURI().equals(request.getContextPath() + loginUrl)) {
      return false;
    }

    if ((SecurityContextHolder.getContext() != null)) {
      final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
      if ((auth != null) && auth.isAuthenticated()) {
        if (!(auth.getName().equals("anonymousUser"))) {
          return false;
        }
      }
    } else {
      return false;
    }

    if (request.getRequestedSessionId() == null) {
      return false;
    }

    return true;
  }
}

ログアウト時の Session 破棄

問題点

<sec:session-management invalid-session-url="/error/invalidSession"/>

上記のように invalid-session-url を設定した場合は、セッションが不当な場合に指定 URL へ遷移する。 Session Timeout のチェック処理を Spring Security に委ねる場合に設定する。

<sec:logout logout-url="/logout" invalidate-session="true"/>

上記のように invalidate-sessiontrue を設定するとログアウト処理時にセッションが破棄されるが、 ログアウト処理後にリダイレクトされるページが Filter Chain の対象である場合に org.springframework.security.web.session.SessionManagementFilter で invalid として処理されてしまう。

解決方法

<sec:logout logout-url="/logout" invalidate-session="false"/>

上記のようにログアウト時にセッションを破棄しないようにするか、 エラー時の遷移先を Filter Chain の対象外にするかしか解決策はない。

ユーザ毎の最大セッション数エラーの識別

問題点

<sec:session-management>
  <sec:concurrency-control expired-url="/error/invalidSession" max-sessions="1" error-if-maximum-exceeded="true"/>
</sec:session-management>

上記のように error-if-maximum-exceededtrue を設定すると、 ユーザ毎の最大セッション数を超えた場合にエラーが発生するが、 通常の認証エラー(ユーザ名、パスワード不正)との区別がつかない。

解決策

独自の AuthenticationFailureHandler を作成し登録する。

<sec:form-login login-page="/login" authentication-failure-handler-ref="authenticationFailureHandler"/>

<bean id="authenticationFailureHandler" class="org.minazou67.samples.handler.CustomAuthenticationFailureHandler">
  <constructor-arg value="/login"/>
</bean>

引き数の org.springframework.security.core.AuthenticationException から例外の発生原因を特定し、 エラーメッセージを出し分けする。

public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

  private String defaultFailureUrl;

  public CustomAuthenticationFailureHandler(final String defaultFailureUrl) {
    this.defaultFailureUrl = defaultFailureUrl;
  }

  @Override
  public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response,
      final AuthenticationException exception) throws IOException, ServletException {

    if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
      setDefaultFailureUrl(defaultFailureUrl + "?badCredentials=true");
    } else if (exception.getClass().isAssignableFrom(SessionAuthenticationException.class)) {
      setDefaultFailureUrl(defaultFailureUrl + "?maximumSessions=true");
    }

    super.onAuthenticationFailure(request, response, exception);
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment