http://www.javalobby.org/articles/acegisecurity/part1.jsp

<div class="note"> principals - 사용자
vote - 제안
role - 역활 </div>

당신의 자바 애플리케이션을 보안화하기 - Acegi 보안 스타일#

목차#

  1. 소개[1]
  2. 인증[2]
  3. 인증 제공자(Providers)[3]
  4. 권한[4]
  5. 결론[5]

소개[#1]#

애플리케이션 개발에서 가장 지루하고 어려운 것중에 하나는 보안(security)이다. 특히 인증과 권한이 더욱 그러하다. 대부분의 다중 사용자 애플리케이션은 사용자가 누구인지 알고 필요한 자원에 접근하기 위해 적절한 인증절차를 거쳐야만 할 필요가 있다. 그러므로, 보안이란 종종 가장 중요한 부분중에 하나가 되기도 한다. 이러한 요소의 부조화는 특히 기업용 애플리케이션에서 보안을 무관심하게 만들거나 에러를 발생시키기 쉽고, 잠재적인 위험에 빠트리게 만든다.

J2EE스펙과 자바 인증및 권한 서비스(JAAS-Java Authorization and Authentication Service)는 올바른 방향으로 각각의 단계를 제공한다. 모든 애플리케이션 서버 업체는 각각 다른 방식으로 컨테이너 보안을 구현할뿐 아니라 JAAS를 요구하지 않는다. 이것으로 인해 이식성과 사용자 관리에서 제약사항이 만들어진다. 더군다나, 이것은 위에서 언급한 방식으로 보안을 달성하지 않는다.

Spring을 위해 디자인된 오픈소스 보안 프레임워크인 Acegi Security프레임워크로 들어가보자. Ben Alex에 의해 만들어진, 이 프레임워크는 종합적인 기능(훌륭한 단위 테스트 범위, 쉬운 사용법, Spring과의 느슨하게 커플링된 통합)을 달성하기 위해 시작했다. 비록 Spring을 위해 임의로 디자인되었지만, Spring을 사용하지 않는 애플리케이션, 특히 웹 애플리케이션에서 사용될수 있다.

이 두번에 걸쳐 배포되는 글에서, 우리는 Acegi Security프레임워크를 살펴볼것이다. 첫 부분은 Acegi에 대한 소개(핵심 컴포넌트, Spring애플리케이션 컨텍스트를 통한 설정)를 제공한다. 이러한 지식을 바탕으로, 우리는 간단한 웹 애플리케이션을 위한 인증과 권한 서비스를 구현하는 방법을 배울것이다.

이 글의 다음 부분에서, 우리는 비지니스 객체를 보호하기 위한 Spring기반의 AOP기능과 도메인 객체 인스턴스 보안을 위한 접근제어 목록(ACL-access control list) 같은 좀더 고급의 기능을 소개할것이다.

인증[#2]#

자원에 대한 접근을 승인하거나 제한하기로 결정하기 전에, 사용자는 적절한 보안 확인을 해야만 한다. 이러한 이유로, Acegi는 인증서비스를 제공하기 위한 두개의 핵심 인터페이스(Authentication 과 AuthenticationManager)를 제공한다. 이 두가지가 완벽한 인증시스템을 완성하는 방법을 알아보자.

Authentication 인터페이스는 3개의 중요한 객체를 가진다. 첫번째 객체는 호출자(즉 사용자)를 확인하는 principal이다. 두번째 객체는 호출자 확인을 증명하는 credentials이다. 전통적인 로그인에서, 사용자명에 대응되는 비밀번호이다. 마지막 객체는 Authentication인터페이스에 의해 포함되고 principal에 의해 부여된 authorities의 배열이다.

인증절차를 거치는 동안, Authentication인터페이스의 구현은 클라이언트 코드에 의해 principal 과 credentials를 활성화한다. 예를 들면, 웹 애플리케이션은 사용자명과 비밀번호를 위한 확인창을 사용자에게 제시한다. 입력된 사용자명과 비밀번호는 Authentication객체를 생성하기 위해 사용된다. 여기서 3개의 객체중 하나인 부여된 authorities 배열은 빈것으로 남겨둔다. 이부분이 AuthenticationManager가 일련의 인증작업(authentication chain)을 수행하는 지점이다.

AuthenticationManager는 다음과 같은 authenticate라는 하나의 메소드를 명시한다.

public Authentication authenticate(Authentication authentication) throws AuthenticationException;

이 메소드는 파라미터로 활성화된 Authentication객체를 가진다. 여기서 메소드는 완전히 활성화된 Authentication객체를 반환하거나 AuthenticationException예외를 던지는 두가지의 선택사항을 가진다. 만약 정확한 principal 과 credentials가 제공되었다면, AuthenticationManager는 완전히 활성화된 Authentication객체를 반환하여 구성자(former)가 된다. 이것은 인증된 principal에 권한을 부여하는 것으로 보완한다. 인증이 실패한다면, AuthenticationException은 던져지고 하위클래스의 수를 통해 원인을 표시한다.

가장 중요한 하위클래스는 BadCredentialsException, DisabledException, 그리고 LockedException이다. 단어자체에서 형상화해볼때, 첫번째는 틀린 principal 과 credentials를 제공할때 던져진다. 나머지 두개는 principal계정이 사용가능하지 않거나 잠겨있을때 던져진다. 그러므로 credentials은 체크되지 않고 authorization은 거부된다.

인증 제공자(Providers)[#3]#

위 언급된 인터페이스가 중요(특히 개발자가 Acegi를 위한 사용자정의 인증 기법을 생성하기 위해서는 특히) 하지만, 가장 중요한 것은 일련의 인증작업을 이해하는 것이다. 대부분의 개발자는 포함된 provider-기반 인증 패키지중에 하나를 사용하는 것을 고려한다. AuthenticationManagers의 가장 유명한 구현물은 ProviderManager이다.

모든 상황에 대한 목적을 수행하기 위해, ProviderManager는 단지 클래스에 제공되는 하나 또는 그 이상의 AuthenticationProviders 목록을 포장하는 것에 지나지 않는다. 인증을 거치는 동안, 래퍼(wrapper)클래스는 호환가능한 provider가 찾아지기 전까지 AuthenticationProviders목록을 통해 순환한다. 한번 찾아지면, AuthenticationManager의 authenticate메소드는 특정 provider로 위임한다. 이에 대해, provider는 완전히 활성화된 Authentication객체를 반환하거나 AuthenticationException을 던진다. 기대대로, 모든 provider의 누적된 결과는 래퍼(ProviderManager)로부터 반환된다.

JAAS와 JBoss인증을 포함한 여러가지 AuthenticationProviders구현물은 Acegi Security시스템에 의해 제공된다. 이 글에서, 우리는 데이터베이스나 내부-메모리(in-memory) 해시(hash)와 같은 저장소에 대한 사용자명과 비밀번호 조합을 인증할수 있는 DaoAuthenticationProvider를 사용할것이다. 이 provider는 이해와 설정, 시험가동이 쉽다. 어쨌든 이 글을 읽는 사람은 요구사항에 가장 잘 맞는 것을 찾기 위해 다른 provider도 조사해야만 한다.

추상화(abstraction)와 위임(delegation)의 각각의 단계는 유연성을 가져온다. DaoAuthenticationProvider의 경우, 이것은 하나의 메소드인 loadUserByUsername를 가지는 AuthenticationDao인터페이스의 구현물에 위임한다. 이 메소드는 사용자명을 가지고 인증을 확인하기 위한 사용자의 상세정보를 로드한다. InMemoryDaoImpl개발자는 자신만의 구현물(예를들면 hibernate를 사용하는)을 생성하는데 자유롭다. 어쨌든, Acegi는 두개(JDBC기반과 메모리기반)의 매우 유용한 구현물을 가진다. 이 글에서는 후자인 메모리기반 즉, InMemoryDaoImpl를 사용할것이다.

AuthenticationDao로 시작하는 언급된 컴포넌트를 설정하는것으로 시작해보자.( 이 글은 사용자가 Spring XML설정에 친숙하는것을 전제로 작성되었다. ). 아래의 설정은 "matthew"라는 이름의 사용자로 InMemoryDaoImpl인스턴스를 생성한다. 이 사용자의 비밀번호는 "contegix"이고 "ROLE_ADMIN"의 부여된 권한을 가진다.

applicationContext.xml

<bean id="memoryAuthenticationDao" class="net.sf.acegisecurity.providers.dao.memory.InMemoryDaoImpl">
    <property name="userMap">
        <value>
            matthew=contegix,ROLE_ADMIN
        </value>
    </property>
</bean>

다음 단계는 provider처럼 목록화된 메모리 인증 DAO로 InMemoryDaoImpl인스턴스를 생성하는 것이다.

applicationContext.xml

<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
     <property name="authenticationDao">
         <ref local="memoryAuthenticationDao"/>
     </property>
</bean>

마지막으로, 다음 단계로 가서 유일한 provider처럼 DAO인증 provider를 가진 인증관리자를 생성한다.

applicationContext.xml

<bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">
    <property name="providers">
        <list>
            <ref bean="daoAuthenticationProvider"/>
        </list>
    </property>
</bean>

이 시점에서, 인증관리자는 완전히 설정되었고 사용할 준비가 되었다. 다음 단계는 우리의 웹 애플리케이션으로 이것을 묶는 것이다.

웹 애플리케이션을 위한 인증을 수행하는 많은 방법이 있다. 가장 널리 퍼진 방법은 RFC 1945, 11섹션 그리고 HTTP세션인증에 의해 정의된 Basic Authentication이다. 우리의 애플리케이션에서는, 후자를 사용할것이다.

Acegi는 서블릿 필터를 통해 HTTP세션 인증을 수행한다. AuthenticationProcessingFilter는 두개의 파라미터(j_username 과 j_password)를 포함하는 프로그래밍된 로그인 폼으로부터 정보를 처리한다. Acegi인증 필터의 유일한 관점은 FilterToBeanProxy를 사용하여 Spring의 애플리케이션 컨텍스트에 명시된 bean프록시에 이것을 위임한다는 사실이다. 그러므로, 모든 Acegi필터는 이 클래스의 인스턴스를 사용하여 설정될것이고 애플리케이션 컨텍스트내 bean을 가리키는 초기화 파라미터를 통해 targetClass나 targetBean이 제공되어야만 한다. 애플리케이션 컨텍스트 bean은 필터보다는 authentication을 위한 파라미터를 가지고 설정된다.

web.xml

<filter>
    <filter-name>Acegi Authentication Processing Filter</filter-name>
    <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
    <init-param>
        <param-name>targetClass</param-name>
        <param-value>
        net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter
        </param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>Acegi Authentication Processing Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

applicationContext.xml

<bean id="authenticationProcessingFilter" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
    <property name="authenticationManager">
        <ref bean="authenticationManager"/>
    </property>
    <property name="authenticationFailureUrl">
        <value>/login.jsp?error=1</value>
    </property>
    <property  name="defaultTargetUrl">
        <value>/</value>
    </property>
    <property name="filterProcessesUrl">
        <value>/j_acegi_security_check</value>
    </property>
</bean>

AuthenticationProcessingFilter bean에 전달되는 프라퍼티를 보자. 첫번째 프라퍼티는 상대적으로 자명하다. 이것은 설정된 인증관리자에 대한 참조이다. 명백히, bean은 일련의 인증과정을 통해 처리하기 위해서 이것을 도구화할것이다.

만약 인증이 실패한다면, 브라우저는 authenticationFailureUrl에 명시된 URL로 자동으로 이동할것이다. 만약 인증이 성공한다면, 브라우저는 인증과정이 필수인 제한된 URL로 이동할것이다. 이것은 사용자에게 접근을 시도하는 것에 자동으로 반환되도록 허용한다. 만약 어떠한 자원도 명시되지 않았다면(예를 들면 사용자가 로그인 URL로 직접 접근할때), defaultTargetUrl프라퍼티는 사용자가 자동으로 이동할 지점을 명시한다.

마지막으로, filterProcessesUrl은 필터가 활성화되는 URL이다. 개발자를 위해, 이 URL은 HTML폼내 action파라미터처럼 명시될것이다.

지금 우리의 애플리케이션은 사용자를 인증할수 있다. 그럼 각각의 자원에 대한 접근제한을 둬보자.

권한[#4]#

Security Interception의 개념은 Acegi아래 자원을 제한하는 것이 핵심이다. 자원에 접근하기에 앞서, 인터셉션(interception)은 자원에 대한 접근이 제한될지 제한되지 않을지를 결정한다. 인터셉션은 추가 호출하고 접근이 할당되는지 되지 않는지 시험한다. 결과에 기초하여, 인터셉션은 요청을 허용하기도 하고 허용하지 않기도 한다.

일련의 인증과정을 추적하여, 보안 인터셉터는 제한된 자원에 대한 접근을 받는다. 사용자가 인증되었다고 가정할때, 이것은 인증된 Authentication객체와 자원 프라퍼티와 같은 핵심 파라미터를 받는 AccessDecisionManager의 구현물로 위임한다. 접근에 대한 마지막 결정은 AccessDecisionManager에게 남겨진다.

개발자가 적절할때 사용자정의 AccessDecisionManager를 구현하는 것은 환영받지만, 대부분의 환경은 제안(voting)의 개념에 기초한 구현물의 사용을 허용한다. Acegi는 죄근 계산된 제안인 ConsensusBased, UnanimousBased, 그리고 AffirmativeBased에 들어맞는 3개의 AccessDecisionManager 구현물을 제공한다. ConsensusBased구현물은 절제되지 않는(non-abstain) 제안(votes)의 일치(consensus)에 기초하여 접근을 허용하거나 거부한다. 이름이 제시하는것처럼, UnanimousBased구현물은 접근을 허용하기 위한 일치하는(unanimous) 허가(consent)를 요구하지만 절제(abstains)는 무시한다. 마지막으로, AffirmativeBased구현물은 거부 결정사항이 무시되는 동안 접근허용이 적어도 한번 받아들여진다면, 접근을 허용한다.

생각해볼만한 질문은 AccessDecisionManager를 제안하는 것이 어떻게 제안을 수용하기 위한 방법을 결정하는가 이다. 이것은 AccessDecisionVoters가 일련의 인증 결정의 역활을 수행하는 것이다. 이 인터페이스의 유일한 구현물은 principal이 역활에 할당된다면 접근을 허용하는 RoleVoter이다. 역활 할당은 각각의 인증된 Authentication객체의 허용된 권한배열의 요소이다. 반면에, 자원이 SUPERVISOR의 역활을 요구한다면, 허용된 인증절차를 거칠때 사용자는 접근이 허용될것이다.

우리의 애플리케이션의 경우, RoleVoter와 함께 UnanimousBased구현물은 필수적인 요구사항을 만족시킬것이다. 어쨌든, 복잡한 구조와 Acegi내 AccessDecisionManagers의 역활을 이해하는 것은 매우 중요하다. 좀더 많은 것을 알기 위해서는 참조문서를 보라.

RoleVoter 와 UnanimousBased로 시작하여 인증시스템을 설정해보자.

applicationContext.xml

<bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>

<bean id="accessDecisionManager" class="net.sf.acegisecurity.vote.UnanimousBased">
    <property name="allowIfAllAbstainDecisions">
        <value>false</value>
    </property>
    <property name="decisionVoters">
        <list>
           <ref local="roleVoter"/>
        </list>
    </property>
</bean>

RoleVoter는 프라퍼티 없이 간단한 bean초기상태처럼 존재한다. UnanimousBased는 두개의 프라퍼티를 설정한다. 첫번째는 모든 AccessDecisionVoters를 금할때 접근이 허용될지 안될지를 결정한다. 두번째는 초기화된 RoleVoter를 위한 참조이다.

다음 단계는 보안 인터셉션 시스템을 설정하는 것이다. 웹 애플리케이션의 경우, 보안 인터셉션은 FilterSecurityInterceptor와의 조합으로 서블릿 필터(SecurityEnforcementFilter)를 사용하여 수행한다. URL기반 자원을 위한 인증 접근을 제공하기 위해 협력하여 작동하는 두개의 객체가 있다. 대부분 필터는 보안을 위한 인터셉터에 위임하는 동안 세션관리와 사용자 로그인을 위한 URL리다이렉션(AuthenticationEntryPoint객체에 의해 명시된것처럼)을 다룬다.

이전처럼, 필터는 애플리케이션 컨텍스트로부터 초기화된 bean을 가져오기 위해 FilterToBeanProxy를 도구화한다.

web.xml

<filter>
    <filter-name>Acegi HTTP Request Security Filter</filter-name>>
    <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>

    <init-param>
        <param-name>targetClass</param-name>
        <param-value>
        net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter
        </param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>Acegi HTTP Request Security Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

applicationContext.xml

<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
    <property name="filterSecurityInterceptor">
        <ref bean="filterInvocationInterceptor"/>
    </property>
    <property name="authenticationEntryPoint">
        <ref bean="authenticationEntryPoint"/>
    </property>
</bean>

<bean id="authenticationEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
    <property name="loginFormUrl">
        <value>/login.jsp</value>
    </property>
</bean>

<bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">
    <property name="authenticationManager">
        <ref bean="authenticationManager"/></property>
    <property name="accessDecisionManager">
        <ref bean="accessDecisionManager"/></property>
    <property name="objectDefinitionSource">
        <value>
            CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
           PATTERN_TYPE_APACHE_ANT
            /secure/super/* *=ROLE_SUPERVISOR <!-- 별표 사이를 붙여야 함 */-->
            /secure/.* =ROLE_USER,ROLE_SUPERVISOR
        </value>
    </property>
</bean>

밑에서 부터, FilterSecurityInterceptor가 선언되고 인증관리자와 접근결정관리자로 전달된다. 게다가, 객체는 ObjectDefintionSources세트를 가져온다. 각각의 값은 특정 효과를 제공한다. 첫번째 값은 평가되기전에 모든 URL을 소문자로 변환하기 위한 변환자를 알린다. PATTERN_TYPE_APACHE_ANT는 마지막 두개의 값들의 형태를 설명하는 지시자이다. 이것은 나머지 파라미터가 정규표현식을 사용하는 디폴트 패턴보다는 아파치 Ant스타일의 패턴을 사용하도록 인터셉터에게 알린다.

마지막 두 값은 보안을 위한 URL패턴이다. = 표시를 기점으로 왼쪽의 값은 오른쪽의 값들이 허용을 제안하도록 계획하기 위해 필요한 역활을 설명하는 URL패턴이다. 이 예제에서, /secure/super/ 아래의 것들은 오직 SUPERVISOR역활을 가진 사용자에 의해서만 접근가능하다. 반면에 /secure/는 USER나 SUPERVISOR역활을 가진 사용자에 의해서 접근가능하다.

AuthenticationEntryPoint는 사용자가 인증절차를 거치지 않았을때 제한된 URL로 요청을 보내면 호출될것이다. 마지막으로, targetClass bean이 선언되고 AuthenticationEntryPoint 와 FilterSecurityInterceptor에 대한 참조를 전달한다.

결론[#5]#

Acegi는 자바 플랫폼을 위해 사용가능한 가장 좋은 보안 프레임워크이다. 비록 설정이 Spring을 도구화할지라도, 이 글은 애플리케이션을 Spring과 통합하지 않는 시스템에서조차 강력함을 보여준다. 게다가, 전체 프레임워크는 추상화를 통해 확장가능성의 멋진 예제를 제공한다.

이 글과 설치법이 독자에게 Acegi를 통합하기 시작하도록 해주지만, 많은 설정옵션과 기능이 제외되었다. 독자에게는 멋진 참조문서를 보길 권한다.

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-6) was last changed on 06-Apr-2006 09:45 by 이동국