21장. Secure Object 구현

21.1. AOP 연합(MethodInvocation) 보안 인터셉터

MethodInvocation을 보호하기 위해서는 개발자들은 단순히 적절히 설정된 MethodSecurityInterceptor를 애플리케이션 컨텍스트에 추가하기만 하면 된다. 그렇게 한 다음에는 보안을 필요로 하는 빈이 인터셉터에 연결(chaining)된다. 이러한 연결은 Spring의 ProxyFactoryBean이나 BeanNameAutoProxyCreator를 이용하여 만들어 지며, Spring의 여러 다른 부분들이 통상적으로 이용되는 방식과 유사하다(예제를 보려면 예제 애플리케이션을 참고한다). Acegi Security는 Spring의 DefaultAdvisorAutoProxyCreator와 함께 사용될 수 있는 MethodDefinitionSourceAdvisor를 대안으로 제공하며, 이것을 이용하여 자동적으로 보안 인터셉터를 MethodSecurityInterceptor를 정의한 빈의 앞부분에 연결한다. MethodSecurityInterceptor 자체는 다음과 같이 설정한다:

<bean id="bankManagerSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
  <property name="validateConfigAttributes"><value>true</value></property>
  <property name="authenticationManager"><ref bean="authenticationManager"/></property>
  <property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
  <property name="runAsManager"><ref bean="runAsManager"/></property>
  <property name="afterInvocationManager"><ref bean="afterInvocationManager"/></property>
  <property name="objectDefinitionSource">
    <value>
      org.acegisecurity.context.BankManager.delete*=ROLE_SUPERVISOR,RUN_AS_SERVER
      org.acegisecurity.context.BankManager.getBalance=ROLE_TELLER,ROLE_SUPERVISOR,BANKSECURITY_CUSTOMER,RUN_AS_SERVER
    </value>
  </property>
</bean>        

위에서 볼 수 있는 것과 같이 MethodSecurityInterceptorAuthenticationManager, AccessDecisionManagerRunAsManager와 함께 설정되며, 각각은 아래의 별도 섹션에서 설명하도록 하겠다. 이 경우 이미 AfterInvocationManager를 정의하긴 했지만, 이러한 설정은 완전히 선택적이다. 또한 MethodSecurityInterceptor는 다른 메소드 서명에 적용되는 설정 속성들도 함께 설정한다. 설정 속성에 관한 완전한 설명은 본 문서의 고수준 설계(High Level Design)에서 설명하도록 하겠다.

MethodSecurityInterceptor는 세 가지 방법으로 설정 속성들을 설정할 수 있다. 첫 번째 방법은 프로퍼티 에디터와 애플리케이션 컨텍스트를 이용하는 것으로서, 위에 나타나 있다. 두 번째 방법은 여러분의 소스코드에 Jakarta Commons Attributes나 Java 5의 어노테이션을 이용하여 설정 속성들을 정의하는 것이다. 세 번째 방법은 본 문서의 범위를 넘어서긴 하지만 여러분만의 자체적인 ObjectDefinitionSource를 작성하는 것이다. 사용된 접근법과는 상관없이 ObjectDefinitionSource는 하나의 안전 메소드와 연결되어 있는 설정 속성들을 모두 포함하는 ConfigAttributeDefinition 객체를 반환할 책임이 있다.

한 가지 알아둘 것은 MethodSecurityInterceptor.setObjectDefinitionSource() 메소드가 실질적으로는 MethodDefinitionSource의 인스턴스를 기대한다는 것이다. MethodDefinitionSourceObjectDefinitionSource를 서브클래싱하는 마커 인터페이스이다. ObjectDefinitionSource는 단지 ObjectDefinitionSourceMethodInvocation을 알고 있다는 것만을 나타낸다. 단순함을 위해 MethodDefinitionSourceObjectDefinitionSource로서 계속 참조할 것이며, MethodSecurityInterceptor를 사용하는 대부분의 사용자들과는 거의 상관이 없다.

애플리케이션 컨텍스트 프로퍼티 에디터 접근법을 사용할 경우(위에 나타나 있는 것과 같이), 콤마를 사용하여 주어진 메소드 패턴을 적용하는 여러 설정 속성들을 구분한다. 각 설정 속성들은 자체적인 SecurityConfig 객체에 할당된다. SecurityConfig 객체는 고수준 설계(High Level Design) 섹션에서 논의할 것이다.

만약 여러분이 Jakarta Commons Attributes를 사용하는 접근법을 취할 경우, 여러분의 빈 컨텍스트는 다르게 설정될 것이다.

<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>
<bean id="objectDefinitionSource" class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">
  <property name="attributes"><ref local="attributes"/></property>
</bean>

