Anyframe IAM Core 의 기반이 되는 Spring Security 는 기본적으로 XML 설정만을 지원한다. 하지만 XML 설정 방식만을 지원하는 경우 방대하고 복잡한 속성 파일들로 인해 시스템 유지보수시 비용 발생 및 지연을 초래할 가능성이 높아지며 일반적인 엔터프라이즈 어플리케이션에서 선호되는 형태의 설정 방식이 아니다.
이러한 문제점을 해결하기 위해 Ayframe IAM Core 는 Spring Security 를 확장하여 Database 기반의 설정 정보를 기반으로 동작하도록 개발되었다. 본 문서에서는 IAM Core 의 설정방법에 대해 상세히 살펴보도록 한다.
일반적으로 Spring Security 기본 기능 적용을 위한 필수 라이브러리는 다음과 같다.
spring-security-core-2.0.4.jar
spring-security-taglibs-2.0.4.jar
IAM Core 에서는 spring-security-core, spring-security-acl, spring-security-taglibs 의 기능을 확장하여 제공하고 있으며, 또한 annotation 기반의 Method 보안 적용을 위해서는 spring-security-core-tiger 도 반드시 쓰이게 되므로 위 4개의 Spring Security 라이브러리는 포함하게 되는 것이 일반적이다. 이 외에도 Spring, AspectJ 등 AOP 관련 라이브러리, Apache Commons 일부 라이브러리 등을 함께 사용하고 있다.
web.xml 등록
org.springframework.web.filter.DelegatingFilterProxy 등록 : Application Context에 Spring 빈으로 등록된 필터 구현체를 대표하는 Spring Framework 클래스이다.
모든 웹요청이 Spring Security의 DelegatingFilterProxy로 전달되도록 한다.
DelegatingFilterProxy는 웹요청이 서로 다른 URL 패턴에 근거하여 서로 다른 필터로 전달될 수 있도록 해주는 일반 목적용으로 사용할 수 있는 클래스이다.
이런 위임된 필터들은 어플리케이션 컨텍스트 내에서 관리되며, 따라서 Dependency Injection 의 이점을 누릴 수 있다.
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
..
<!-- if you wish to use Concurrent Session Control
<listener>
<listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
-->
Security Namespace
since Spring Security 2.0.x
이전의 복잡한 Security bean 설정에 비해 매우 간소한 설정
하부 구현의 이해없이 가장 일반적으로 사용되는 케이스에 대해 미리 만들어 제공
모든 Security 기능을 포함하지는 않으며 변경/확장하기 어려운 부분이 존재함
Namespace 기반의 Spring Security 설정 파일의 주요 Tag
<http>
<intercept-url … />
<global-method-security />
<jdbc-user-service />
Namespace 기반의 Spring Security 설정 예
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="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-2.5.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
<http path-type="regex" lowercase-comparisons="false">
<intercept-url pattern="\A/sample/images/.*\Z" filters="none" />
<intercept-url pattern="\A/sample/css/.*\Z" filters="none" />
<intercept-url pattern="\A/sample/javascript/.*\Z" filters="none" />
<intercept-url pattern="\A/login\.do.*\Z" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="\A/security/users/list\.do.*\Z" access="ROLE_USER" />
<intercept-url pattern="\A/.*\.do.*\Z" access="ROLE_RESTRICTED" />
<intercept-url pattern="\A/.*\Z" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<form-login login-page="/login.do" login-processing-url="/j_spring_security_check"
authentication-failure-url="/login.do?login_error=1"
default-target-url="/plugins.do" />
<anonymous />
<logout logout-success-url="/plugins.do" />
<!--
if you wish to use Concurrent Session Control - see also listener
configuration of web.xml - HttpSessionEventPublisher
-->
<concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" />
</http>
<authentication-provider>
<password-encoder hash="md5"/>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="SELECT USER_ID, PASSWORD, ENABLED FROM USERS WHERE USER_ID = ?"
authorities-by-username-query="SELECT SUBJECT_ID AS USER_ID, ROLE_ID FROM AUTHORITIES WHERE SUBJECT_ID = ?"
/>
</authentication-provider>
<global-method-security secured-annotations="enabled" jsr250-annotations="enabled">
<protect-pointcut expression="execution(* security..UsersService.remove(..))" access="ROLE_ADMIN" />
</global-method-security>
</b:beans>
위는 Spring Security 의 Namespace 태그 기반의 인증/권한 처리 설정 예이다.
http 태그를 통해 Spring Security 의 주요 보안 기능이 자동으로 설정된다. 위에서는 path-type 속성을 통해 정규식 url 패턴 매처를 사용하고 있으며, lowercase-comparisons 는 false 로 url 패턴의 대소문자는 원본 그대로 유지한채 비교토록 하고 있는 예이다.
http 하위의 intercept-url 태그로 먼저 상단에 images, css, javascript 에 해당하는 정적 웹 리소스에 대해 filters="none" 을 지정하여 Spring Security 의 Filter Chain 을 태우지 않고 무조건 제공토록 설정하였다.
intercept-url 태그에 access 속성을 통해 권한을 지정할 수 있으며 위에서 로그인 페이지 이동을 위한 login.do 패턴에 대해서는 아무에게나 접근 가능토록 열려 있다. 또한 /security/users/list.do 에 대해서는 ROLE_USER 권한을 지정하고 있으며 기타 모든 .do 패턴에 대해서는 ROLE_RESTRICTED, 그 외의 모든 패턴에 대해서는 아무나 접근 가능토록 설정한 예이다.
form-login 을 지정하여 HTML Form 기반 인증으로 설정하였으며 login-page 나 인증에 실패했을때 되돌아갈 페이지, 인증에 성공했을 때 기본으로 이동할 페이지 등을 지정하고 있다.
anonymous 태그를 설정하면 AnonymousProcessingFilter 가 자동으로 추가되며 인증되지 않은 임의의 사용자 접근 시 ROLE_ANONYMOUS 라는 기본 Role 로 처리될 것이다.
logout 태그를 통해 logout 성공 시 되돌아갈 페이지를 지정하고 있다.
concurrent-session-control 태그를 통해 한 사용자가 동시 접근할 수 있는 갯수를 지정할 수 있다. 이 태그와 쌍으로 반드시 web.xml 에 HttpSessionEventPublisher 에 대한 Listener 지정이 필요하다.
authenication-provider 태그를 기본 설정으로 썼을때 DaoAuthenticationProvider 로 설정되며, JDBC 기반의 UserDetailsService 가 id, password 에 따른 사용자 정보 조회를 위해 사용된다. 위에서는 사용자 패스워드의 암호화를 위해 password-encoder 를 지정하여 복호화가 불가능하도록 One-way hash algorithim 으로 md5 를 지정한 예이다. 사용자 인증 시 암호화된 password 를 비교하여 처리하게 된다. (위에서 나타나지는 않았으나 사용자 password 데이터 변경을 위한 서비스를 개발할 때는 동일하게 password encoder bean 을 통해 암호화해야할 필요성이 있다. 관련 사용예는 Anyframe 4.0.0 의 Security 샘플을 참조하라.)
global-method-security 태그를 통해 Method 실행에 대한 보안 제어를 설정하고 있으며 위 예에서는 jsr250 의 @RolesAllowed annotation 및 Spring Security 의 자체 @Secured annotation 을 사용 가능토록 설정하였으며, AspectJ 의 Pointcut 표현식으로 사용자 서비스의 remove 에 대해서는 ROLE_ADMIN 권한을 지정하고 있음을 알 수 있다. Spring AOP 가 적용되기 위해서는 interface - implementation 형태로 서비스를 작성하는 것을 권고하는 바이며, interface 가 없는 경우에 AOP 적용을 위해서는 CGLib 적용이 필요하므로 유의한다.
사용자 인증과 관련된 테이블은 사용자테이블과 사용자권한테이블이며 사용자권한관련 테이블은 역할, 자원, 역할계층 등의 테이블이 있다.
Table 6.1. IAM Table
| 테이블 명칭 | 설명 | |
|---|---|---|
| 사용자정보 | USERS | 어플리케이션 사용자 정보 |
| 그룹정보 | GROUPS | 사용자 그룹 정보 |
| GROUPS_HIERARCHY | 사용자 그룹간의 계층 정보 | |
| GROUPS_USERS | 사용자 그룹과 사용자 간의 매핑 정보 | |
| Role(권한)정보 | ROLES | ROLE(권한) 정보 |
| ROLES_HIERARCHY | ROLE(권한)간의 계층 정보 | |
| AUTHORITIES | ROLE(권한)과 그룹/사용자 매핑 정보 | |
| 보호자원정보 | SECURED_RESOURCES | 보호대상이 되는 자원 정보 |
| SECURED_RESOURCES_ROLES | 보호대상 자원과 ROLE(권한)과의 매핑 정보 | |
| CANDIDATE_SECURED_RESOURCES | 보호자원으로 등록 될 수 있는 자원 정보 | |
| View 정보 | VIEW_RESOURCES | 어플리케이션 화면 정보 |
| VIEW_RESOURCES_MAPPING | 어플리케이션 화면 정보과 ROLE(권한) 정보간의 매핑 | |
| 시간에 의한 제한 관련 정보 | RESTRICTED_TIMES | 시스템 사용 제한 시간에 대한 정보 |
| RESTRICTED_TIMES_RESOURCES | 시스템 사용 제한 시간과 자원 간의 매핑 정보 | |
| RESTRICTED_TIMES_ROLES | 시스템 사용 제한 시간과 ROLE(권한)간의 매핑정보 | |
| TIME_RESOURCES_EXCLUSION | 시스템 사용 제한 예외 정보 | |
| 기타 | IDS | ID 자동생성 관련 정보 |
해당 테이블의 ERD는 다음과 같으며 각 테이블의 DDL 문장은 Appendix B. IAM Database Schema를 참조한다.
![]() |
Anyframe IAM Core 에서 사용하는 확장 기능 위주의 설정에 대해서 알아본다. 일반적인 경우 IAM 과 함께 제공되는 샘플 어플리케이션의 설정을 그대로 사용하면 되므로 각 설정의 의미를 이해하는데 주안점을 두었다.
타겟 어플리케이션의 messageSource 빈 설정(주로 context-common.xml 에 존재)에 아래와 같이 IAM 메시지 파일을 추가한다.
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
..
<value>anyframe/iam/core/messages/security</value>
</list>
</property>
</bean>
Spring Security 의<jdbc-user-service /> 대신 IAM 의 ExtJdbcUserDetailsManager 를 적용한다.
<b:bean id="jdbcUserService"
class="anyframe.iam.core.userdetails.jdbc.ExtJdbcUserDetailsManager" >
<!-- usersByUsernameQuery 에서 사용자에 대한 group 을 함께 조회할 때 해당 property 명을 설정 -->
<b:property name="userGroupPropertyName" value="GROUP_ID"/>
<!-- USER_ID, PASSWORD, ENABLED 는 항상 1,2,3 번째 순서로 나타나야 함! -->
<b:property name="usersByUsernameQuery">
<b:value>
SELECT A.USER_ID AS USER_ID, PASSWORD, CASE WHEN ENABLED = 'Y' THEN 1 ELSE 0 END ENABLED,
B.GROUP_ID AS GROUP_ID, USER_NAME, USER_NAME, CREATE_DATE, MODIFY_DATE
FROM USERS A LEFT OUTER JOIN GROUPS_USERS B
ON ( A.USER_ID = B.USER_ID )
WHERE A.USER_ID = ?
</b:value>
</b:property>
<b:property name="authoritiesByUsernameQuery">
<b:value>
SELECT USER_ID,ROLE_ID,GROUP_ID,SUBJECT_ID,TYPE
FROM AUTHORITIES C, (
SELECT A.USER_ID,B.GROUP_ID
FROM USERS A LEFT OUTER JOIN GROUPS_USERS B ON ( A.USER_ID = B.USER_ID )
WHERE A.USER_ID = ? ) D
WHERE ( C.SUBJECT_ID = D.USER_ID
OR C.SUBJECT_ID = D.GROUP_ID )
</b:value>
</b:property>
<b:property name="dataSource" ref="dataSource"/>
<!-- 사용자 정의 CustomUserVO 를 쓰고자 하는 경우 ExtUsersByUsernameMapping 를 확장하여 makeCustomUser() 에 관련 로직을 작성한 MappingSqlQuery 등록 -->
<!-- <b:property name="mapClass" value="anyframe.iam.core.userdetails.jdbc.CustomUsersByUsernameMapping"/> -->
</b:bean>
..
<authentication-manager alias="authenticationManager" />
<authentication-provider user-service-ref="jdbcUserService" />
커스텀 사용자 정보를 Spring Security 의 UserDetails 확장 객체로 저장할 수 있게 하였고 그룹에 대한 권한을 사용자 로그인 시 함께 사용자의 권한으로 로딩하게 한 예이다. mapClass 를 지정하지 않으면 IAM 의 ExtUsersByUsernameMapping 로 내부적으로 커스텀 사용자 정보는 Map 형태로 처리되나 사용자가 이를 확장한 맵핑 객체를 작성하여 제공하면 사용자가 지정한 JavaBeans 객체로도 처리가 가능하다.(관련 예는 ExtUsersByUsernameMapping 의 javadoc 참조) 또 위에서 사용자 그룹에 대한 property 명을 설정 가능토록 하였으므로 이를 잘 맞추어 주도록 한다. 설정치 않은 경우 default 값은 userGroup 이다.
아래는 CustomUserDetailsHelper 를 사용하여 커스텀 사용자 객체를 얻어낼 수 있는 사용법의 예이다. 아래 사용자 정보 객체는 웹 영역에서 뿐만 아니라 Application 전체 레이어에 걸쳐 동일한 방법으로 접근이 가능하다.
ExtUser extUser = CustomUserDetailsHelper.getAuthenticatedUser();
Map customUserMap = (Map) extUser.getCustomUser();
// CustomUser 라는 JavaBeans 로 맵핑을 처리하는 mapClass 를 작성하여 적용한 경우
// CustomUser customUser = (CustomUser) extUser.getCustomUser();
IAM 에서는 Spring Security 의 보호자원 및 RoleHierarchy, 자체 확장한 Time 기반의 보호자원에 대한 데이터를 DB 기반으로 처리하고 있다. 이는 Container 기동 시에 한번(또는 reload 요청시) 조회하여 처리되는 구조이다.
<b:bean id="securedObjectService" class="anyframe.iam.core.securedobject.impl.SecuredObjectServiceImpl">
<b:property name="securedObjectDAO" ref="securedObjectDAO" />
</b:bean>
<b:bean id="securedObjectDAO" class="anyframe.iam.core.securedobject.impl.SecuredObjectDAO">
<b:property name="dataSource" ref="dataSource" />
</b:bean>
IAM 의 DB Schema 를 사용하지 않고 IAM Core 만을 단독으로 적용하거나 일부 DB 테이블이 다른 경우 등 쿼리 변경이 필요한 경우를 고려하여 securedObjectDAO 에는 sqlRolesAndUrl, sqlRolesAndMethod, sqlRolesAndPointcut, sqlRegexMatchedRequestMapping, sqlHierarchicalRoles, sqlRestrictedTimesRoles, sqlRestrictedTimesResources, sqlViewResourceMapping 에 대해 사용자 설정이 가능하도록 되어 있다. 상세 내용은 SecuredObject 사용자화 설정 이나 SecuredObjectDAO 소스를 참고하도록 한다.
Spring Security 의 RoleHierarchy 처리를 그대로 사용하고 있으며, Role 의 Hierachy 데이터를 DB 기반으로 로딩하는 부분이 추가되어 있다.
<b:bean id="roleHierarchy" class="org.springframework.security.userdetails.hierarchicalroles.RoleHierarchyImpl">
<b:property name="hierarchy" ref="hierarchyStrings" />
</b:bean>
<b:bean id="hierarchyStrings" class="anyframe.iam.core.userdetails.hierarchicalroles.HierarchyStringsFactoryBean"
init-method="init">
<b:property name="securedObjectService" ref="securedObjectService" />
</b:bean>
<b:bean id="userDetailsServiceWrapper" class="org.springframework.security.userdetails.hierarchicalroles.UserDetailsServiceWrapper">
<b:property name="roleHierarchy" ref="roleHierarchy" />
<b:property name="userDetailsService" ref="jdbcUserService" />
</b:bean>
..
<authentication-provider user-service-ref="userDetailsServiceWrapper" />
<b:bean id="roleHierarchy" class="org.springframework.security.userdetails.hierarchicalroles.RoleHierarchyImpl">
<b:property name="hierarchy" ref="hierarchyStrings" />
</b:bean>
<b:bean id="hierarchyStrings" class="anyframe.iam.core.userdetails.hierarchicalroles.HierarchyStringsFactoryBean"
init-method="init">
<b:property name="securedObjectService" ref="securedObjectService" />
</b:bean>
<b:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
<b:property name="allowIfAllAbstainDecisions" value="false" />
<b:property name="decisionVoters">
<b:list>
<!-- RoleHierarchyVoter -->
<b:bean class="org.springframework.security.vote.RoleHierarchyVoter">
<b:constructor-arg ref="roleHierarchy" />
</b:bean>
<b:bean class="org.springframework.security.vote.AuthenticatedVoter" />
</b:list>
</b:property>
</b:bean>
Spring Security 에서는 RoleHierarchy 처리를 위한 2가지 방법을 제공하고 있다. UserDetailsServiceWrapper 를 사용하여 원본 jdbcUserService 를 랩핑하는 방법과 랩핑하지 않고 AccessDecisionVoter 처리 시 RoleVoter 가 아닌 RoleHierarchyVoter (roleHierarchy 를 내부적으로 활용) 를 설정하는 방법이다. UserDetailsServiceWrapper 를 사용하는 방법을 한 때 반대 하기도 하였으나 Tag 사용시 accessDecisionManager 지정 등의 문제 및 사용자 요구로 현재는 2가지 방식을 함께 사용할 수 있도록 되어 있다. Anyframe IAM 에서는 사용자 로그인 시 한번에 해당 사용자 Role 에 하위 Role 을 포함토록 처리하는 UserDetailsServiceWrapper 형태 사용법을 일반적으로 권고하는 바이나 WAS 의 Session Clustering을 적용하는 환경에서는 RoleHierarchyVoter 처리를 사용할 필요성이 있다. (현재 Spring Security 의 RoleHierarchyImpl 이 Serializable 하지 않으며, Authentication 객체 내에 함께 저장되므로 문제가 발생함. Serializable 에 대한 요구사항은 Spring Security 이슈관리시스템 JIRA 에 올라가 있는 상태)
Namespace 에서 기본 포함되는 FilterSecurityInterceptor 및 DefaultFilterInvocationDefinitionSource 외에 DB 기반의 보호자원(Url)-권한맵핑을 적용하기 위해 아래 설정을 추가한다.
<b:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
<b:property name="allowIfAllAbstainDecisions" value="false" />
<b:property name="decisionVoters">
<b:list>
<b:bean class="org.springframework.security.vote.RoleVoter">
<b:property name="rolePrefix" value="" />
</b:bean>
<b:bean class="org.springframework.security.vote.AuthenticatedVoter" />
</b:list>
</b:property>
</b:bean>
<b:bean id="filterSecurityInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
<custom-filter after="FILTER_SECURITY_INTERCEPTOR" />
<!-- Namespace 에서 기본으로 FilterSecurityInterceptor 는 들어가는데 observeOncePerRequest 를 설정하지 않으면 기본으로 같은 Filter 타입인 경우에는 수행을 하지 않음에 유의함.-->
<b:property name="observeOncePerRequest" value="false" />
<b:property name="authenticationManager" ref="authenticationManager" />
<b:property name="accessDecisionManager" ref="accessDecisionManager" />
<b:property name="objectDefinitionSource" ref="databaseObjectDefinitionSource" />
</b:bean>
<b:bean id="databaseObjectDefinitionSource"
class="org.springframework.security.intercept.web.AnyframeReloadableDefaultFilterInvocationDefinitionSource">
<b:constructor-arg ref="regexUrlPathMatcher" />
<b:constructor-arg ref="requestMap" />
<b:property name="securedObjectService" ref="securedObjectService" />
</b:bean>
<b:bean id="regexUrlPathMatcher" class="org.springframework.security.util.RegexUrlPathMatcher" />
<b:bean id="requestMap" class="anyframe.iam.core.intercept.ResourcesMapFactoryBean" init-method="init">
<b:property name="securedObjectService" ref="securedObjectService" />
<b:property name="resourceType" value="url" />
</b:bean>Spring Security 의 Namespace 에서 URL 보호 자원에 대한 Authorization 처리 기능을 제공하기 위해 자동으로 추가되는 FilterSecurityInterceptor 에 더하여 위와 같이 동일한 FilterSecurityInterceptor 를 추가로 custom-filter 로 추가하여 DB 기반의 보호자원-권한맵핑 데이터 기반 처리를 추가하기 위한 설정 예이다. 기본 FILTER_SECURITY_INTERCEPTOR 바로 다음 위치에 배치하는 것을 권고하는 바이고 주의할 점은 Spring Security 에서는 동일한 Filter 타입에 대해서는 기본적으로 하나의 request 처리 시 한번만 동작하도록 구동하기 때문에 이를 해제해 주기 위해 observeOncePerRequest 를 false 로 지정해 주어야 한다. databaseObjectDefinitionSource 으로 등록하는 bean 은 Spring Security 의 DefaultFilterInvocationDefinitionSource 를 확장하여 DB 기반 초기 데이터 로딩 및 runtime reload 기능을 추가하였고, requestMap 로 정의한 FactoryBean 확장 클래스를 통해 securedObjectService 를 사용한 DB 데이터를 databaseObjectDefinitionSource 의 초기 데이터로 제공하게 된다. Url 패턴 매칭을 검사하는 PatternMatcher 는 위에서 정규식 기반의 RegexUrlPathMatcher 로 지정한 예이다. 정규식을 사용하면 매우 강력한 패턴 매칭 검사가 가능하고 기본으로 URL parameter 를 포함한 전체 url 에 대해 검사할 수 있다. 이외에 AntUrlPathMatcher 를 설정할 수 있으며 표현식이 정규식 작성에 비해 간결하다. 기본으로는 URL parameter 는 제거하고 패턴 매칭 비교를 수행하게 된다.
DB 기반의 Method/Pointcut 보호자원 데이터를 활용한 Method Authorization 을 적용하기 위한 설정의 예이다.
<!-- customizing method security start -->
<b:bean id="methodMap" class="anyframe.iam.core.intercept.ResourcesMapFactoryBean" init-method="init">
<b:property name="securedObjectService" ref="securedObjectService" />
<b:property name="resourceType" value="method" />
</b:bean>
<b:bean id="methodDefinitionSources" class="org.springframework.security.intercept.method.MapBasedMethodDefinitionSource">
<b:constructor-arg ref="methodMap" />
</b:bean>
<b:bean id="_delegatingMethodDefinitionSource" class="org.springframework.security.intercept.method.DelegatingMethodDefinitionSource">
<b:property name="methodDefinitionSources">
<b:list>
<b:ref bean="methodDefinitionSources" />
<b:bean class="org.springframework.security.annotation.SecuredMethodDefinitionSource" />
<b:bean class="org.springframework.security.annotation.Jsr250MethodDefinitionSource" />
</b:list>
</b:property>
</b:bean>
<b:bean id="_methodDefinitionSourceAdvisor" class="org.springframework.security.intercept.method.aopalliance.MethodDefinitionSourceAdvisor">
<b:constructor-arg value="_methodSecurityInterceptor" />
<b:constructor-arg ref="_delegatingMethodDefinitionSource" />
</b:bean>
<b:bean id="_methodSecurityInterceptor" class="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor">
<b:property name="validateConfigAttributes" value="false" />
<b:property name="authenticationManager" ref="authenticationManager" />
<b:property name="accessDecisionManager" ref="accessDecisionManager" />
<!--
<b:property name="afterInvocationManager" ref="afterInvocationManager" />
-->
<b:property name="objectDefinitionSource" ref="_delegatingMethodDefinitionSource" />
</b:bean>
<b:bean id="pointcutMap" class="anyframe.iam.core.intercept.ResourcesMapFactoryBean" init-method="init">
<b:property name="securedObjectService" ref="securedObjectService" />
<b:property name="resourceType" value="pointcut" />
</b:bean>
<b:bean id="_protectPointcutPostProcessor" class="org.springframework.security.intercept.method.ProtectPointcutPostProcessor">
<b:constructor-arg ref="methodDefinitionSources" />
<b:property name="pointcutMap" ref="pointcutMap" />
</b:bean>
<!-- customizing method security end -->Namespace 의 <global-method-security> 를 대체하여 전통적인 Bean 설정 형식으로 정의하여야 하며 특히 jsr-250 / secured annotation 기반의 메서드 보안을 동일하게 지원하기 위한 다단계의 methodDefinitionSource (ObjectDefinitionSource) 참조 설정에 유의한다. methodDefinitionSources 으로 등록하는 bean 은 Spring Security 의 MapBasedMethodDefinitionSource 를 그대로 사용하며 생성자 인자로 methodMap 로 정의한 FactoryBean 확장 클래스를 통해 securedObjectService 를 사용한 보호자원(method)-권한 맵핑 DB 데이터를 초기 데이터로 제공하게 된다. 마찬가지로 pointcutMap 으로 정의한 FactoryBean 확장 클래스를 통해 securedObjectService 를 사용한 보호자원(pointcut)-권한맵핑 DB 데이터를 기반으로 AspectJ 의 Pointcut 표현식을 읽어 각각의 Bean 생성 시 동적으로 포인트컷을 추가하는 BeanPostProcessor 인 ProtectPointcutPostProcessor 에 dependency 로 제공하여 표현식이 매치되는 bean 의 대상 메서드에 메서드 보안처리를 위한 Advisor 를 적용하게 된다.
실행 시간에 따른 제한 자원(Url) 및 제한 Role 에 대한 접근 제어 기능을 적용하기 위한 예이다.
<b:bean id="restrictedTimesAccessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
<b:property name="allowIfAllAbstainDecisions" value="false" />
<b:property name="decisionVoters">
<b:list>
<b:bean
class="org.springframework.security.vote.AnyframeRoleHierarchyRestrictedVoter">
<b:property name="rolePrefix" value="" />
<b:property name="roleHierarchy" ref="roleHierarchy" />
</b:bean>
<b:bean class="org.springframework.security.vote.AuthenticatedVoter" />
</b:list>
</b:property>
</b:bean>
<b:bean id="restrictedTimesFilterSecurityInterceptor"
class="anyframe.iam.core.intercept.web.RestrictedTimesFilterSecurityInterceptor">
<custom-filter before="FILTER_SECURITY_INTERCEPTOR"/>
<b:property name="authenticationManager" ref="authenticationManager" />
<b:property name="accessDecisionManager" ref="restrictedTimesAccessDecisionManager" />
<b:property name="objectDefinitionSource" ref="restrictedTimesObjectDefinitionSource" />
</b:bean>
<b:bean id="restrictedTimesObjectDefinitionSource"
class="anyframe.iam.core.intercept.web.ReloadableRestrictedTimesFilterInvocationDefinitionSource">
<b:property name="urlMatcher" ref="regexUrlPathMatcher" />
<b:property name="securedObjectService" ref="securedObjectService" />
</b:bean>Spring Security 의 FilterSecurityInterceptor 를 확장하여 실행 시간에 따른 접근 제어 기능을 제공하는 RestrictedTimesFilterSecurityInterceptor 와 DB 기반의 실행시간-Resource/Role 제한 데이터로 부터 현재 시각에 해당하는 제한 데이터에 대해 매칭을 판단하여 적절한 제한/허용 Role 을 얻기 위한 ReloadableRestrictedTimesFilterInvocationDefinitionSource, 또 시간제한 Resource/Role 에 따른 최종적인 ACCESS/DENY 접근 제어를 판단하기 위한 AccessDecisionVoter 확장 구현인 AnyframeRoleHierarchyRestrictedVoter 를 설정하여 실행시간에 따른 접근 제어를 유연하게 적용할 수 있다. 위의 시간 제한 설정을 위한 세가지 Bean 은 반드시 쌍으로 사용되며, RestrictedTimesFilterSecurityInterceptor 는 custom-filter 로 Spring Security의 Namespace 에서 자동으로 추가되는 FILTER_SECURITY_INTERCEPTOR 바로 앞에 배치하여 사용하기를 권고한다. 실행 시간 제한 유형은 매일 적용되는 제한 유형인 crash, daily 와 사용자가 시작 ~ 종료 일시를 지정하는 weekend, holiday, improve 유형을 지정할 수 있도록 IAM Admin 에서 데이터 관리 영역이 제공되고 있으니 참고토록 한다.
IAM Admin 에서 보호자원을 등록할 때 실제 타겟 어플리케이션에서 구동되는 UrlMapping 이나 서비스 메서드 정보를 기반으로 Assist(자동완성) 를 지원하기 위한 설정의 예이다.
resourceGatherAssistService 와 resourceCreationAssistService 를 통해 해당 어플리케이션의 자원 정보를 수집하고 DB 의 임시 테이블에 저장할 수 있다. 이 서비스들은 IAM Admin 에 remote 로 노출되어 Admin 에서 호출 시 해당 기능을 그 시점에 수행하게 될 것이다.
<bean id="resourceGatherAssistService"
class="anyframe.iam.core.assist.impl.ResourceGatherAssistServiceImpl" />resourceGatherAssistService 는 url, method, pointcut 유형의 보호자원 등록의 Assist 를 위해 Service 로 끝나는(candidateBeanPostfix 를 지정하는 경우 다른 postfix 를 지정할 수 있음) 업무서비스 Bean의 package/class/method 등의 정보를 수집하고 이를 기반으로 간략한 pointcut 표현식 Assist 정보도 계산한다. Url 정보를 수집하기 위해서는 SpringMVC 의 AbstractUrlHandlerMapping 유형의 HandlerMapping 빈들에 등록되어 있는 url 을 활용하므로 위 resourceGatherAssistService 빈 설정은 DispatcherServlet 을 통해 로딩하는 WebApplicationContext 에 해당하는 xx-servlet.xml 설정 파일(아래 IAM Admin Remote 연동 설정 파일과 동일)에 등록 하여야 함에 유의한다.
보호자원 정보를 런타임에 갱신할 수 있는 기능을 대표로 수행하는 reloadService 설정 예이다.
<b:bean id="resourceReloadService"
class="anyframe.iam.core.reload.impl.ResourceReloadServiceImpl">
<b:property name="databaseObjectDefinitionSource" ref="databaseObjectDefinitionSource" />
<b:property name="restrictedTimesObjectDefinitionSource" ref="restrictedTimesObjectDefinitionSource" />
<b:property name="restrictedTimesAccessDecisionManager" ref="restrictedTimesAccessDecisionManager" />
</b:bean>reloadService 를 통해 DB 기반의 보호자원(Url)-권한 맵핑 정보(requestMap) 및 시간제한에 따른 보호자원 정보를 런타임에 갱신할 수 있으며, 이 서비스는 IAM Admin 에 remote 로 노출되어 Admin 에서 호출 시 해당 기능을 그 시점에 수행하게 될 것이다.
보호자원 정보 Assist 및 런타임 갱신을 IAM Admin 에서 Remote 로 처리할 수 있도록 설정한 예이다.
<!-- Remote HttpInvoker Call -->
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" />
<bean id="resourceGatherAssistService"
class="anyframe.iam.core.assist.impl.ResourceGatherAssistServiceImpl" />
<bean id="gatherService"
class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="resourceGatherAssistService" />
<property name="serviceInterface"
value="anyframe.iam.core.assist.IResourceGatherAssistService" />
</bean>
<bean id="reloadService"
class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="resourceReloadService" />
<property name="serviceInterface"
value="anyframe.iam.core.reload.IResourceReloadService" />
</bean>
<bean id="urlMappingResourceAssist"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/assist/resourceGatherAssist.do">gatherService</prop>
<prop key="/reload/resourceReload.do">reloadService</prop>
</props>
</property>
</bean>IAM Admin 도 동일하게 Spring 기반 어플리케이션 이므로 Spring Bean 에 대해 remote 서비스로 노출할 수 있는 방법 중 하나인 HttpInvoker 기술을 사용하여 위와 같이 SpringMVC 의 DispatcherServlet 을 통해 Http 기반으로 매우 간단하게 remote 연동이 가능하다. 위 설정 내용은 spring-security-servlet.xml 으로 분리하여 DispatcherServlet 에 의한 WebApplicationContext 에 등록토록 한다.
<intercept-url pattern="\A/assist/resourceGatherAssist\.do.*\Z" access="ROLE_ADMIN" />
<intercept-url pattern="\A/reload/resourceReload\.do.*\Z" access="ROLE_ADMIN" />관리자 만이 위 runtime reload 및 assit 정보 수집/등록을 할 수 있도록 하기 위해서는 http 태그의 intercept-url 설정으로 해당 remote 공개 url 에 대해 ROLE_ADMIN 을 지정하는 것이 필요하다. IAM Admin 에서는 이를 고려하여 로그인한 사용자(관리자)의 인증정보를 자동으로 HttpInvoker remote 호출 시 넘길 수 있도록 HttpInvoker client 에 대한 HttpInvokerProxyFactoryBean 설정 시 httpInvokerRequestExecutor 로 AuthenticationSimpleHttpInvokerRequestExecutor 를 설정한다. 관련 예는 Admin 환경 설정 방법의 remote-invoker-servlet.xml 설정 예를 참고한다.
HttpInvoker 방식의 remote 호출 시 암호화한 형태로 데이터를 전송하는 것이 아니라 단순히 Base64 인코딩한 형태로만 전송하므로 보안에 취약한 문제가 있다. 이를 개선하기 위해서는 https 채널 보안을 적용하는 것이 필요하며 설정은 다음과 같다.
<intercept-url pattern="\A/reload/resourceReload\.do.*\Z" access="ROLE_ADMIN" requires-channel="https" />
..https 프로토콜 기반의 채널 보안을 적용하기 위한 SSL, X.509 인증서(또는 사설인증서) 처리를 위한 적용 절차는 본 문서 범위를 넘어가므로 생략하였다.
사용자가 정의 View Resource(ex. 화면, 프로그램)에 대해 매핑된 Permission 을 체크할 수 있는 기능을 제공한다.
<b:bean id="viewResourceAccessService"
class="anyframe.iam.core.acl.impl.ViewResourceAccessServiceImpl">
<b:property name="securedObjectService" ref="securedObjectService" />
<b:property name="registeredPermissions">
<b:list>
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.READ" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.WRITE" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.CREATE" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.DELETE" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.ADMINISTRATION" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.LIST" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.PRINT" />
<!--
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.REPORT" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.POPUP" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.DOWNLOAD" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.UPLOAD" />
<b:ref local="anyframe.iam.core.acl.ExtBasePermission.HELP" />
-->
</b:list>
</b:property>
</b:bean>
<!-- 현재 Application 에서 사용하는 Permission 정의 -->
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.READ"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.READ" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.WRITE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.WRITE" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.CREATE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.CREATE" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.DELETE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.DELETE" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.ADMINISTRATION"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.ADMINISTRATION" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.LIST"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.LIST" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.PRINT"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.PRINT" />
</b:bean>
<!--
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.REPORT"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.REPORT" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.POPUP"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.POPUP" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.DOWNLOAD"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.DOWNLOAD" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.UPLOAD"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.UPLOAD" />
</b:bean>
<b:bean id="anyframe.iam.core.acl.ExtBasePermission.HELP"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<b:property name="staticField"
value="anyframe.iam.core.acl.ExtBasePermission.HELP" />
</b:bean>
-->Anyframe IAM 에서는 Spring Security 의 BasePermission 을 확장하여 많이 쓰이는 몇가지 Permission 속성을 추가한 ExtBasePermission 을 통하여 Spring Security 와 마찬가지로 Bit Mask 표현 및 처리를 제공하고 있다. READ, WRITE, CREATE, DELETE, ADMINISTRATION 는 Spring Security 에서 기본으로 제공하는 Permission 이며 위 설정 상에서는 해당 어플리케이션에서는 LIST, PRINT 만 더 추가하여 사용한 예이다. 위에서 확인할 수 있듯이 현재 Application 에서 사용하는 Permission 정의 영역에 필요한 Permission 정의 부분만 필터링 하여 적용할 수 있다. 위 예에서는 나타나 있지 않지만 ExtBasePermission 은 FNC0 ~ FNC9 까지의 추가 예약 Permission 속성을 가지고 있으므로 Application 에서 더많은 Permission 항목이 필요한 경우 적절히 의미를 부여하여 사용할 수 있을 것이다. 현재의 IAM Admin 에서도 타겟 어플리케이션마다 각각 달라질 수 있는 Permission 항목을 동적으로 처리하려면 위의 viewResourceAccessService 관련 설정을 IAM Admin 의 context-security.xml 에 동일하게 맞춰 주어야 함에 유의한다.
viewResourceAccessService 를 통해 로그인한 사용자(사용자 > 그룹 > ROLE 의 우선순위로 고려함)가 특정 viewResourceId 에 대해 특정 Permissions 를 가지고 있는지 체크할 수 있는 기능을 제공하며, JSP 커스텀 태그인 ViewResourceTag 는 이를 활용하여 Permission 에 따라 화면내에 제어할 영역의 처리를 쉽게 할 수 있게 된다. 관련 사용 예는 Access Control 챕터를 참고토록 한다. 또한 IAM Admin 에서는 Resource Management - View List / View Mapping 항목으로 사용자 정의 ViewResource 및 Permission 맵핑 데이터 관리 영역이 제공되고 있으니 참고토록 한다.