Java EE에서는 선언적 보안을 통해 웹 리소스를 보호할 수 있지만, 이 접근 방식으로는 서블릿 및 JSP(JavaServer Page)에서 사용하는 로컬 빈을 보호할 수 없다. 또한 선언적 보안을 통해 JSF(JavaServer Faces) 기술 페이지를 보호할 수 있지만 충분치 않은 경우가 많다.

이 팁에서는 관리 빈 메소드를 이용하여 JSF 보안 구성을 웹 페이지의 범위 밖으로 확장하는 방법을 소개한다.

서론

Java EE에서는 선언적 보안을 통해 웹 페이지 및 기타 웹 리소스(예: 파일, 디렉토리, 서블릿)를 보호할 수 있다. 이 방식에서는 web.xml 파일의 웹 리소스 그리고 이 리소스에 액세스할 수 있는 보안 역할을 선언한다. 예를 들어, web.xml 파일의 다음 선언에 따르면 admin 보안 역할을 갖는 인증된 사용자만 URL 패턴 /members.jsf에 의해 식별된 보안 리소스에 액세스할 수 있다.

   <security-constraint>
     <display-name>Sample</display-name>
        <web-resource-collection>
          <web-resource-name>members</web-resource-name>
          <description/>
          <url-pattern>/members.jsf</url-pattern>
          <http-method>GET</http-method>
          <http-method>POST</http-method>
        </web-resource-collection>
        <auth-constraint>
          <description/>
          <role-name>admin</role-name>
        </auth-constraint>
        </security-constraint>
    <security-role>
        <description/>
        <role-name>admin</role-name>
    </security-role>

<url-pattern> 요소에서 URL을 지정하여 보호하려는 리소스를 식별한다. 그러나 서블릿 및 JSP에서 사용하는 로컬 빈은 <url-pattern> 요소로 매핑할 수 없으므로 선언적 보안을 사용하여 로컬 빈을 보호할 수 없다.

또한 선언적 보안을 통해 JSF 페이지를 보호할 수는 있으나 충분치 않은 경우가 많다. 예를 들어, JSF 애플리케이션에서 동일한 페이지를 다른 역할의 사용자에게 제시하지만 이 역할 중 일부만 특정 연산을 수행하게 하려고 한다. 즉 모든 역할의 사용자가 데이터를 읽고 업데이트할 수 있지만 특정 역할의 사용자만 데이터를 만들고 삭제할 수 있다. 그러한 경우 JSF 보안을 웹 페이지의 범위 밖으로 확장할 방법이 필요하다.

또한 선언적 보안은 MVC 프레임워크 및 JSF에서 흔히 사용하는 요청 처리 과정에서 역할을 확인하지 않는다. 그 결과, 관리 빈은 어떤 뷰 ID도 반환할 수 있으며, 그 대상에는 보호 리소스도 포함된다. 그로 인해 액세스 권한이 없는 역할에게 보호 리소스가 노출될 우려가 있다.

해결책 하나는 JBoss Seam Web Bean, 즉 JSR 299: Web Bean을 사용하는 것이다. 웹 빈을 사용하면 페이지 보안, 구성 요소 보안 심지어 JPA(Java Persistence Architecture) 엔터티 보안도 구성할 수 있다. 그러나 Seam, Spring, EJB 또는 보안 프레임워크가 없는 더 간단한 보안 솔루션을 도입하는 회사들이 많다.

이 팁에서 다루는 방법에서는 관리 빈 메소드에 주석을 사용하여 JSF 보안을 확장하는 간단한 방식을 보여 준다. 샘플 애플리케이션이 이 팁에 제공된다. 팁의 코드 예제는 샘플 애플리케이션의 소스 코드에서 가져온 것이다.

확장 JSF ActionListener 및 NavigationHandler 선언

관리 빈 메소드의 보호를 제공하려면 확장 JSF ActionListenerNavigationHandler를 선언해야 한다. 이 사용자 정의 클래스는 각 사용자 작업을 분석하고 인증 및 권한 부여를 검사한다.

클래스를 활성화하려면 faces-config.xml 파일 내부에 다음 요소를 선언한다.

   <!-- JSF-security method-->
   <application>
     <action-listener>
       br.com.globalcode.jsf.security.SecureActionListener
     </action-listener>
     <navigation-handler>
       br.com.globalcode.jsf.security.SecureNavigationHandler
     </navigation-handler>
   </application>

