웹 사이트에 존재하는 모든 사용자들은 사이트 정책에 따라 그 부류 별로 컨텐츠에 대한 접근이 제한 되는데 이것을 권한 부여(authorization)라 한다.
즉, 권한은 특정 사용자가 웹 사이트에서 제공하는 컨텐츠(정보 또는 기능)에 접근 가능한지를 판단하는 과정을 의미한다.
XML 또는 DB에서 권한을 관리하며 계층적 권한을 지원한다.
Support hierarchical roles : Hierarchical roles : ROLE_A > ROLE_B > ROLE_C
FilterSecurityInterceptor 의 objectDefinitionSource 를 properties file에 나타내는 것을 허용한다.
모든 Authentication 구현체들은 GrantedAuthority 객체의 배열을 저장할 필요가 있다.
이런 GrantedAuthority 객체의 배열은 인증 주체에 허용된 권한들을 나타낸다.
GrantedAuthority 객체는 AuthenticationManager에 의해 Authentication 객체에 반환되며 권한부여에 관한 의사결정을 할 경우 AccessDecisionManager에 의해 읽어들여진다.
AccessDecisionManager는 사용자가 보안 자원에 접근하기 위한 적절한 권한을 가지고 있는지를 결정한다.
하나 이상의 허가 오브젝트 결과에 의해 보안자원에 대한 접근여부를 결정한다.
AccessDecisionManager는 다음과 같이 설정한다.
<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>
요청되는 웹 url을 점검하여 DB에 저장된 url과 비교하여 접근권한을 지정할 수 있다.
\A/sale/.*\.do\Z
\A/cvpl/((?!EgovCvplLogin\.do).)*\Z
FilterSecurityInterceptor는 HTTP 자원의 보안을 처리할 책임이 있다.
다른 보안 인터셉터와 유사하게 FilterSecurityInterceptor는 AuthenticationManager와 AccessDecisionManager에 대한 참조를 필요로 한다.
또한 FilterSecurityInterceptor는 서로 다른 HTTP URL 요청에 적용되는 설정 속성도 설정한다.
<b:bean id="filterSecurityInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor"> <custom-filter before="FILTER_SECURITY_INTERCEPTOR" /> <b:property name="authenticationManager" ref="authenticationManager" /> <b:property name="accessDecisionManager" ref="accessDecisionManager" /> <b:property name="objectDefinitionSource" ref="databaseObjectDefinitionSource" /> </b:bean>
DB 기반으로 현재 시점의 url 보호자원-권한의 맵핑 정보를 Runtime 에 동적으로
변경 반영하기 위한 Spring Security 의 DefaultFilterInvocationDefinitionSource 확장 클래스이다.
<b:bean id="databaseObjectDefinitionSource" class="org.springframework.security.intercept.web.EgovReloadableDefaultFilterInvocationDefinitionSource"> <b:constructor-arg ref="regexUrlPathMatcher" /> <b:constructor-arg ref="requestMap" /> <b:property name="securedObjectService" ref="securedObjectService"/> </b:bean>
DB 기반의 보호자원 맵핑 정보를 얻어 이를 참조하는 빈의 초기화 데이터로 제공한다.
resourceType을 url로 설정하여 securedObjectService의 getRolesAndUrl()를 호출하여 DB에서 역할과 url의 매핑정보를 얻어온다.
<b:bean id="requestMap" class="egovframework.rte.fdl.security.intercept.ResourcesMapFactoryBean" init-method="init"> <b:property name="securedObjectService" ref="securedObjectService"/> <b:property name="resourceType" value="url"/> </b:bean>
정규식 url을 지원한다.
<b:bean id="regexUrlPathMatcher" class="org.springframework.security.util.RegexUrlPathMatcher" />
자원을 요청하는 메소드명과 관련하여 egovframework.rte.fdl.security.web.CategoryController.selectCategoryList
Spring Security는 Spring의 DefaultAdvisorAutoProxyCreator와 함께 사용될 수 있는 MethodDefinitionSourceAdvisor를 대안으로 제공하며, 이것을 이용하여 자동적으로 보안 인터셉터를 MethodSecurityInterceptor를 정의한 빈의 앞부분에 연결한다.
<b:bean id="_methodDefinitionSourceAdvisor" class="org.springframework.security.intercept.method.aopalliance.MethodDefinitionSourceAdvisor"> <b:constructor-arg value="_methodSecurityInterceptor" /> <b:constructor-arg ref="_delegatingMethodDefinitionSource" /> </b:bean>
메소드 요청에 대한 자원을 보호하기 위해 MethodSecurityInterceptor를 어플리케이션 Context에 추가해야하며 보안을 필요로 하는 빈이 인터셉터에 연결(chaining)된다.
이러한 연결은 Spring의 ProxyFactoryBean이나 BeanNameAutoProxyCreator를 이용하여 만들어지며, Spring의 여러 다른 부분들이 통상적으로 이용되는 방식과 유사하다.
MethodSecurityInterceptor 는 다음과 같이 설정한다.
<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="objectDefinitionSource" ref="_delegatingMethodDefinitionSource" /> </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="methodDefinitionSources" class="org.springframework.security.intercept.method.MapBasedMethodDefinitionSource"> <b:constructor-arg ref="methodMap" /> </b:bean>
DB 기반의 보호자원 맵핑 정보를 얻어 이를 참조하는 빈의 초기화 데이터로 제공한다.
resourceType을 method로 설정하여 securedObjectService의 getRolesAndMethod()를 호출하여 DB에서 역할과 메소드의 매핑정보를 얻어온다.
<b:bean id="methodMap" class="egovframework.rte.fdl.security.intercept.ResourcesMapFactoryBean" init-method="init"> <b:property name="securedObjectService" ref="securedObjectService"/> <b:property name="resourceType" value="method"/> </b:bean>
DB 기반의 보호자원 맵핑 정보를 얻어 이를 참조하는 빈의 초기화 데이터로 제공한다.
resourceType을 pointcut으로 설정하여 securedObjectService의 getRolesAndPointcut()를 호출하여 DB에서 역할과 Pointcut의 매핑정보를 얻어온다.
execution(* egovframework.rte.security..service.*Service.insert*(..))
<b:bean id="pointcutMap" class="egovframework.rte.fdl.security.intercept.ResourcesMapFactoryBean" init-method="init"> <b:property name="securedObjectService" ref="securedObjectService"/> <b:property name="resourceType" value="pointcut"/> </b:bean>
역할은 상하 계층으로 관리하며 어플리케이션 Context 또는 DB에 저장하여 관리한다.
<b:bean id="roleHierarchy" class="org.springframework.security.userdetails.hierarchicalroles.RoleHierarchyImpl" > <b:property name="hierarchy"> <b:value> ROLE_ADMIN > ROLE_USER ROLE_USER > ROLE_RESTRICTED ROLE_RESTRICTED > IS_AUTHENTICATED_FULLY IS_AUTHENTICATED_REMEMBERED > IS_AUTHENTICATED_ANONYMOUSLY </b:value> </b:property> </b:bean>
<b:bean id="roleHierarchy" class="org.springframework.security.userdetails.hierarchicalroles.RoleHierarchyImpl" > <b:property name="hierarchy" ref="hierarchyStrings"/> </b:bean> <b:bean id="hierarchyStrings" class="egovframework.rte.fdl.security.userdetails.hierarchicalroles.HierarchyStringsFactoryBean" init-method="init"> <b:property name="securedObjectService" ref="securedObjectService"/> </b:bean>
<b:bean id="jdbcUserService" class="egovframework.rte.fdl.security.userdetails.jdbc.EgovJdbcUserDetailsManager" > <b:property name="usersByUsernameQuery" value="SELECT USER_ID,PASSWORD,ENABLED,USER_NAME,BIRTH_DAY,SSN FROM USERS WHERE USER_ID = ?"/> <b:property name="authoritiesByUsernameQuery" value="SELECT USER_ID,AUTHORITY FROM AUTHORITIES WHERE USER_ID = ?"/> <b:property name="roleHierarchy" ref="roleHierarchy"/> <b:property name="dataSource" ref="dataSource"/> <b:property name="mapClass" value="egovframework.rte.fdl.security.userdetails.EgovUserDetailsMapping"/> </b:bean>
<b:bean id="securedObjectService" class="egovframework.rte.fdl.security.securedobject.impl.SecuredObjectServiceImpl"> <b:property name="securedObjectDAO" ref="securedObjectDAO"/> </b:bean> <b:bean id="securedObjectDAO" class="egovframework.rte.fdl.security.securedobject.impl.SecuredObjectDAO" > <b:property name="dataSource" ref="dataSource"/> </b:bean>
아래의 속성쿼리는 SecuredObjectDAO 빈에 기본으로 내장되었으며 DBMS 벤더에 따른 SQL문 차이 또는 DB 스키마 차이로 인한 변경된 쿼리를 직접 반영할 수 있다.
내장된 기본 쿼리는 다음과 같다.
SELECT a.child_role child, a.parent_role parent FROM ROLES_HIERARCHY a LEFT JOIN ROLES_HIERARCHY b on (a.child_role = b.parent_role)
SELECT a.resource_pattern url, b.authority authority FROM SECURED_RESOURCES a, SECURED_RESOURCES_ROLE b WHERE a.resource_id = b.resource_id AND a.resource_type = 'url' ORDER BY a.sort_order
SELECT a.resource_pattern method, b.authority authority FROM SECURED_RESOURCES a, SECURED_RESOURCES_ROLE b WHERE a.resource_id = b.resource_id AND a.resource_type = 'method' ORDER BY a.sort_order
SELECT a.resource_pattern pointcut, b.authority authority FROM SECURED_RESOURCES a, SECURED_RESOURCES_ROLE b WHERE a.resource_id = b.resource_id AND a.resource_type = 'pointcut' ORDER BY a.sort_order
SecuredObjectDAO 빈에 내장된 SQL을 사용하지 않을 경우 아래와 같이 SQL을 지정하여 설정한다.
<b:bean id="securedObjectDAO" class="egovframework.rte.fdl.security.securedobject.impl.SecuredObjectDAO" > <b:property name="dataSource" ref="dataSource"/> <b:property name="sqlHierarchicalRoles"> <b:value> SELECT a.child_role child, a.parent_role parent FROM ROLES_HIERARCHY a LEFT JOIN ROLES_HIERARCHY b on (a.child_role = b.parent_role) </b:value> </b:property> <b:property name="sqlRolesAndUrl"> <b:value> SELECT a.resource_pattern url, b.authority authority FROM SECURED_RESOURCES a, SECURED_RESOURCES_ROLE b WHERE a.resource_id = b.resource_id AND a.resource_type = 'url' ORDER BY a.sort_order </b:value> </b:property> <b:property name="sqlRolesAndMethod"> <b:value> SELECT a.resource_pattern method, b.authority authority FROM SECURED_RESOURCES a, SECURED_RESOURCES_ROLE b WHERE a.resource_id = b.resource_id AND a.resource_type = 'method' ORDER BY a.sort_order </b:value> </b:property> <b:property name="sqlRolesAndPointcut"> <b:value> SELECT a.resource_pattern pointcut, b.authority authority FROM SECURED_RESOURCES a, SECURED_RESOURCES_ROLE b WHERE a.resource_id = b.resource_id AND a.resource_type = 'pointcut' ORDER BY a.sort_order </b:value> </b:property> </b:bean>
List<String> authorities = EgovUserDetailsHelper.getAuthorities(); // 1. authorites 에 권한이 있는지 체크 TRUE/FALSE assertTrue(authorities.contains("ROLE_USER")); assertTrue(authorities.contains("ROLE_RESTRICTED")); assertTrue(authorities.contains("IS_AUTHENTICATED_ANONYMOUSLY")); assertTrue(authorities.contains("IS_AUTHENTICATED_FULLY")); assertTrue(authorities.contains("IS_AUTHENTICATED_REMEMBERED")); // 2. authorites 에 ROLE 이 여러개 설정된 경우 for (Iterator<String> it = authorities.iterator(); it.hasNext();) { String auth = it.next(); } // 3. authorites 에 ROLE 이 하나만 설정된 경우 String auth = (String) authorities.toArray()[0];