원문 출처 : http://moova.tistory.com/entry/Spring-Security-URL-%EA%B6%8C%ED%95%9C%EC%9D%84-DB2
필자는 그동안 경험했던 정보나 팁들, 또는 지식을 언제부터인가 온라인보다 오프라인으로 공유하는 것을 즐겨했습니다.상당수가 프로젝트 보안이 걸려있는 문제이기도 하겠지요, 그 모든 부분을 온라인에 공유할 수 없는 심정, 이루 말할 수 없겠죠?오픈소스와 관련된 사항도 프로젝트와 연관이 있으면 보안이라는 타이틀이 걸려있습니다. 하지만 공개할 것은 공개하자라는 근본 원칙은 항상 마음속에 간직하고 있습니다."버려야 새로운 것을 얻는다." 바로 이 정신. 참 아릅답습니다.~~
그림1. 커스터마이징 할 filterSecurityInterceptor의 인터페이스와 클래스 사용도입니다.
FilterSecurityInterceptor는 Spring Security의 중요 필터중 하나입니다. FilterSecurityInterceptor는 AuthenticationManager를 의존하며 User와 관련된 (인증 Authentication) 프로세스를 수행하고, 동시에 수행될 자원에 인가여부를 따지는 AccessDecisionManager를 의존하여 투표를 실시합니다.
<beans:bean id="filterSecurityInterceptor"
class="org.springframework.security.intercept.web.FilterSecurityInterceptor"
autowire="byType">
<custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
<beans:property name="accessDecisionManager" ref="accessDecisionManager" />
<beans:property name="objectDefinitionSource" >
<security:intercept-url pattern="/secure/super/**" access="ROLE_WE_DONT_HAVE"/>
<security:intercept-url pattern="/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
</beans:property>
</beans:bean>
FilterSecurityInterceptor의 objectDefinitionSource필드를 커스터마이징 해야합니다. 바로 이 부분이 앞에서 모델링한 테이블과 연동을 해야 할 부분입니다. 위의 objectDefinitionSource를 다음과 같이 바꾸어 줍니다.
<beans:property name="objectDefinitionSource" ref="coolInvocationDefinitionSource"/>
objectDefinitionsource에 reference될 bean을 등록해야합니다.
<beans:bean id="coolInvocationDefinitionSource"
class="com.moova.secure.filterInvocation.CoolObjectDefinitionSourceFactoryBean">
<beans:property name="dataSource" ref="springSecurityDataSource" />
<beans:property name="resourceQuery"
value="
SELECT URL.URL_SPRING, R.NAME
FROM ROLE ROLE JOIN URL_ROLE UR ON ROLE.ID = UR.ROLE_ID JOIN PROGRAM ON PROGRAM.ID = UR.PROGRAM_ID JOIN URL_REPOSITORY REPO ON PROGRAM.ID = REPO.ID " />
</beans:bean>
FilterSecurityInterceptor의 objectDefinitionSource의 API를 살펴보면 다음과 같이 setter injection으로 되어 있는 것을 볼 수 있습니다.
Spring Security 2.0
ObjectDefinitionSource | obtainObjectDefinitionSource() |
void | setObjectDefinitionSource(FilterInvocationDefinitionSource newSource) |
Spring Security 3.0
SecurityMetadataSource | obtainSecurityMetadataSource() |
void | setObjectDefinitionSource(FilterInvocationSecurityMetadataSource newSource) Deprecated. use setSecurityMetadataSource instead |
void | setObserveOncePerRequest(boolean observeOncePerRequest) |
void | setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) |
Spring Security 2.0 과 3.0 API를 살펴보면 크게 변화된 부분이 바로 이 objectDefinitionSource입니다. 3.0에선 objectDefinitionSource가 deplecated되었습니다. 이것이 securitymetadataSource로 변경이 되어 있네요. 2.0의 objectDefinitionSource의 Type은 FilterInvocationDefinitionSource 이고 3.0의 objectDefinitionSource의 Type은 FilterInvocationSecurityMetadataSourcen 인터페이스입니다.
이를 확인하고 각 버전에 따라 작성해야하는 클래스가 다를 수 있다는 것에 주목해 주십시오. 여기선 그대로 objectDefinitionSource를 사용합니다.
새로 제작할 CoolObjectDefinitionSourceFactoryBean는 FactoryBean을 implements합니다. FactoryBean을 implements 하면 어떤 객체라도 Spring lifecycle안에서 DI할 수 있게 해줍니다. 이는 단순 객체가 아닌 Factory 클래스로써 getObject()와 getObjectType()를 구현해 주기만 하면 새롭게 사용할 클래스를 반환시켜줍니다. 추가로 데이터베이스와 연계가 필요하니 "org.springframework.jdbc.core.support.JdbcDaoSupport"를 상속합니다.
사용할 구조
public class CoolObjectDefinitionSourceFactoryBean extends JdbcDaoSupport implements FactoryBean {
private String resourceQuery;
public boolean isSingleton() {
return true;
}
public Class getObjectType() {
return FilterInvocationDefinitionSource.class;
}
public Object getObject() {
return new DefaultFilterInvocationDefinitionSource(
this.getUrlMatcher(), this.buildRequestMap());
}
}
데이터베이스 연계부분은 MappingSqlQuery을 사용합니다. MappingSqlQuery 사용법은 여기를 참조해 주세요.
private class UrlRepository {
private String url;
private String role;
public UrlRepository(String url, String role) {
this.url = url;
this.role = role;
}
public String getUrl() {
return url;
}
public String getRole() {
return role;
}
}
private class UrlRepositoryMapping extends MappingSqlQuery {
protected UrlRepositoryMapping(DataSource dataSource, String resourceQuery) {
super(dataSource, resourceQuery);
compile();
}
protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
String url = rs.getString(1);
String role = rs.getString(2);
UrlRepository resource = new UrlRepository(url, role);
return resource;
}
}
"com.moova.secure.filterInvocation.CoolFilterInvocationDefinitionSourceFactoryBean"의 원본 파일
public class CoolFilterInvocationDefinitionSourceFactoryBean extends JdbcDaoSupport implements FactoryBean { private String resourceQuery; public boolean isSingleton() { return true; } public Class getObjectType() { return FilterInvocationDefinitionSource.class; } public Object getObject() { return new DefaultFilterInvocationDefinitionSource( this.getUrlMatcher(), this.executeResourceMap()); } protected Map<String, String> findUrlRepositories() { UrlRepositoryMapping UrlRepositoryMapping = new UrlRepositoryMapping(getDataSource(), resourceQuery); Map<String, String> resourceMap = new LinkedHashMap<String, String>(); for (UrlRepository urlRepository : (List<Resource>) UrlRepositoryMapping.execute()) { String url = urlRepository.getUrl(); String role = urlRepository.getRole(); if (resourceMap.containsKey(url)) { String value = resourceMap.get(url); resourceMap.put(url, value + "," + role); } else { resourceMap.put(url, role); } } return resourceMap; } protected LinkedHashMap<RequestKey, ConfigAttributeDefinition> executeResourceMap() { LinkedHashMap<RequestKey, ConfigAttributeDefinition> requestMap = null; requestMap = new LinkedHashMap<RequestKey, ConfigAttributeDefinition>(); ConfigAttributeEditor editor = new ConfigAttributeEditor(); Map<String, String> resourceMap = this.findUrlRepositories(); for (Map.Entry<String, String> entry : resourceMap.entrySet()) { RequestKey key = new RequestKey(entry.getKey(), null); editor.setAsText(entry.getValue()); requestMap.put(key, (ConfigAttributeDefinition) editor.getValue()); } return requestMap; } protected UrlMatcher getUrlMatcher() { return new AntUrlPathMatcher(); } public void setResourceQuery(String resourceQuery) { this.resourceQuery = resourceQuery; } private class UrlRepository { private String url; private String role; public UrlRepository(String url, String role) { this.url = url; this.role = role; } public String getUrl() { return url; } public String getRole() { return role; } } private class UrlRepositoryMapping extends MappingSqlQuery { protected UrlRepositoryMapping(DataSource dataSource, String resourceQuery) { super(dataSource, resourceQuery); compile(); } protected Object mapRow(ResultSet rs, int rownum) throws SQLException { String url = rs.getString(1); String role = rs.getString(2); UrlRepository resource = new UrlRepository(url, role); return resource; } } }
executeResourceMap()메소드에서 URL과 ROLE 관련된 매핑부분을 key와 Value로 조합해야 합니다.
DefaultFilterInvocationDefinitionSource의 두 번째 인자는 LinkedHashMap입니다.
당연히 HampMap값을 넘겨주어야 하기 때문에 buildRequestMap에서는 LinkedHashMap를 new해서 반환해 주고 있습니다.
DefaultFilterInvocationDefinitionSource의 첫 번째 인자는 AntUrlPathMatcher를 new해서 주입하고 있습니다.
스키마를 새로 개정하거나 생성해서 사용해도 무방합니다.하지만 URL과 Role에 대한 모델링 연관은 그다지 바뀔게 없다고 봅니다.
태그 : springsecurity











최근 덧글