認証手形発行所(AuthenticationProvider)でID/PW以外の情報も受け取る部品

通常の認証ではIDとPWがあれば十分です。

しかし、指紋認証などの特殊な認証の場合、チャレンジなどのID/PW以外の情報も必要になります。

この場合、かなりの修正をしなければ対応できなくなってしまいます。

指紋認証用の認証手形発行所(AuthenticationProvider)のコードを記述するのはどの道やらなければならないことですが、

他のSpringSecurityのクラスまで修正をかけるのは避けたいところです。

こんなとき、ID/PW以外の情報も認証手形発行所(AuthenticationProvider)に渡せれば後は設定だけで何とかなりそうです。

そんなことをしてくれるのが、AddInfoAuthenticationProcessingFilterです。

以下では、このクラスの使い方の具体例を見ていきます。

目標

まず、以下のサンプルの目標(ゴール)を示します。

ここでは、指紋認証を例題にしたサンプルを見ていきます。

もちろん他の認証方法にも応用できます。

指紋認証では、challengeという情報を必要とします。

指紋認証用の認証手形発行所(AuthenticationProvider)を作成し、Providerの中でj_usernameとj_passwordと、challengeを取得します。

実際に指紋認証することはできませんので、ここではいつでも認証OKにします。

使用サンプル

<Spring設定ファイルのサンプル>

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:util="http://www.springframework.org/schema/util"

xmlns:aop="http://www.springframework.org/schema/aop"

xmlns:sec="http://www.springframework.org/schema/security"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

http://www.springframework.org/schema/util

http://www.springframework.org/schema/util/spring-util-2.5.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-2.5.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-2.5.xsd

http://www.springframework.org/schema/security

http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">

<!--

-->

<sec:http auto-config="false" entry-point-ref="authenticationProcessingFilterEntryPoint"

access-denied-page="/403.jsp" path-type="ant" >

<sec:intercept-url pattern="/css/*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

<sec:intercept-url pattern="/error.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

<sec:intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

<sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>

<!-- 独自フィルタを入れる場合はコメントアウトします。 -->

<!--sec:form-login login-page="/login.jsp" default-target-url="/top.html"

authentication-failure-url="/login.jsp?error=true"/>

-->

<sec:logout logout-url="/logout" logout-success-url="/login.jsp" invalidate-session="true"/>

<sec:anonymous granted-authority="ROLE_ANONYMOUS"/>

</sec:http>

<!--

AuthenticationProviderオブジェクト

-->

<bean id="authenticationManager"

class="org.springframework.security.providers.ProviderManager">

<property name="providers" >

<list>

<ref local="fingerAuthenticationProvider"/>

</list>

</property>

</bean>

<!-- 認証手形発行所 -->

<bean id="fingerAuthenticationProvider" class="test.security.webapp.FingerAuthenticationProvider">

<property name="userDetailsService" ref="userService"/>

</bean>

<!--

ログインユーザの設定。AuthenticationのUserに設定されます。

自作Providerが独自の認証を行うためpasswordは実際には使用されません。

-->

<sec:user-service id="userService">

<sec:user name="taro" password="taro" authorities="ADMIN"/>

<sec:user name="hanako" password="hanako" authorities="READ"/>

</sec:user-service>

<!--

エントリポイントの設定

-->

<bean id="authenticationProcessingFilterEntryPoint"

class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">

<property name="loginFormUrl" value="/login.jsp"/>

</bean>

<!--

パスワードに様々な情報を追加するフィルタ

-->

<bean id="authenticationProcessingFilter"

class="jp.co.soracane.security.webapp.AddInfoAuthenticationProcessingFilter">

<sec:custom-filter position="AUTHENTICATION_PROCESSING_FILTER" />

<property name="authenticationManager" ref="authenticationManager" />

<property name="defaultTargetUrl" value="/top.html" />

<property name="authenticationFailureUrl" value="/login.jsp?error=true" />

<property name="requestKeyList">

<list>

<value>challenge</value>

</list>

</property>

<property name="addInfoIfExistThisParameter" value="fingermode" />

</bean>

</beans>

最後の設定beanは、AddInfoAuthenticationProcessingFilterです。

sec:custom-filter タグは、このフィルタの位置を決めるタグです。

デフォルトのAuthenticationProcessingFilter位置と置き換えるということを示しています。

【AddInfoAuthenticationProcessingFilterの各プロパティ値について】

requestKeyList

addInfoIfExistThisParameter

指紋認証用のProviderに渡すリクエストパラメタ名を設定します。設定していないパラメタは渡されません。

パラメタをProviderに渡すかどうかを制御できます。

ここで設定されたパラメタに値が入っていた場合にProviderにパラメタを渡します。

【AddInfoAuthenticationProcessingFilterの動作】

AddInfoAuthenticationProcessingFilterは、指定したパラメタをProviderに渡すことができます。

渡し方は少し変わっていて、パスワードに指定のパラメタをエンコードして押し込めて渡します。

