내용 목차
본 장에서는 JEUS의 밸브와 필터를 설정하는 방법을 확인한다.
밸브는 Tomcat에서 가져온 개념으로 요청을 검사 혹은 변경을 위해 추가하는 구성요소를 의미한다.
밸브의 설정은 웹엔진, 가상호스트, 컨텍스트 단위로 설정할 수 있다.
필터는 javax.servlet.Filter를 의미하며, 밸브와 달리 서블릿 단위로 설정할 수 있다. 자세한 내용은 javax.servlet.Filter 참고하기 바란다.
본 절에서는 밸브에 대한 기본적인 개념과 사용법에 대해 설명한다.
밸브는 서블릿의 필터 개념을 서버와 가상호스트 레벨로 확장시킨 개념이다.
웹엔진, 가상호스트, 컨텍스트 순서로 밸브 코드를 실행시키게 되며, 같은 단위 내의 호출 순서는 설정에 지정한 순서대로 시행된다. 필터와 밸브가 모두 설정되어 있는 경우에는 밸브의 설정 모두 적용된 이후에 필터가 적용되게 된다.
WEB-INF/ 디렉터리 아래에 jeus-web-dd.xml을 생성한다. 이와 관련된 자세한 설명은“제3장 웹 컨텍스트”를 참고한다.
다음은 jeus-web-dd.xml 파일의 예이다. 몇몇 항목들은 생략되어 있다. 생략된 항목들은 "JEUS XML Reference"의 "13. jeus-web-dd.xml 설정"을 참고한다.
[예 9.1] 웹 컨텍스트 설정 파일 : <<jeus-web-dd.xml>>
<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="8.5"> <context-path>/examples</context-path> <pipeline> <valve> <class-name>jeus.servlet.valve.RemoteAddressValve</class-name> <property> <key>deny</key> <value>127\.0\.0\.1</value> </property> <property> <key>denyStatus</key> <value>403</value> </property> </valve> </pipeline> </jeus-web-dd>
다음은 domain.xml의 web-engine에 valve를 설정하는 예시이다. 관련 없는 설정은 생략했다.
[예 9.2] 서버 설정 파일 : <<domain.xml>>
<domain xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="8.5"> <web-engine> <pipeline> <valve> <class-name>jeus.servlet.valve.RemoteAddressValve</class-name> <property> <key>deny</key> <value>127\.0\.0\.1</value> </property> <property> <key>denyStatus</key> <value>403</value> </property> </valve> </pipeline> </web-engine> </domain>
다음은 설정 태그에 대한 설명이다.
class-name에 들어갈 class는 jeus.servlet.valve.ValveBase 클래스를 상속받아야 한다.
JEUS에서는 이용자 편의를 위해 밸브 구현체를 제공한다.
method를 검사해, 특정 메소드를 막거나 허용하는 옵션을 의미한다.
[예 9.3] 메소드 제약 옵션 밸브 추가 : <<jeus-web-dd.xml>>
<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="8.5"> <context-path>/examples</context-path> <pipeline> <valve> <class-name>jeus.servlet.valve.MethodConstraintValve</class-name> <property> <key>allow</key> <value>GET,POST</value> </property> </valve> </pipeline> </jeus-web-dd>
JEUS가 제공하는 메소드 제약 옵션에 사용할 클래스 이름은 jeus.servlet.valve.MethodConstraintValve이다. key로 allow와 deny를 줄 수 있으며, value로 메소드 목록을 주면 된다. 이 때, 각 http 메소드를 구분하는 구분자는 콤마( , )이다.
원격 호스트/주소를 허용할지 말지 결정하는 옵션을 제공한다.
차단하고자 하면 key에 deny를, 허용하고자 하면 key에 allow를 적는다. 이 때 대소문자를 구분하므로 주의해서 key를 주도록 한다. value에는 차단, 혹은 허용하고자 하는 remote address나 remote host를 적는다. 여기서 remote host의 값은 servletRequest객체에서 getRemoteHost() 메소드를 호출했을 때 리턴되는 값을 사용한다. 이에 대한 자세한 내용은 https://jakarta.ee/specifications/servlet/4.0/apidocs/javax/servlet/servletrequest#getRemoteHost--를 참조한다.
value에 줄 문자열은 자바 정규표현식을 따른다. 정규표현식 규칙은 java.util.regex 패키지의 규칙을 따른다. denyStatus는 요청을 막는 경우에 적용할 status 코드이다. 설정하지 않은 경우의 기본값은 403이다.
아래의 예시는 로컬에서의 접근만 허용하는 예제이다.
[예 9.4] remote address 밸브 추가 : <<jeus-web-dd.xml>>
<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="8.5"> <context-path>/examples</context-path> <pipeline> <valve> <class-name>jeus.servlet.valve.RemoteAddressValve</class-name> <property> <key>allow</key> <value>127\.0\.0\.1</value> </property> <property> <key>denyStatus</key> <value>403</value> </property> </valve> </pipeline> </jeus-web-dd>
아래 예제는 remote host valve의 사용 예제이다. remote host의 hostname이 www.tamx.co.kr인 경우 접근을 거부하는 예제이다.
[예 9.5] remote host 밸브 추가 : <<jeus-web-dd.xml>>
<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="8.5"> <context-path>/examples</context-path> <pipeline> <valve> <class-name>jeus.servlet.valve.RemoteHostValve</class-name> <property> <key>deny</key> <value>www\.tmax\.co\.kr</value> </property> </valve> </pipeline> </jeus-web-dd>
key와 value는 대소문자를 구분하기 때문에 옵션을 줄 때 주의하도록 한다. 또한, deny와 allow를 동시에 주지 않도록 주의한다.
URL을 재작성하기 위해 설정하는 밸브를 의미한다. Tomcat의 RewriteValve를 참고해 구현했고, 같은 규칙을 따른다.
url rewrite 기능의 목적은 외부에 자신이 설정한 도메인이 노출되지 않게 하거나, 다른 곳으로 redirect 하게 하는 목적이 있다. rewrite 규칙은 rewrite.config란 파일에 별도로 작성한다. rewrite 규칙에 대한 자세한 내용은 https://tomcat.apache.org/tomcat-8.5-doc/rewrite.html를 참조한다.
설정은 web-engine 및 context 단위로 할 수 있다. web-engine 단위로 하는 경우에는 DOMAIN/config에 한다. app 단위로 하는 경우에는 APP_HOME/WEB-INF 에 파일을 위치시키면 된다.
JEUS가 제공하는 url rewrite 옵션을 적용하기 위한 클래스 이름은 jeus.servlet.valve.rewrite.RewriteValve이다.
[예 9.6] rewrite 밸브 추가 : <<jeus-web-dd.xml>>
<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="8.5"> <context-path>/examples</context-path> <pipeline> <valve> <class-name>jeus.servlet.valve.rewrite.RewriteValve</class-name> <property> <key>encoding</key> <value>utf-8</value> </property> </valve> </pipeline> </jeus-web-dd>
key로 encoding을 줄 수 있으며 url rewrite 시에 사용할 인코딩을 지정한다. 지정하지 않는다면 JEUS의 요청 쿼리 인코딩을 적용하도록 되어 있다. JEUS의 인코딩과 rewrite.config의 인코딩, RewriteValve의 인코딩은 일치해야 한글 사용시 정상적으로 사용 가능하다.
JEUS의 query encoding은 다음과 같은 우선순위에 따라 결정된다.
domain.xml 내의 forced url encoding 설정
forced request encoding 설정
client override encoding 설정
url mapping 의 encoding 설정
default encoding 설정
아무것도 설정되어있지 않은 경우엔 ISO-8859-1
JEUS 개발자 또한 밸브를 상속받아 구현할 수 있다.
밸브를 추가하기 위해선 JEUS의 jeus.servlet.valve.ValveBase를 상속 받아 구현한다. ValveBase를 상속받아 사용하기 위해선 JEUS_HOME/lib/system의 javaee.jar와 jeus-servlet-engine.jar 라이브러리가 필요하다. 빌드한 이후에는 jar로 패키징해 JEUS_HOME/lib/thirdparty에 추가하면 된다. 이 때, jar파일 내에 javaee.jar와 jeus-servlet-engine.jar가 들어가지 않도록 주의한다.
아래는 모든 http response header에 test test를 추가해서 내보내는 valve의 예제이다.
[예 9.7] response header에 test:test를 추가하는 예제
package com.test.valve; import jeus.servlet.valve.ValveBase; import jeus.servlet.valve.ValveException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; public class TestValve extends ValveBase { private String test; @Override public void init() throws ValveException { Map<String,String> paramMap = getValveParameterMap(); test = paramMap.get("test"); } @Override public void invoke(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { //어플리케이션 실행 전 getNext().invoke(req, resp); //어플리케이션 실행 후 resp.setHeader(test,test); } }
init() 메소드의 경우에는 context 단위에 설정된 경우 deploy 될 때, domain.xml의 경우 가상호스트나 웹 엔진이 시작할 때 호출된다. getValveParameter() 메소드는 jeus-web-dd.xml이나 domain.xml에서 설정한 property의 map을 가져오는 메소드이다.
invoke 메소드의 경우에는 요청이 들어왔을 때의 valve의 동작을 정의한다. 기본적인 구조는 서블릿 필터와 유사하다. getNext().invoke() 메소드는 필터 구현에서의 chain.doFilter() 메소드와 같은 역할을 한다. getNext().invoke() 메소드 호출 이전에는 request를 이용해 필터 작업을 수행하고, response를 통해 응답의 필터링 작업을 실행한다.
JEUS_HOME/docs/api/jeus-servlet의 jeus.servlet.valve.ValveBase에 대한 설명이 있다.
init() 메소드에서 아무런 동작을 하지 않더라도 구현은 해야 한다. invoke() 메소드 또한 valve의 동작을 정의하기 때문에 구현해야 한다. 또한, invoke 구현시에 getNext().invoke()메소드를 호출하는 것을 잊어선 안된다. 요청의 처리는 항상 JEUS에서 정의한 마지막 밸브에서 하기 때문에, 다음 밸브로 넘어가지 못하는 경우에는 요청을 처리하지 못한다.
JEUS에서는 이용자 편의를 위해 필터 구현체를 제공한다.
samesite는 context 별로 설정할 수 있지만, user-agent를 식별해 다르게 동작할 수는 없다.
특정 client에서는 samesite를 처리하지 못하는 경우가 있어, JEUS에서 user-agent를 식별해 samesite를 삭제하는 필터를 제공한다.
[예 9.8] samesite 제한 옵션 필터 추가 : <<web.xml>>
<?xml version="1.0" encoding="UTF-8"?> <web-app version="4.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_4_0.xsd"> <filter> <filter-name>NoSameSiteFilter</filter-name> <filter-class>jeus.servlet.filters.NoSameSiteFilter</filter-class> <init-param> <param-name>agent</param-name> <param-value>^Chrome/\w$</param-value> </init-param> </filter> <filter-mapping> <filter-name>NoSameSiteFilter</filter-name> <servlet-name>NoSameSiteServletTest</servlet-name> </filter-mapping> </web-app>
JEUS가 제공하는 samesite 제한 옵션에 사용할 클래스 이름은 jeus.servlet.filters.NoSameSiteFilter이다. init-param에 name에는 agent로 설정해 주어야 하며, value로 samesite를 삭제하고자 하는 user-agent의 정규 표현식으로 표현한다.
RFC 7239에 정의되어 있는 Forwarded Header를 식별하여, javax.servlet.HttpServletRequest API에서 proxy가 아닌 client 정보를 얻어 올 수 있도록 하는 필터를 제공한다.
[예 9.9] Forwarded Header 옵션 필터 추가 : <<web.xml>>
<?xml version="1.0" encoding="UTF-8"?> <web-app version="4.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_4_0.xsd"> <filter> <filter-name>ForwardedFilter</filter-name> <filter-class>jeus.servlet.filters.ForwardedFilter</filter-class> </filter> <filter-mapping> <filter-name>ForwardedFilter</filter-name> <servlet-name>ForwardedFilterServletTest</servlet-name> </filter-mapping> </web-app>
JEUS가 제공하는 Forwarded Header 옵션에 사용할 클래스 이름은 jeus.servlet.filters.ForwardedFilter이다.