spring security 認証処理を自作する方法を解説

spring

認証処理ってシステムの要件によってはID・PWのチェックだけではなく、CookieやSessionを使ってゴニョゴニョ独自仕様にしていくのをよく耳にします。

もちろんspring securityでもこの独自の仕様にカスタマイズできるような作りになっています。
「うちの会社は特殊だから・・」といってspring securityを諦めるのではなく、どんどんカスタマイズしていきしょう。

今回はspring securityを使って、認証処理を自作する方法を紹介します。

[スポンサーリンク]

Spring Securityの設定ファイル

少し量が多いのですが、大事なところなので省略せず全てを載せておきます。
自作する認証filterは2つです。
この2つのfilterがチェーンして次々動いていきます。

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-4.0.xsd">

    <!-- 自作の認証を使う場合は、entry-point-refを指定します。 --> 
    <http auto-config="false" use-expressions="true" entry-point-ref="loginUrlAuthenticationEntryPoint" >

        <!-- 自作の認証filterを指定します。 --> 
        <custom-filter after="FORM_LOGIN_FILTER" ref="customLoginFilter" /> 

        <intercept-url pattern="/top*" access="hasAnyRole('ROLE_ADMIN', 'ROLE_USER')" />
        <intercept-url pattern="/admin*" access="hasRole('ROLE_ADMIN')" />
        <intercept-url pattern="/user*" access="hasRole('ROLE_USER')" />

        <access-denied-handler error-page="/403" />
        
        <!-- 認証のログイン処理は、自作認証を使う場合は不要です。 -->
        <!-- 
        <form-login 
            login-page="/" 
            default-target-url="/top" 
            authentication-failure-url="/error" 
            login-processing-url="/j_spring_security_check"/>
         -->
         
        <logout
            logout-url="/logout"
            logout-success-url="/"
            invalidate-session="true"/>
        <!-- anonymousユーザのROLE -->
        <anonymous granted-authority="ROLE_ANONYMOUS" />
    </http>

    <!-- entry-point-refは、どのパスへアクセスした時に自作認証filterを実行するのかを指定します。 --> 
    <beans:bean id="loginUrlAuthenticationEntryPoint"
         class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <beans:constructor-arg value="/" />
    </beans:bean>

    <!-- 最初に動く認証filterです。 --> 
    <beans:bean id="customLoginFilter"
        class="jp.co.security.CustomLoginFilter">
        <!-- 次に動くfilterです。ここで認可(ロール)の設定をします。 --> 
        <beans:property name="authenticationManager" ref="authenticationManager" />
        <beans:property name="authenticationFailureHandler" ref="authenticationFailureHandler" />
        <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
        <beans:property name="filterProcessesUrl" value="/authentication" /> 
    </beans:bean>
    
    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="customAuthenticationProvider" />
    </authentication-manager>
     
    <beans:bean id="customAuthenticationProvider"
        class="jp.co.security.CustomAuthenticationProvider" />
    
    <beans:bean id="authenticationFailureHandler"
        class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler" >
        <beans:property name="defaultFailureUrl" value="/error"/>
        <beans:property name="useForward" value="true"/>
        <beans:property name="exceptionMappings">
            <beans:props>
              <beans:prop key=
                "org.springframework.security.authentication.AuthenticationServiceException">
                  /error
              </beans:prop>
            </beans:props>
        </beans:property>

    </beans:bean>
        
    <beans:bean id="authenticationSuccessHandler"
        class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler" >
        <beans:property name="targetUrlParameter" value="redirectTo"/> 
    </beans:bean>

</beans:beans>

最初に動く認証filter

ここではID・PWをチェックしています。ID・PWのどちらかが不正だった場合は例外をあげるようにしています。
二回目に動く認証filterへの値の引渡しはハイライト部分のTokenを使っています。
もしCookieやSessionから値をとってゴニョゴニョする場合は、requestから取得して処理して下さい。

public class CustomLoginFilter extends UsernamePasswordAuthenticationFilter{

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {

        String username = super.obtainUsername(request);
        String password = super.obtainPassword(request);

        if ("admin".equals(username)) {
            if (!"admin".equals(password)) {
                throw new AuthenticationServiceException("Password is invalid.");
            }
        } else if ("user".equals(username)) {
            if (!"user".equals(password)) {
                throw new AuthenticationServiceException("Password is invalid.");
            }
        } else {
            throw new AuthenticationServiceException("User Name is invalid.");
        }

        CustomUsernamePasswordAuthenticationToken authRequest =
                new CustomUsernamePasswordAuthenticationToken(username, password);

        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

受け渡しに使っているToken

最初に動くfilterと2回目に動くfilter間で値を受け渡す場合は、UsernamePasswordAuthenticationTokenを継承してTokenを使うようにして下さい。

public class CustomUsernamePasswordAuthenticationToken extends
        UsernamePasswordAuthenticationToken {
    private static final long serialVersionUID = 1L;

    public CustomUsernamePasswordAuthenticationToken(Object principal,
            Object credentials) {
        super(principal, credentials);
    }
}

二回目に動く認証filter

ここでは権限(ロール)をセットしています。

public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        if ("admin".equals(authentication.getName())) {
            return new UsernamePasswordAuthenticationToken(null, null,
                    Collections.singletonList(new SimpleGrantedAuthority("ROLE_ADMIN")));
        } else if ("user".equals(authentication.getName())) {
            return new UsernamePasswordAuthenticationToken(null, null,
                    Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
        } else {
            throw new AuthenticationServiceException("Authentication Error");
        }
    }

    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }

springとjavaのバージョン

springframeworknのバージョンは、4.0.1を使っています。
javaは、version 8を使っています。

サンプルコード

EclipseでGitHubからCloneしたら「プロジェクトを右クリック > Configure > Convert to maven project」をしてビルド&実行してください。
GitHubでサンプルソースを公開しています。

さいごに

spring securityは基本的にはfilterをチェーンさせて動いています。
実は他にも色々なfilterが用意されていますので、ログイン時以外にもfiterを動かせることもできます。
お好みでカスタマイズしてみてください。

それでは!