이번에는 서블릿 필터를 만들어보자. 서블릿 필터는 사실 그냥 Java로 서블릿을 사용하면 거의 무조건 사용하는 컴포넌트이다.
그래서 이건 뭐 JIRA 플러그인을 개발하기 위해 따로 알아야 하는 개념이 아니라 아마 익숙할 것 같다.
우선 서블릿 필터를 만드려면 당연히 dependencies로 서블릿이 있어야 할 것이고, 이건 이미 이 전 포스팅에서 다뤘다.
그리고 필터를 구현하는 클래스를 만들면 된다.
CustomServletFilter
package kr.osci.kapproval.admin.servlet.filter;
import lombok.RequiredArgsConstructor;
import javax.servlet.*;
import java.io.IOException;
@RequiredArgsConstructor
public class CustomServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
- void init(): 필터 초기화 작업이 필요하다면 이 메서드에 작성하면 된다.
- doFilter(): 각 필터마다 이 메서드에서 필터 처리를 한다. 필터는 요청을 다음 필터 또는 서블릿으로 전달할지 여부를 결정할 수 있습니다.
- destroy(): 필터 종료 작업이 필요하다면 이 메서드에 작성하면 된다.
필터의 주요 역할
- 요청(Request)에 대한 전처리: 클라이언트 요청이 서블릿이나 JSP로 전달되기 전에 요청을 수정하거나, 인증/인가 검사를 수행하거나, 로깅 등을 할 수 있다.
- 응답(Response)에 대한 후처리: 서블릿이나 JSP가 응답을 만들고, 클라이언트에게 전달되기 전에 응답을 수정하거나, 로깅 등을 할 수 있다.
필터의 동작 과정
- 클라이언트의 요청 수신: 클라이언트로부터 HTTP 요청이 들어오면 웹 서버는 이를 필터 체인(Filter Chain)에 전달한다.
- 필터 체인 통과: 요청은 필터 체인을 따라 이동하며, 각 필터는 요청을 처리할 기회를 가진다.
- 각 필터는 `doFilter` 메서드를 통해 요청을 처리한다.
- 필터는 요청을 다음 필터 또는 서블릿으로 전달할지 여부를 결정할 수 있다.
- 서블릿 또는 JSP로 전달: 필터 체인을 모두 통과한 요청은 최종적으로 서블릿이나 JSP에 도달하여 본래의 비즈니스 로직을 수행한다.
- 응답 생성: 서블릿이나 JSP가 응답을 생성하면, 응답은 다시 필터 체인을 따라 클라이언트로 돌아간다.
- 필터 체인 역순 통과: 응답은 필터 체인을 역순으로 통과하며 각 필터는 응답을 처리할 기회를 가진다.
- 클라이언트 응답 전달: 최종적으로 처리된 응답이 클라이언트에게 전달된다.
다음과 같은 필터를 만들어보자!
CustomServletFilter
public class CustomServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 필터 초기화 작업 (필요한 경우)
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 요청 전처리 작업
System.out.println("Request received at MyFilter");
// 필터 체인의 다음 요소로 요청을 전달
chain.doFilter(request, response);
// 응답 후처리 작업
System.out.println("Response leaving MyFilter");
}
@Override
public void destroy() {
// 필터 종료 작업 (필요한 경우)
}
}
이 필터는 다음과 같은 동작을 한다.
- 클라이언트로부터 요청이 들어오면 `Request received at MyFilter`를 출력한다.
- 요청을 다음 필터 또는 서블릿으로 넘긴다.
- 요청에 대한 응답을 생성한 서블릿 또는 JSP가 응답을 다시 필터로 넘기고 그 응답이 여러 필터를 거쳐 이 필터로 도착한다.
- `Response leaving MyFilter`를 출력한다.
- 응답을 클라이언트에게 최종적으로 전달한다.
이렇게 만든 필터를 결국 등록을 해야 사용할 수 있는데, 이 JIRA DC 플러그인을 개발할땐 언제나 리소스는? Add-on Descriptor(atlassian-plugin.xml)에 등록한다. 참고로 JIRA DC 플러그인을 개발하는게 아니면 개발 방식에 따라 필터 등록하는 방법은 다 가지각색이라 목적에 맞는 방법을 찾으면 된다.
atlassian-plugin.xml
<servlet-filter key="licenseServletFilter" class="kr.osci.kapproval.admin.servlet.filter.LicenseServletFilter" location="before-dispatch" >
<url-pattern>/plugins/servlet/kapproval/admin/*</url-pattern>
</servlet-filter>
서블릿 필터를 등록하고, url-pattern 태그로 어떤 URL의 요청이 이 필터를 거칠지를 결정하면 된다. 이렇게 설정하면 끝이다.
여기서 location 이라는 attribute가 있다. 이건 이 필터가 어디쯤에 위치할지를 정하는 것이다.
나는 `before-dispatch` 라는 값을 주었다. 이게 기본값이고 이건 서블릿 필터 체인의 가장 마지막에 이 필터를 추가하는 것이다. 그러니까 이 요청을 처리하는 서블릿이나 JSP에 도달하기 바로 직전에. 그리고 이러한 옵션들에 대한 내용, 또한 서블릿 필터에 대한 자세한 내용은 아래 공식 문서를 참고하자.
서블릿 필터가 어떤 원리로 동작하고 어떻게 사용되는지 알아보았다!
만약, 요청을 가로채서 하는 작업이 인증/인가를 확인하는 처리라면 인증이 되지 않은 경우 다음 필터 또는 서블릿으로 넘기기 전에 그냥 바로 사용자에게 응답을 돌려줄 수 있다. 예를 들면 이런 코드를 작성할 수 있다.
public class CustomServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 필터 초기화 작업 (필요한 경우)
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 인증 여부 확인 (여기서는 간단하게 세션에 "authenticated" 속성이 있는지 확인)
Boolean isAuthenticated = (Boolean) httpRequest.getSession().getAttribute("authenticated");
if (isAuthenticated == null || !isAuthenticated) {
// 인증이 안된 경우, 바로 응답 생성
httpResponse.setContentType("text/html");
PrintWriter out = httpResponse.getWriter();
out.println("<html><body>");
out.println("<h3>Authentication Required</h3>");
out.println("<p>You are not authenticated. Please <a href=\"login.html\">login</a>.</p>");
out.println("</body></html>");
out.close();
} else {
// 인증이 된 경우, 다음 필터 또는 서블릿으로 요청 전달
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
// 필터 종료 작업 (필요한 경우)
}
}
이거는 단순 예시일 뿐, 구현은 원하는대로 어떻게든 할 수 있다!
결론
서블릿 필터를 통해 클라이언트의 요청을 가로채서 추가 작업을 할 수 있고 응답을 내보내기 전 마지막 작업을 할 수 있다. 그러려면 Filter를 구현한 서블릿 필터 클래스가 필요하고, 이 클래스는 init, doFilter, destroy 라는 메서드를 재정의해야 하는데 여기서 가장 중요한 건 doFilter 메서드이다. 이 doFilter 메서드에 chain.doFilter()를 호출하기 전에 작성한 코드가 클라이언트의 요청이 서블릿으로 넘어가기 전 작업하는 부분이고 chain.doFilter()를 호출한 이후 코드가 생성된 응답을 클라이언트에게 최종적으로 전달하기 전 작업하는 부분이 된다.
'Jira & ScriptRunner' 카테고리의 다른 글
Jira DC 플러그인 개발 Part.10 - 로그 설정 (0) | 2024.08.13 |
---|---|
Jira DC 플러그인 개발 Part.8 이벤트 리스너 등록하기 (0) | 2024.07.23 |
Jira DC 플러그인 개발 Part.6 - JQL Function 만들기 (0) | 2024.07.16 |
Jira DC 플러그인 개발 Part.5 - 상단 네비게이션 바에 앱 링크 노출하기 (0) | 2024.07.16 |
Jira DC 플러그인 개발 Part.4 - ActiveObjects ORM (0) | 2024.07.15 |