90-3.アクセス認可設定を自動リロードするサンプル
(Security3.x系)
概要
Spring Security 3.1系のサンプルです。
自作したフィルタを使いたいときにどうすればよいか?
そのサンプルと思っていください。
サンプル全体の説明もお読みください。
目標
まずはゴールを示します。
内容は2.x系の記事と同じです。そちらを参照ください。
また、各クラスの説明も2.x系の記事と同じなので省略しています。
実際のサンプル
自作クラス(/src/com/my/security/PuppetDefinitionSource.java)
package com.my.security;
import java.util.Collection;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
/**
* 1つのリクエストの途中で情報が変わらないように、スレッドローカルで認可の情報を管理するクラス。
* このクラスを介して、FilterSecurityInterceptorの認可定義を変更することを目的としたクラス。
*/
public class PuppetDefinitionSource implements FilterInvocationSecurityMetadataSource {
static private ThreadLocal<FilterInvocationSecurityMetadataSource> localDef = new ThreadLocal<FilterInvocationSecurityMetadataSource>();
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return localDef.get().getAllConfigAttributes();
}
@Override
public Collection<ConfigAttribute> getAttributes(Object securedObject)
throws IllegalArgumentException {
return localDef.get().getAttributes(securedObject);
}
@Override
public boolean supports(Class<?> c) {
return localDef.get().supports(c);
}
public void setDefinitionSource(FilterInvocationSecurityMetadataSource def){
localDef.set(def);
}
}
Spring Security2.x系との違いは、importしているクラスのパッケージ名と、Class<?>のようにGenericになったところくらいです。
実装を変える必要はありませんでした。
自作クラス(/src/com/my/security/DefinitionSourceRefreshFilter.java)
package com.my.security;
import java.io.File;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.util.Assert;
/**
* リクエストのたびにFilterSecurityInterceptorに認可定義currentDefSourceを設定するだけのフィルタ。
* 初めてのdoFilter時に強引にFilterSecurityInterceptorの認可定義(FilterInvocationDefinitionSource)を
* localDefinitionSourceに変更している。
* FilterSecurityInterceptorフィルタの前に設定すること。
*/
public class DefinitionSourceRefreshFilter
implements Filter, Ordered, InitializingBean, BeanFactoryAware {
private FilterSecurityInterceptor nextFilter;
private boolean isAlreadySetDef = false;
private FilterInvocationSecurityMetadataSource currentDefSource;
private PuppetDefinitionSource localDefinitionSource = new PuppetDefinitionSource();
private Resource resource;
private long curFileTimestamp = 0L;
private BeanFactory beanFactory;
@Override
public int getOrder() {
return 1650; //FilterSecurityInterceptorが1700なのでそれより前の値にした
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig conf) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
//nextFilterにカスタマイズクラスを設定しておく
if(!this.isAlreadySetDef){
this.nextFilter.setSecurityMetadataSource(this.localDefinitionSource);
this.isAlreadySetDef = true;
}
//定義の読み込みと定義の設定
reload();
this.localDefinitionSource.setDefinitionSource(this.currentDefSource);
chain.doFilter(req, res);
}
/**タイムスタンプが新しい場合、
* AppilicationContextを読み込み直し、currentDefSource を設定しなおす
* @throws Exception
*/
synchronized
protected void reload() throws ServletException{
FileSystemXmlApplicationContext securityContext = null;
try{
//タイムスタンプが新しくない場合は何もしない
long fileTimestamp = this.resource.lastModified();
if(this.curFileTimestamp == fileTimestamp) return;
//context xmlファイルの読み込み
File file = this.resource.getFile();
securityContext = new FileSystemXmlApplicationContext(file.getAbsolutePath());
FilterInvocationSecurityMetadataSource def
= securityContext.getBean(FilterInvocationSecurityMetadataSource.class);
//Sourceを設定する
if(def == null){
throw new RuntimeException("FilterInvocationDefinitionSourceがNULLだった。file=" + file);
}
this.currentDefSource = def;
this.curFileTimestamp = fileTimestamp;
}catch(Exception e){
throw new ServletException("security contextをリロード中にエラー発生", e);
}finally{
//applicationContextのclose
if(securityContext != null){
try{ securityContext.close(); }catch(Exception e){}
}
}
}
//-------------------------------------------------
@Override
public void afterPropertiesSet() throws Exception {
this.nextFilter = this.beanFactory.getBean(FilterSecurityInterceptor.class);
Assert.notNull(this.nextFilter, "filter is required.");
Assert.notNull(this.resource, "resource is required.");
reload();
}
public void setResource(Resource resource) {
this.resource = resource;
}
@Override
public void setBeanFactory(BeanFactory factory) throws BeansException {
this.beanFactory = factory;
}
}
Spring Security2.x系からの変更点はパッケージ名が変更されたくらいです。
リロード用の外部Spring設定ファイル(c:/temp/security3.xml)
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- 認可のカスタマイズ -->
<sec:filter-security-metadata-source id="myDefinitionSource">
<sec:intercept-url pattern="/error.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<sec:intercept-url pattern="/login-provs.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<sec:intercept-url pattern="/403.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<sec:intercept-url pattern="/top2.jsp" access="ROLE_ADMIN,ROLE_READ"/>
<sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
</sec:filter-security-metadata-source>
</beans>
2.x系からの変更点は、タグ名の変更です。
Spring設定ファイル(/WEB-INF/spring/applicationContext-security-interc.xml)
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- 認可のカスタマイズ -->
<!-- SpringSecurityのメイン設定
-->
<sec:http path-type="ant" access-denied-page="/403.jsp" auto-config="false" >
<sec:intercept-url pattern="/error.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<sec:intercept-url pattern="/login-provs.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<sec:intercept-url pattern="/403.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<sec:intercept-url pattern="/top2.jsp" access="ROLE_ADMIN"/>
<sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
<sec:form-login login-page="/login-provs.jsp" default-target-url="/top.jsp"
authentication-failure-url="/login-provs.jsp?error=true"/>
<sec:logout logout-url="/logout" logout-success-url="/login-provs.jsp" invalidate-session="true"/>
<sec:anonymous granted-authority="ROLE_ANONYMOUS"/>
<!-- フィルタの挿入位置を指定する。ここでは1つ前を指定している。 -->
<sec:custom-filter ref="accessDefRefreshFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
</sec:http>
<!-- 認証手形発行所 -->
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="taro" password="taro" authorities="ROLE_ADMIN"/>
<sec:user name="hanako" password="hanako" authorities="ROLE_READ"/>
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
<!--
自作のフィルタ。
-->
<bean id="accessDefRefreshFilter"
class="com.my.security.DefinitionSourceRefreshFilter">
<property name="resource" value="file:c:/temp/security3.xml" />
</bean>
</beans>
Spring Security2.x系からの変更点は、タグの使い方です。
2.x系では、authentication-manager タグを記述しなくてもauthentication-providerタグを使用できましたが、できなくなったようです。
3.x系では上記のように、httpタグ内に記述してください。
Created Date: 2013/07/07