본문 바로가기

표준프레임워크_eGovFrame/공통컴포넌트_Common Components

표준프레임워크 공통컴포넌트 3.8 HTMLTagFilter 특정 페이지 미적용하기(javaconfig 방식)

표준프레임워크 2018년에 정식 릴리즈한 표준프레임워크 공통컴포넌트 3.8은 HTMLTagFilter를 

Java Config 방식으로 바꾸었다. Java 내 에서 커스터마이징이 가능한 구조로 변경이 되었다는 소리다.

 

먼저 두 개의 클래스가 HTMLTagFilter에 쓰이는 자바 파일이라 보면 된다.

  • /egovframework/com/cmm/filter/HTMLTagFilter
    : 기존  javax.servlet.Filter doFilter method 통해 수행하는(implements) filter 구현체

/*
 * Copyright 2008-2009 MOPAS(MINISTRY OF SECURITY AND PUBLIC ADMINISTRATION).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package egovframework.com.cmm.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class HTMLTagFilter implements Filter{

	@SuppressWarnings("unused")
	private FilterConfig config;

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		chain.doFilter(new HTMLTagFilterRequestWrapper((HttpServletRequest)request), response);
	}

	public void init(FilterConfig config) throws ServletException {
		this.config = config;
	}

	public void destroy() {

	}
}
  • /egovframework/com/cmm/filter/HTMLTagFilterRequestWrapper
    : 기존  javax.servlet.http.HttpServletRequestWrapper 확장해(extends)  HtmlTagFilter에서 Wrapper 사용되는 클래스이며, 화이트리스트의 방식에 대한 커스텀 태그 등록이 가능한 클래스

/*
 * Copyright 2008-2009 MOPAS(MINISTRY OF SECURITY AND PUBLIC ADMINISTRATION).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package egovframework.com.cmm.filter;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
*
* HTMLTagFilterRequestWrapper 
* @author 공통컴포넌트 팀 신용호
* @since 2018.03.21
* @version 1.0
* @see
*
* <pre>
* << 개정이력(Modification Information) >>
*
*   수정일              수정자              수정내용
*  -------      --------    ---------------------------
*   2018.03.21  신용호              getParameterMap()구현 추가
*   2019.01.31  신용호              whiteList 태그 추가
*
*/

public class HTMLTagFilterRequestWrapper extends HttpServletRequestWrapper {

	// Tag 화이트 리스트 ( 허용할 태그 등록 )
	static private String[] whiteListTag = { "<p>","</p>","<br />" };
	
	public HTMLTagFilterRequestWrapper(HttpServletRequest request) {
		super(request);
	}

	public String[] getParameterValues(String parameter) {

		String[] values = super.getParameterValues(parameter);
		
		if(values==null){
			return null;			
		}
		
		for (int i = 0; i < values.length; i++) {
			if (values[i] != null) {
				values[i] = getSafeParamData(values[i]);
				//System.out.println( "[HTMLTagFilter getParameterValues] "+ parameter + "===>>>"+values[i] );
			} else {
				values[i] = null;
			}
		}

		return values;
	}

	public String getParameter(String parameter) {
		
		String value = super.getParameter(parameter);
		
		if(value==null){
			return null;
		}
		
		value = getSafeParamData(value);
		//System.out.println( "[HTMLTagFilter getParameter] "+ parameter + "===>>>"+value );
		return value;
	}

	/**
	 * Map으로 바인딩된 경우를 처리한다.
	 *
	 * @return  Map - String Type Key / String배열타입 값
	 */
    public Map<String, String[]> getParameterMap() {
    	Map<String, String[]> valueMap = super.getParameterMap();

    	String[] values;
    	for( String key : valueMap.keySet() ){
    		values = valueMap.get(key);

    		for (int i = 0; i < values.length; i++) {			
    			if (values[i] != null) {				
    				values[i] = getSafeParamData(values[i]);
    				//System.out.println( "[HTMLTagFilter getParameterMap] "+ key + "===>>>"+values[i] );
    			} else {
    				values[i] = null;
    			}
    		}
    		
            //System.out.println( String.format("키 : %s, 값 : %s", key, valueMap.get(key)) );
        }

    	return valueMap;
    }
    
