java 환경에서 웹 개발을 할 때,  jsp에서 객체들을 활용 하여 요구 사항에 맞는 화면을 구성을 합니다.  기존 HTML 태그를 활용한 것 보다 더 다양한 화면 구성을 위해 JSTL(JavaServer Pages Standard Tag Library)을 사용 하지만, 이것 만으로 요구사항을 구현하기 힘들 때가 있습니다.  그래서 커스텀 태그를 활용하여, 구현이 간편하고, 이해 하기 쉽게 개발을 했습니다.

커스텀 태그란?

개발자가 직접 정의할 수 있는 태그를 의미합니다. 일반적으로 태그가 존재하며, 각각의 기능들이 존재하지만, 그것만으로 개발자가 원하는 내용을 구현 하지 못할 때가 많습니다. 이럴 때 커스텀 태그를 정의하여, 개발자 입맛에 맞게 만들어 효율적으로 사용 할 수 있는 태그 입니다. 기존 JTSL을 사용하는 것과 같이 선언을 해야 사용할 수 있으며(<%@ taglib .... %>), prifix와 uri로 다른 taglib와 구분 할 수 있습니다.

프로젝트를 하면서 페이지마다 사용자 권한을 체크하여 기능을 컨트롤 할 경우가 있습니다.

  • Spring-Security를 통한 권한별 페이지 접근 컨트롤
  • 각 컨트롤러 마다 Session의 값을 불러와 사용자의 권한을 조회 및 페이지 컨트롤
  • 커스텀 태그를 이용하여, 태그에서 권한 체크 및 페이지 컨트롤

다양한 방법이 있지만, 그 중 손쉽게 권한 체크를 하고 그에 따른 페이지 출력 할 수 있는 방안을 생각해 보았습니다.

권한 LEVEL 기능 비고
ADMIN 3 조회, 수정, 삭제 모든 사용자의 게시물을 수정, 삭제 가능
MANAGER 2 조회, 수정 본인의 게시물만 수정가능
DEVELOPER 2 조회, 삭제 본인의 게시물만 삭제 가능
GUEST 1 조회 -

프로젝트 초기에는 사용자 권한에 대한 별다른 요구사항이 없었기 때문에 ADMIN과 GUEST로 구분 지어 진행을 했었습니다. 그런데 프로젝트 중반에 고객사에서 사용자 권한을 위와 같이 구분 지어달라고 요청이 들어 왔습니다.

페이지 마다 요구 하는 기능이 각각 다르기 때문에 아래와 같이 권한을 체크하는 코드가 들어가야 했습니다. 그리고 요청 받는 Controller 마다 LoginUser 객체를 넘겨줘야 하는 불편함을 감수 했습니다. 페이지가 한 두개 일 때는 큰 문제가 없었지만, 수십개의 페이지에 적용을 하기에는 코드가 길어지고, 이해하기 어렵다는 문제점이 있었습니다.

<c:choose>
    <c:when test="${'ADMIN' eq loginUser.userGrade
                   or ('MANAGER' eq loginUser.userGrade and writerId eq loginUser.userId})">
        <!-- 수정기능 -->
    </c:when>
    <c:when test="${'ADMIN' eq loginUser.userGrade
                   or ('DEVELOPER' eq loginUser.userGrade and writerId eq loginUser.userId})">
        <!-- 삭제기능 -->
    </c:when>
</c:choose>

그리고, 한가지 고민거리가 있었습니다. 바로 MANAGER 와 DEVELOPER 두 개의 권한의 레벨이 같으면서 다른 기능을 갖는다는 것 입니다. 레벨이 서로 다르다면(LEVEL이 ADMIN-4, MANAGER -3, DEVELOPER-2, GUEST-1 일 때 ) 권한을 체크 할 때, '삭제기능은  MANAGER(Level 3) 이상 가능하다. 수정기능은 DEVELOPER(Level 2) 이상 가능하다.' 이렇게 정의를 하여 권한을 체크하기가 쉬웠을 겁니다. 하지만 레벨이 같은 권한이 있기 때문에 앞에 말한 것 처럼 사용 할 수 없었습니다. 그래서 해당 하는 기능을 ADMIN과 MANAGER, ADMIN과 DEVELOPER 처럼 각각 기능에 따라 권한 체크를 해줘야 했습니다.