SecureActionListener는 관리 빈 메소드에 대한 호출을 인터셉트하고 주석이 있는 메소드의 권한을 검사한다. NavigationHandler는 사용자에게 필요한 자격 증명 및 역할이 있는 경우 그 사용자를 요청된 뷰로 전달한다.

예를 들어, 다음 코드는 View 버튼 및 Delete 버튼이 있는 JSF 페이지를 렌더링한다.

   <h:form id="sampleSecurity">
     <h:commandButton value="View" id="unprotectedButton"
               action="#{CustomerCRUD.view}"/>
     <h:commandButton value="Delete"
               id="protectedButtonprotectedButton"
               action="#{CustomerCRUD.delete}"/>
   </h:form>

사용자가 Delete 버튼을 클릭하면 CustomerCRUD.delete 메소드가 호출된다. 이 메소드는 메소드에 필수적인 역할을 선언하는 주석을 포함한다.

   public class CustomerCRUD {
     
     public String view() {
       return "view-customer";
     }
     
     @SecurityRoles("customer-admin-adv, root")
     public String delete() {
       System.out.println("I'm a protected method!");
       return "delete-customer";
     }
     ...

SecureActionListenerCustomerCRUD.delete에 대한 호출을 인터셉트하고 customer-admin-adv 및 루트 권한을 검사한다. NavigationHandler는 사용자에게 필요한 자격 증명 및 역할이 있는 경우 그 사용자를 요청된 뷰로 전달한다.

사용자 개체 공급자 설정

web.xml에 컨텍스트 매개 변수를 추가함으로써 다음과 같이 여러 사용자 개체 공급자를 설정할 수 있다.

  • ContainerUserProvider: 컨테이너/선언적 보안과 통합된다.
  • SessionUserProvider: Http 세션에서 "user"라는 개체를 찾는다.
  • 해당 공급자: UserProvider 인터페이스 구현:

         <context-param>
           <param-name>jsf-security-user-provider</param-name>
           <param-value>
               YourClassImplementsUserProvider
           </param-value>
         </context-param>

ContainerUserProvider 설정

웹 컨테이너 공급자 방식은 선언적 보안과 통합되므로, 이미 선언적 보안을 사용하는 애플리케이션과 함께 사용할 수 있다. 기본 컨테이너 사용자 공급자를 설정하려면 다음 컨텍스트 매개 변수를 추가한다.

  <context-param>
    <param-name>jsf-security-user-provider</param-name>
    <param-value>
        br.com.globalcode.jsf.security.usersession.ContainerUserProvider
    </param-value>
  </context-param>

기본 웹 컨테이너 사용자 공급자 클래스는 다음과 같다.

   public class ContainerUserProvider implements UserProvider {
     ContainerUser user = new ContainerUser();
     public User getUser() {
       if(user.getLoginName()==null ||
               user.getLoginName().equals("")) {
         return null;
       } else {
         return user;
       }
     }

ContainerUserProviderContainerUser 클래스를 참조한다. ContainerUser 클래스는 다음과 같다(일부 코드 줄은 페이지 너비에 맞춰 나뉘어졌음).

   public class ContainerUser implements User {
     public String getLoginName() {
       if(FacesContext.getCurrentInstance().getExternalContext().
       getUserPrincipal()==null) return null;
       else return FacesContext.getCurrentInstance().
       getExternalContext().getUserPrincipal().toString();
     }
     public boolean isUserInRole(String roleName) {
       return
        FacesContext.getCurrentInstance().getExternalContext().
        isUserInRole(roleName);
     }

SessionUserProvider 사용

솔루션에서 사용자 정의 보안 인증 및 권한 부여 프로세스를 사용하는 경우, 해당 사용자 인터페이스를 구현하고 "user"라는 키 이름과 함께 사용자 개체 인스턴스를 HTTP 세션에 바인딩하는 사용자 클래스 어댑터를 제공할 수 있다. 이 방식은 선언적 보안을 사용하지 않는 레거시 Java EE 또는 J2EE 애플리케이션에서 효과적이다.

SessionUserProvider를 사용하도록 애플리케이션을 설정하려면 다음 단계를 따른다.

  1. 다음 컨텍스트 매개 변수를 web.xml 파일에 추가하여 사용자 공급자가 HTTP 세션에서 "user" 개체를 찾게 설정한다.

         <context-param>
           <param-name>jsf-security-user-provider</param-name>
           <param-value>
               br.com.globalcode.jsf.security.usersession.SessionUserProvider
           </param-value>
         </context-param>
     
  2. User 클래스 어댑터 구현을 만든다.

          package model;

          public class MyUser
            implements br.com.globalcode.jsf.security.User {
            //Your user instance object
            public String getLoginName() {
              //your user bridge
              return "me";
            }

            public boolean isUserInRole(String roleName) {
            //your user roles bridge
            return true;
            }
          } 

  3. 페이지 로그인에 login이라는 탐색 케이스를 제공한다.

           //Login page
            <h:form id="loginForm">
              <h:outputText value="Login:"/>
                <h:inputText value="#{LoginMB.userName}">
                </h:inputText>
               
              <h:outputText value="Password:"/>
              <h:inputText value="#{LoginMB.password}"/>
              <h:commandButton value="Login" action="#{LoginMB.login}"/>
              <h:messages/>
            </h:form>
       
          <navigation-case>
            <from-outcome>login</from-outcome>
            <to-view-id>/login.xhtml</to-view-id>
          </navigation-case>

  4. 사용자 자격 증명을 확인하고 그 사용자 개체를 HTTP 세션에 포함시키거나 포함시키지 않는 로그인 관리 빈을 작성한다.

          public class LoginMB {
            private String userName;
            private String password;

          @SecurityLogin
          public void login() {
            //Your login process here...
            MyUser user = new MyUser();
            HttpSession session =
            (HttpSession) FacesContext.getCurrentInstance().
            getExternalContext().getSession(false);
            session.setAttribute("user", user);
          }
        }

샘플 코드 실행하기

샘플 패키지가 이 팁에 제공된다. 이 샘플은 SessionUserProvider와 함께 실행되며 매우 단순한 사용자 및 로그인 페이지를 갖는다. 샘플을 설치하고 실행하려면 다음 단계를 수행한다.

  1. 샘플 패키지를 다운로드하고 압축을 푼다. 이제 압축이 풀린 새 디렉토리, <sample_install_dir>/facesannotations-glassfish가 나타나며, 여기서 <sample_install_dir>은 샘플 패키지를 설치한 디렉토리이다. 예를 들어, Windows 시스템의 C:\에 압축을 풀었다면 새로 만든 디렉토리는 C:\facesannotations-glassfish에 있어야 한다.

    확장된 샘플 패키지의 faces-config.xml 파일은 SecureActionListenerSecureNavigationHandler의 선언을 포함한다.

  2. NetBeans IDE를 시작한다.

  3. 다음과 같이 facesannotations-glassfish 프로젝트를 연다.

    • File 메뉴에서 Open Project를 선택한다.
    • 샘플 애플리케이션 다운로드에서 facesannotations-glassfish 디렉토리로 이동한다.
    • Open Project Folder 버튼을 클릭한다.

  4. 다음과 같이 facesannotations-glassfish를 실행한다.

두 개의 버튼, 즉 보호되지 않는 메소드를 호출하는 버튼과 보호되는 메소드를 호출하는 버튼을 포함하는 페이지가 나타나야 한다.

사용자 삽입 이미지

두 버튼을 모두 클릭하고 그 결과를 확인한다. 보호되지 않는 메소드를 실행할 수 있지만, 보
호 메소드에서는 특별한 역할이 필요함을 알게 된다.
사용자 삽입 이미지



저자 Vinicius Senger는 성능 분석가, Java EE 설계자 및 강사로 활동 중입니다.

이 아티클의 영문 원본은 http://java.sun.com/mailers/techtips/en ··· html%232 에서 볼수 있습니다.

"Java SE" 카테고리의 다른 글

2007/10/09 15:42 2007/10/09 15:42

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/448

댓글을 달아 주세요

[로그인][오픈아이디란?]

◀ Prev 1  ... 214 215 216 217 218 219 220 221 222  ... 641  Next ▶