そのため、Providerでパラメタを受け取るには、パスワードからデコードすることになります。

デコード後の値はPropertiesクラスにて受け取ります。

例:

String addedPass = authentication.getCredentials().toString();

Properties prop = AddInfoAuthenticationProcessingFilter.extractInfo(addedPass);

soracaneではSpringSecurityについて、他にも部品を用意しています。

addInfoIfExistThisParameterプロパティが存在する理由は、他の部品を使用するときに組み合わせやすくするためです。

他の部品もご確認いただき、組み合わせて使用することもご検討いただければと思います。

以下で、具体的に指紋認証用のProviderクラスで使い方を確認できるかと思います。

<指紋認証用の認証発行手形発行所のサンプル: FingerAuthenticationProvider.java>

public class FingerAuthenticationProvider implements AuthenticationProvider {

private UserDetailsService userDetailsService;

@Override

public Authentication authenticate(Authentication auth)

throws AuthenticationException {

//パスワードを展開してリクエストの各種値を取得する

String addedPass = auth.getCredentials().toString();

Properties prop = AddInfoAuthenticationProcessingFilter.extractInfo(addedPass);

//パスワード取得

String id = auth.getName();

String passwd = prop.getProperty(AuthenticationProcessingFilter.SPRING_SECURITY_FORM_PASSWORD_KEY);

String challenge = prop.getProperty("challenge");

String mode = prop.getProperty("fingermode");

if(!StringUtils.hasText(challenge)) throw new AuthenticationServiceException("challengeが空です。");

//認証

if(fingerAuthenticate(id, passwd, challenge)){

auth = createAuthentication(id, (UsernamePasswordAuthenticationToken) auth);

}else{

throw new BadCredentialsException("認証エラー発生", id);

}

return auth;

}

protected boolean fingerAuthenticate(String id, String passwd, String challenge){

//実際には指紋認証の処理を記述する

return true;

}

@Override

public boolean supports(Class token) {

return UsernamePasswordAuthenticationToken.class.isAssignableFrom(token);

}

//認証手形を発行する

protected final Authentication createAuthentication(String username, UsernamePasswordAuthenticationToken token)

throws AuthenticationException {

UsernamePasswordAuthenticationToken auth;

UserDetails loadedUser;

try{

loadedUser = getUserDetailsService().loadUserByUsername(username);

}catch (DataAccessException repositoryProblem) {

throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);

}

if (loadedUser == null) {

throw new AuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");

}

//

auth = new UsernamePasswordAuthenticationToken(token.getPrincipal(), token.toString(), token.getAuthorities());

auth.setDetails(loadedUser);

return auth;

}

public UserDetailsService getUserDetailsService() {

return userDetailsService;

}

public void setUserDetailsService(UserDetailsService userDetailsService) {

this.userDetailsService = userDetailsService;

}

}

赤字の部分が、エンコードしたパスワードをデコードするコードです。

他は、通常通りの認証処理です。

デコードした結果得られるPropertiesには、設定したキー名「challenge」で登録されているので、そのキー名で取得しています。

通常、認証手形を発行するときは生成しなおします。

createAuthentication()メソッドではそれに従って生成し、権限情報の設定を行っています。

<ログイン画面サンプル: /login.jsp>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<link rel="stylesheet" href="css/default.css" type="text/css" />

<title>ログイン</title>

</head>

<body>

<form name="f" action="<c:url value='j_spring_security_check'/>" method="post">

<input type="text" name="fingermode" value="1"><br>

<input type="text" name="challenge" value="faweorq23p4rjtq3"><br>

<table class="login">

<tr>

<th>ログインID</th>

<td><input type="text" name="j_username" value="<c:out value="${SPRING_SECURITY_LAST_USERNAME}"/>"></td>

</tr>

<tr>

<th>ログインPW</th>

<td><input type="text" name="j_password"></td>

</tr>

</table>

<input type="submit" name="login" value="ログイン">

</form>

</body>

</html>

challengeには、通常、javascriptなどで指紋認証後の指紋情報を設定しますが、ここでは固定値を入れています。

fingermodeパラメタには何でも良いので値を入れておきます。

AddInfoAuthenticationProcessingFilterでは、このパラメタに値を設定しなければchallengeパラメタがProviderに渡りません。

ですので、値を入れておきます。

もし、パラメタをパスワードに送りたくない場合は空にするか、inputタグ自体を削除してください。

最後に

上記のようにパラメタを取得するのがかなり簡略化されます。

結局、コードを記述したのはFingerAuthenticationProviderクラスだけです。

つまり、本来やらなければならないことのみ、です。

設定は少し煩雑になりますが、カスタマイズするときは仕方がありません。

一般的に、カスタマイズするときは上記のように、エントリポイントなどもSpring設定ファイルに記述する必要があります。

Spring概要のSpring Securityの記事も読んでいただくと、カスタマイズの設定XMLで何をしているのかが少し分かるかと思います。

参考: SpringSecurityの内部動作

Created Date: 2010/01/05