認証手形発行所(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で何をしているのかが少し分かるかと思います。
Created Date: 2010/01/05