	private String getSafeParamData(String value) {
		StringBuffer strBuff = new StringBuffer();

		for (int i = 0; i < value.length(); i++) {
			char c = value.charAt(i);
			switch (c) {
			case '<':
				if ( checkNextWhiteListTag(i, value) == false )
					strBuff.append("&lt;");
				else 
					strBuff.append(c);
				//System.out.println("checkNextWhiteListTag = "+checkNextWhiteListTag(i, value));
				break;
			case '>':
				if ( checkPrevWhiteListTag(i, value) == false )
					strBuff.append("&gt;");
				else 
					strBuff.append(c);
				//System.out.println("checkPrevWhiteListTag = "+checkPrevWhiteListTag(i, value));
				break;
			//case '&':
			//	strBuff.append("&amp;");
			//	break;
			case '"':
				strBuff.append("&quot;");
				break;
			case '\'':
				strBuff.append("&apos;");
				break;	
			default:
				strBuff.append(c);
				break;
			}
		}
		
		value = strBuff.toString();
		return value;
	}

	private boolean checkNextWhiteListTag(int index, String data) {
		String extractData = "";
		//int beginIndex = 0;
		int endIndex = 0;
		for(String whiteListData: whiteListTag) {
		    //System.out.println("===>>> whiteListData="+whiteListData);
			endIndex = index+whiteListData.length();
		    if ( data.length() > endIndex )
		    	extractData = data.substring(index, endIndex);
		    else
		    	extractData = "";
		    //System.out.println("extractData="+extractData);
		    if ( whiteListData.equals(extractData) ) return true; // whiteList 대상으로 판정
		}
		
		return false;
	}
	
	private boolean checkPrevWhiteListTag(int index, String data) {
		String extractData = "";
		int beginIndex = 0;
		int endIndex = 0;
		for(String whiteListData: whiteListTag) {
		    //System.out.println("===>>> whiteListData="+whiteListData);
			beginIndex = index-whiteListData.length()+1;
			endIndex = index+1;
		    //System.out.println("  range ["+beginIndex+" ~ "+endIndex+"]");
		    if ( beginIndex >= 0 )
		    	extractData = data.substring(beginIndex, endIndex);
		    else
		    	extractData = "";
		    //System.out.println("extractData="+extractData);
		    if ( whiteListData.equals(extractData) ) return true; // whiteList 대상으로 판정
		}
		
		return false;
	}

}

HTMLTagFilter는 EgovWebApplicationInitializer에 선언되어 필터로 관리되고 있다. (전체 코드를 붙이기엔 너무 길어져서 관련 부분 코드만 삽입

  • /egovframework/com/cmm/config/EgovWebApplicationInitializer
		//-------------------------------------------------------------
	    // HTMLTagFilter의 경우는 파라미터에 대하여 XSS 오류 방지를 위한 변환을 처리합니다.
		//-------------------------------------------------------------	
	    // HTMLTagFIlter의 경우는 JSP의 <c:out /> 등을 사용하지 못하는 특수한 상황에서 사용하시면 됩니다.
	    // (<c:out />의 경우 뷰단에서 데이터 출력시 XSS 방지 처리가 됨)
		FilterRegistration.Dynamic htmlTagFilter = servletContext.addFilter("htmlTagFilter", new HTMLTagFilter());
		htmlTagFilter.addMappingForUrlPatterns(null, false, "*.do");

* HTMLTagFilter지만 addMappingForUrlPatterns에 대한 설명도 넣는다. (원문은 접어보기 클릭)

...더보기

void javax.servlet.FilterRegistration.addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns)

 

Adds a filter mapping with the given url patterns and dispatcher types for the Filter represented by this FilterRegistration.

Filter mappings are matched in the order in which they were added.

 

Depending on the value of the isMatchAfter parameter, the given filter mapping will be considered after or before any declared filter mappings of the ServletContext from which this FilterRegistration was obtained.

 

If this method is called multiple times, each successive call adds to the effects of the former.

 