처음에는  Session을 사용 하여 체크 하는 법을 생각 해 보았는데, Session도 각 컨트롤 마다 세션을 불러와 권한을 체크해야 하는 문제점이 있었습니다.  프로젝트에서 Spring-Security가 적용 되어있는 상태이기 때문에 Spring-Security를 활용하는 방안으로 대안을 생각해 보았습니다. 각 권한마다 접근 할 수 있는 페이지를 구분지어 따로 페이지를 구성하는 것 이었습니다.

위 처럼 url을 구분하여 접근 할 수 있는 권한체크를 Spring-Security에 위임을 하여 구현을 하는 것이였습니다. 권한을 체크하는 로직을 구현 안해도 되는 이점이 있지만, 동일한 화면 구성에 동일한 내용을 표현하는데 페이지를 2개로 나눠야 하는 부담이 생겼습니다. 그리고 View1에 대한 수정이 발생하면 test-update.jsp, test-delete.jsp에 적용을 하는 번거로움이 생겼습니다. 권한 체크 기능 구현을 안해도 되는 이점 보다 차후에 개발, 유지보수 해야 하는 단점이 훨씬 많아 Spring-Security를 사용하여 권한체크를 하는 것은 부적합 하다고 생각을 했습니다.

그래서 Custom tag를 사용하여 권한을 체크하는 기능을 위임하는 방법으로 구현을 했습니다. 권한 체크를 jsp가 아닌 java 클래스에서 처리하도록 하여, 컨트롤러마다 LoginUser 객체를 넣는 불편함을 제거 할 수 있었습니다. 그리고 jsp의 복잡한 조건문을 간단히 표현 할 수 있었습니다.

권한을 체크 할 때 각각의 기능에 필요한 권한들을  파라미터로 받아야 했으며, MANAGER와 DEVELOPER는 본인의 게시물만 수정, 삭제가 가능하다는 전제조건이 있기 때문에, 해당 글의 작성자 아이디가 필요 했습니다. 이와 같은 상황을 고려하여 커스텀 태그의 형태를 생각 해 보았습니다. 태그의 이름은 어떤 기능을 하는지 명확히 명시해야 되기 때문에 authorize라 정했습니다. 커스텀 태그의 모습은 prifix를 custag라 했을때

<custag:authorize grades="ADMIN,MANAGER" writerId="${writerId}"> 내용</ct:authrize>

모습이 될 것 입니다.  grades는 여러개가 들어 갈 수 있으며, 권한을 명시 해주지 않으면 권한체크의 기능을 수행 할 수 없기 때문에 필수요소로 지정되야 합니다. ADMMIN은 본인 여부 상관 없이 수정, 삭제가 이뤄지기 때문에 writerId는 필수 요소가 아닙니다.

이제 위와 같은 형태의 커스텀 태그를 만들기 위해서 아래와 같은 작업이 필요 합니다.

  • java 클래스 파일 생성 - 태그의 기능을 수행 하는 부분. 태그핸들러.
  • tld 파일 생성 - jsp에서 태그를 사용 할 수 있게 해주며, 태그가 어떤 핸들러를 통해 수행 할지 설정.
  • jsp 커스텀태그 선언

커스텀태그의 이런 제약 사항들을 명시를 해주어야 하는데 그 역할을 앞으로 만들 tld 파일에서 설정 해 줄 수 있습니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">

<taglib>
	<tlibversion>1.0</tlibversion>
	<jspversion>1.1</jspversion>
	<shortname>authorize</shortname>
	<info>tag library</info>

	<tag>
		<name>authorize</name> <!-- 커스텀 태그의 이름 -->
		<tagclass>test.security.AuthorizeTag</tagclass><!-- 태그 핸들러 클래스 -->
		<bodycontent>JSP</bodycontent> <!-- 바디 부분의 컨텐츠 타입 기술. 없으면 empty로 입력-->
		<attribute> <!-- 태그의 속성 기능을 부여 -->
			<name>grades</name> <!-- 속성명 -->
			<required>true</required> <!-- 필수 입력 여부 true : 필수 -->
			<rtexprvalue>false</rtexprvalue> <!-- 표현식(<%= %>) 사용 여부 -->
		</attribute>
		<attribute>
			<name>writerId</name>
			<required>false</required>
			<rtexprvalue>false</trexprvalue>
		</attribute>
	</tag>