<bean id="bankManagerSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
  <property name="validateConfigAttributes"><value>false</value></property>
  <property name="authenticationManager"><ref bean="authenticationManager"/></property>
  <property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
  <property name="runAsManager"><ref bean="runAsManager"/></property>
  <property name="objectDefinitionSource"><ref bean="objectDefinitionSource"/></property>
</bean>       

추가적으로 여러분의 소스 코드는 ConfigAttribute의 구상 구현체를 참조하는 Jakarta Commons Attributes 태그를 포함할 것이다. 다음 예제는 SecurityConfig 구현을 이용하여 설정 속성들을 표현하고, 그리고 위의 프로퍼티 에디터에서 제공하는 보안 설정과 동일한 결과를 만들어낼 것이다.

public interface BankManager {

    /**
     * @@SecurityConfig("ROLE_SUPERVISOR")
     * @@SecurityConfig("RUN_AS_SERVER")
     */
    public void deleteSomething(int id);

    /**
     * @@SecurityConfig("ROLE_SUPERVISOR")
     * @@SecurityConfig("RUN_AS_SERVER")
     */
    public void deleteAnother(int id);

    /**
     * @@SecurityConfig("ROLE_TELLER")
     * @@SecurityConfig("ROLE_SUPERVISOR")
     * @@SecurityConfig("BANKSECURITY_CUSTOMER")
     * @@SecurityConfig("RUN_AS_SERVER")
     */
    public float getBalance(int id);
}

만약 여러분이 Acegi Security Java 5 Annotation 접근법을 이용한다면 여러분의 빈 컨텍스트는 다음과 같이 설정될 것이다:

<bean id="attributes" class="org.acegisecurity.annotation.SecurityAnnotationAttributes"/>
<bean id="objectDefinitionSource" class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">
  <property name="attributes"><ref local="attributes"/></property>
</bean>

<bean id="bankManagerSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
  <property name="validateConfigAttributes"><value>false</value></property>
  <property name="authenticationManager"><ref bean="authenticationManager"/></property>
  <property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
  <property name="runAsManager"><ref bean="runAsManager"/></property>
  <property name="objectDefinitionSource"><ref bean="objectDefinitionSource"/></property>
</bean>        

추가적으로 여러분의 소스코드는 ConfigAttribute를 나타내는 Acegi Java 5 Security Annotations를 포함할 것이다. 다음 예제는 @Secured 어노테이션을 이용하여 설정 속성들을 보여주며, 그 결과는 프로퍼티 에디터 접근법에 의해 제공된 보안 설정과 동일할 것이다.

import org.acegisecurity.annotation.Secured;

public interface BankManager {

    /**
     * Delete something
     */
    @Secured({"ROLE_SUPERVISOR","RUN_AS_SERVER" })
    public void deleteSomething(int id);

    /**
     * Delete another
     */
    @Secured({"ROLE_SUPERVISOR","RUN_AS_SERVER" })
    public void deleteAnother(int id);

    /**
     * Get balance
     */
    @Secured({"ROLE_TELLER","ROLE_SUPERVISOR","BANKSECURITY_CUSTOMER","RUN_AS_SERVER" })
    public float getBalance(int id);
}

여러분은 위의 MethodSecurityInterceptor 예제에 validateConfigAttributes 프로퍼티가 있다는 것을 알고 있을 것이다. 프로퍼티의 값을 true로 지정하면(기본값) MethodSecurityInterceptor가 시작시 제공된 설정 속성의 유효성 여부를 평가할 것이다. 이는 각 설정 속성들이 AccessDecisionManagerRunAsManager에 의해 처리될 수 있는지를 확인하여 이루어질 수 있다. 이러한 것들 중에서 주어진 설정 속성들을 처리할 수 있는 것이 아무것도 없으면 예외가 던져진다. Jakarta Commons Attributes 설정 메소드를 사용하고 있다면 validateConfigAttributesfalse로 지정해야 한다.

주의할 점은 BeanNameAutoProxyCreator를 사용하여 보안에 필요한 프록시를 생성할 경우 설정에 true로 지정된 proxyTargetClass 프로퍼티가 포함되어 있어야 한다는 것이다. 그렇게 하지 않으면 MethodSecurityInterceptor.invoke에 전달된 메소드는 프록시의 호출자이지 프록시의 대상이 아니다. 이는 CGLIB에 대한 요구사항을 도입한다는 것을 알아두어야 한다. BeanNameAutoProxyCreator를 사용하는 것은 아래 예제를 참조한다:

<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  <property name="interceptorNames">
    <list><value>methodSecurityInterceptor</value></list>
  </property>
  <property name="beanNames">
    <list><value>targetObjectName</value></list>
  </property>
  <property name="proxyTargetClass" value="true"/>
</bean>