Parameters:

    dispatcherTypes the dispatcher types of the filter mapping, or null if the default DispatcherType.REQUESTis to be used

    isMatchAfter true if the given filter mapping should be matched after any declared filter mappings, and false if it is supposed to be matched before any declared filter mappings of the ServletContext from which this FilterRegistration was obtained

    urlPatterns the url patterns of the filter mapping

 

Throws:

IllegalArgumentException - if urlPatterns is null or empty

IllegalStateException - if the ServletContext from which this FilterRegistration was obtained has already been initialized

URL 패턴 및 Dispatcher 타입으로 필터 매핑을 추가한다는 거입니다. 

필터 맵핑은 추가 된 순서대로 일치되는데, addMappingForUrlPatterns(dispatcherTypes,isMatchAfter,)으로 필터의 파라미터를 사용해 쓰는 겁니다. addMappingForUrlPatterns의 파라미터를 설명하자면, 아래와 같습니다.

 

Parameter

    1. dispatcherTypes :

       필터 매핑의 디스패처 유형. 기본 DispatcherType.REQUEST가 사용될 경우 null

    2. isMatchAfter :

        지정된 필터 맵핑이 선언 된 필터 맵핑 후에 일치해야하는 경우 true

        FilterRegistration을 얻은 ServletContext의 선언된 필터 매핑 전에 일치해야하는 경우 false

    3. urlPatterns :

       필터 매핑의 URL 패턴

 

표준프레임워크 코드는 현재 default로

FilterRegistration.Dynamic htmlTagFilter = servletContext.addFilter("htmlTagFilter", new HTMLTagFilter());
htmlTagFilter.addMappingForUrlPatterns(null, false, "*.do");

되어있네요. 이 의미는 현재 htmlTagFilter가 Request 타입으로, 선언된 매핑 "전"에 필터가 일치되고, 모든 *.do에 대해 사용되고 있습니다.

 

공통컴포넌트 화면은 아래와 같습니다.

또한  http://localhost:8080/cop/bbs/insertArticleView.do?bbsId=BBSMSTR_000000000001 에서 

아래와 같이 등록을 해보겠습니다.

그 다음 리스트를 가보면, 아래와 같이 htmlTagFilter가 동작되어 필터된 제목을 보실 수 있으실 겁니다.

하지만 종종 저희는, 필터가 적용되지 않는 게시판을 업무적으로 구현해야하는 경우가 있죠.

자 이제 htmlTagFilter에 클래스를 변경해 만들겠습니다.

  • src/main/java/egovframework/com/cmm/filter/HTMLTagFilter 로 가서
    아래 처럼 코드를 변경합니다.
package egovframework.com.cmm.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class HTMLTagFilter implements Filter{

	@SuppressWarnings("unused")
	private FilterConfig config;
	


	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		
		HttpServletRequest httpRequest = (HttpServletRequest) request ;
		
		if(excludePatternUrl(httpRequest)) {
			chain.doFilter(new HTMLTagFilterRequestWrapper(httpRequest), response);
		}else {
			chain.doFilter(new HttpServletRequestWrapper(httpRequest), response);
		}
		
	}
	
	private boolean excludePatternUrl(HttpServletRequest request) {
		
		String uri = request.getRequestURI().toString().trim();
		
		if(uri.startsWith("/cop/bbs/")) {
			return false;
		}else { 
			return true;
		}
	}


	public void init(FilterConfig config) throws ServletException {
		this.config = config;
	}

	public void destroy() {

	}
}

 

 

자 이제 새로운 글을 등록하면 아래와 같이 htmltagFilter가 적용이 안된 글을 볼 수 있습니다. whitelist방식에 걸리지 않게 되죠.

 

글을 마치면서...

startsWith를 쓴점은 좋지 않죠. 예외처리나 보안상, 로직이 좋지는 않습니다.

또한 소스 단에서 언제나 url 추가를 통해 관리해야하구요.

심화적으로, 별도의 게시판을 추가해 게시판에서 예외의 페이지를 설정할 수 있게 UI로 만들어보려고 합니다.

 

하지만 Springboot + Thymeleaf에 꽂혀서... 그 녀석부터 하고 오겠습니다.

 

감사합니다.