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 ActionListener 및 NavigationHandler를 선언해야 한다. 이 사용자 정의 클래스는 각 사용자 작업을 분석하고 인증 및 권한 부여를 검사한다.
클래스를 활성화하려면 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"; } ...
SecureActionListener는 CustomerCRUD.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; } }
ContainerUserProvider는 ContainerUser 클래스를 참조한다. 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를 사용하도록 애플리케이션을 설정하려면 다음 단계를 따른다.
- 다음 컨텍스트 매개 변수를
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>
User클래스 어댑터 구현을 만든다.package model;public class MyUserimplements br.com.globalcode.jsf.security.User {//Your user instance objectpublic String getLoginName() {//your user bridgereturn "me";}public boolean isUserInRole(String roleName) {//your user roles bridgereturn true;}}
- 페이지 로그인에 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>
- 사용자 자격 증명을 확인하고 그 사용자 개체를 HTTP 세션에 포함시키거나 포함시키지 않는 로그인 관리 빈을 작성한다.
public class LoginMB {private String userName;private String password;@SecurityLoginpublic void login() {//Your login process here...MyUser user = new MyUser();HttpSession session =(HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);session.setAttribute("user", user);}}
샘플 코드 실행하기
샘플 패키지가 이 팁에 제공된다. 이 샘플은 SessionUserProvider와 함께 실행되며 매우 단순한 사용자 및 로그인 페이지를 갖는다. 샘플을 설치하고 실행하려면 다음 단계를 수행한다.
- 샘플 패키지를 다운로드하고 압축을 푼다. 이제 압축이 풀린 새 디렉토리,
<sample_install_dir>/facesannotations-glassfish가 나타나며, 여기서<sample_install_dir>은 샘플 패키지를 설치한 디렉토리이다. 예를 들어, Windows 시스템의C:\에 압축을 풀었다면 새로 만든 디렉토리는C:\facesannotations-glassfish에 있어야 한다.
확장된 샘플 패키지의faces-config.xml파일은SecureActionListener및SecureNavigationHandler의 선언을 포함한다.
- NetBeans IDE를 시작한다.
- 다음과 같이
facesannotations-glassfish프로젝트를 연다.
- File 메뉴에서 Open Project를 선택한다.
- 샘플 애플리케이션 다운로드에서
facesannotations-glassfish디렉토리로 이동한다.
- Open Project Folder 버튼을 클릭한다.
- File 메뉴에서 Open Project를 선택한다.
- 다음과 같이
facesannotations-glassfish를 실행한다.
- Projects 창에서
facesannotations-glassfish노드를 마우스 오른쪽 버튼으로 클릭한다.
- Run Project를 선택한다.
- 브라우저에서 다음 URL을 연다.
http://localhost:8080/facesannotations- ··· ndex.jsf
- Projects 창에서
두 개의 버튼, 즉 보호되지 않는 메소드를 호출하는 버튼과 보호되는 메소드를 호출하는 버튼을 포함하는 페이지가 나타나야 한다.
두 버튼을 모두 클릭하고 그 결과를 확인한다. 보호되지 않는 메소드를 실행할 수 있지만, 보
호 메소드에서는 특별한 역할이 필요함을 알게 된다.
저자 Vinicius Senger는 성능 분석가, Java EE 설계자 및 강사로 활동 중입니다.
이 아티클의 영문 원본은 http://java.sun.com/mailers/techtips/en ··· html%232 에서 볼수 있습니다.
"Java SE" 카테고리의 다른 글
- STRINGTOKENIZER에서 SCANNER까지 (댓글 2개 / 트랙백 1개) 2005/04/26
- 정적 인스턴스 초기화 블록 사용하기 (댓글 2개 / 트랙백 0개) 2004/04/13
- QUEUE와 DELAYED 프로세싱 (댓글 3개 / 트랙백 0개) 2004/11/04
- Callable을 사용하여 Runnable로부터 결과 반환 (댓글 0개 / 트랙백 0개) 2008/02/20
- 가비지 콜렉션 (댓글 3개 / 트랙백 0개) 2004/06/30
- 스윙에서의 멀티 쓰레딩 (댓글 2개 / 트랙백 0개) 2003/12/12
- SWING COMPONENTS의 저장과 재구성 (댓글 5개 / 트랙백 0개) 2003/08/05
- 스플래시 스크린과 MUSTANG (댓글 1개 / 트랙백 2개) 2005/12/20
- WSIT에서의 지원 토큰과 발급된 토큰 위임 (댓글 20개 / 트랙백 2개) 2007/09/03
- Singleton 패턴에 대한 재고찰 (댓글 4개 / 트랙백 1개) 2006/04/21
댓글을 달아 주세요