</taglib>

tld 파일을 작성을 하였다면, 태그의 기능을 수행 할 내용이 필요 합니다. 그 내용들은 AuthorizeTag.java에서 수행 할 것 입니다. Body내용을 컨트롤 하기 위해 BodyTagSupport를 상속 받아 태그 핸들러로써 역할을 할 것이며, 실질적으로 권한을 체크하는 기능을 수행합니다. 해당 글에 권한이 있다면 jsp의 body 내용을 표시 할 것이며, 권한이 없다면 skip하여 내용이 보이지 않을 것 입니다.

public class AuthorizeTag extends BodyTagSupport{

	private static final long serialVersionUID = 1L;
	private final String SEPARATE =",";

	private String grades; // 콤마(,)로 구분되어 있다.
	private String writerId;

	@Override
	public int doAfterBody() throws JspException {
		UserGrade userGrade = null;
		try {
			userGrade = UserGrade.valueOf(LoginUser.getLoginUserGrade());
		} catch (Exception e) {
			return SKIP_BODY;
		}

		// 로그인한 계정만 가능한지 확인 (옵션) - null 이면 무시
		if (!StringUtil.isEmpty(writerId)) {
                        // 게시자의 아이디와 Login User의 Id 비교
			if (!Loginuser.getLoginId().equals(writerId)) {
				return SKIP_BODY; // jsp Body에 출력 안함
			}
		}

		// 메뉴 사용가능한 목록 추출
		List<String> availableGrades = Arrays.asList(grades.split(SEPARATE));
		if (availableGrades.contains(userGrade)) {
			print(); // jsp Body에 출력 , private method.
		}
	}
}

유저가 ADMIN 이라면 writerId는 null 이기 때문에 게시자의 아이디 판단은 무시가 됩니다. 태그의 파라미터 중 grades 속성은 콤마(,)로 구분지어진 문자열로 받아 리스트로 만들어, 로그인 한 유저의 권한과 비교하여 출력을 결정하도록 작성 하였습니다.

custom tag가 이제 준비가 되었습니다. 실제로 사용을 하려면, jsp에 taglib 를 해서 사용을 해야 합니다. jsp에서 authroize.tld 를 선언하고 jsp에서 호출하여 사용 하면 됩니다. 여기서 tld에 grades는 필수 입력하게 설정 했으므로, 반드시 사용 할 기능에 해당하는 권한을 입력 해줘야 합니다.

<%@ taglib prefix="custag" uri="/WEB-INF/tld/authorize.tld"%>
<custag:authorize grades="ADMIN,MANAGER" writerId="${writerId}"/>
    <!-- 수정 기능 -->
</custag:authorize>
<custag:authorize grades="ADMIN,DEVELOPER" writerId="${writerId}"/>
    <!-- 삭제 기능 -->
</custag:authorize>
<custag:authorize grades="ADMIN" >
    <!-- 관리자만 사용 할 수 있는 기능 -->
</custag:authorize>

custom tag를 사용하여 소스코드를 간략하게 만들었고,  가독성을 높일 수 있었습니다. jsp의 반복적인 기능이 필요 할때,  jsp에서 복잡하게 표현되어  java 클래스에서 단순화 할때 custom tag를 사용하면 좋을 것 같습니다. 그렇지만 무분별하게 custom tag를 만든다면, 결국 쓰이지 않는 태그가 만들어 질수도 있고 각각의 태그가 무슨 기능을 하는지 알 수 없어 소스코드를 다시 파악해야 될 것 입니다. custom tag가 궁극의 해결책은 아닙니다. jsp 소스가 복잡해지면, custom tag를 사용하기 보다는 서버 사이드 개선을 통하여 jsp쪽 개발 단순화 방안을 생각해야 할 것 입니다.