티스토리 뷰

반응형

얼마전 회사에서 개발한 Spring으로 구현 된 REST API, Apache web server에서 php로 돌아가는 서비스, expressJS로 돌아가는 서비스, Netty로 동작하고 있는 실시간 Push 서버에 동일한 보안 로직을 적용하기 위한 프로젝트를 진행하였다. 기존에는 각각의 보안 정책을 가지고 있어서 관리하기가 힘들고 또 여러개의 보안 프레임워크와 독자적으로 개발된 보안 로직을 관리 해야 했다. 

서비스 되는 서비스들의 크기가 크지 않을 때는 이러한 방법이 편리할 수도 (?) 있겠지만, 이미 우리는 상당히 꽤 많은 프로젝트 들이 생겨 버려서 더 이상은 프로젝트 별 보안 로직을 관리하기가 힘들어 졌다. 그래서 프로젝트 별로 동일하게 적용되는 보안 정책과 개별 정책들을 모아서 하나의 보안 서버를 만들었고, Tomcat기반에서 돌아가는 프로젝트를 위한 보안체킹을 위해서 보안 필터를 라이브러리를 만들기 위해서 개발 하던 중 HttpServletRequest의 getInputStream을 Servlet의 Filter에서 사용해버리면 다른 필터나 Controller에서 해당 Request에 대해서 다시는 getInputStream을 호출 해서 값을 가져 갈 수 없다는 것을 알았다.... 그래서 이번 포스팅에서는 Servlet Filter에서 getInputStream을 불러도 이후에 또 다시 부를 수 있는 방법에 대해서 설명한다. 

방법은 간단했다. HttpServletRequestWrapper를 상속받아서 정의 해주면 된다. 먼저 소스를 보자.

  package com.hiball.client.filter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

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

public class HiBallHttpServletRequestWrapper extends HttpServletRequestWrapper {
	private final String body;
	
	public HiBallHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
		//So that other request method behave just like before
        super(request);
         
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        try {
            InputStream inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } else {
                stringBuilder.append("");
            }
        } catch (IOException ex) {
            throw ex;
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ex) {
                    throw ex;
                }
            }
        }
        //Store request pody content in 'body' variable
        body = stringBuilder.toString();
	}
	
	@Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletInputStream = new ServletInputStream() {
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;
    }
 
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
 
    //Use this method to read the request body N times
    public String getBody() {
        return this.body;
    }
	
}

HttpServletRequestWrapper를 상속하게 되면 먼저 생성자를 만들게 하는데 해당 생성자에서 구현 할 내용은 InputStream을 읽어와 전역변수로 있는 body라는 변수에 읽어온 내용을 저장해 준다. 만약 여기서만 끝내 버린다면 이후에 getInputStream을 사용했을 경우 참조 할 수 없다는 에러를 똑같이 보게 될것이다. 그래서 getInputStream(): ServletInputStream, getReader(): BufferedReader 두개의 메소드를 Override를 해주게 되고 getInputStream에서는 전역변수 body에 저장된 내용을 가지고 InputStream을 다시 만들어 주게 된다. 
또한 getReader에서 BufferedReader를 다시 생성하게 구현을 해주게 되면 Filter에서 getInputStream()을 호출 하더라도 필터를 지나 이후의 필터라든지 Spring의 Controller에서 HttpServletRequest의 InputStream을 참조 할 수 있게 된다. 

 

반응형

'Java' 카테고리의 다른 글

MapStruct란?  (0) 2021.08.01
FFMPEG Wrapper Library 사용법  (4) 2018.05.04
구글 주스와 MyBatis의 연동  (0) 2017.09.23
구글 주스 사용기[1]  (0) 2017.08.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함