Spring Security - 5. DisableEncodeUrlFilter

하쮸·2025년 1월 28일

1. DisableEncodeUrlFilter.

package org.springframework.security.web.session;

import java.io.IOException;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;

import org.springframework.web.filter.OncePerRequestFilter;

/**
 * Disables encoding URLs using the {@link HttpServletResponse} to prevent including the
 * session id in URLs which is not considered URL because the session id can be leaked in
 * things like HTTP access logs.
 *
 * @author Rob Winch
 * @since 5.7
 */
public class DisableEncodeUrlFilter extends OncePerRequestFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		filterChain.doFilter(request, new DisableEncodeUrlResponseWrapper(response));
	}

	/**
	 * Disables URL rewriting for the {@link HttpServletResponse} to prevent including the
	 * session id in URLs which is not considered URL because the session id can be leaked
	 * in things like HTTP access logs.
	 *
	 * @author Rob Winch
	 * @since 5.7
	 */
	private static final class DisableEncodeUrlResponseWrapper extends HttpServletResponseWrapper {

		/**
		 * Constructs a response adaptor wrapping the given response.
		 * @param response the {@link HttpServletResponse} to be wrapped.
		 * @throws IllegalArgumentException if the response is null
		 */
		private DisableEncodeUrlResponseWrapper(HttpServletResponse response) {
			super(response);
		}

		@Override
		public String encodeRedirectURL(String url) {
			return url;
		}

		@Override
		public String encodeURL(String url) {
			return url;
		}

	}

}
  • DisableEncodeUrlFilter
    • 세션 ID가 HTTP 액세스 로그 등의 위치에서 유출될 수 있기 때문에 HttpServletResponse를 사용하여 URL을 인코딩하는 것을 비활성화해서 URL에 세션 ID가 포함되는 것을 방지함.
  • SecurityFilterChain에서 가장 첫 번째에 위치하고 있는 필터.
@Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        
        httpSecurity.sessionManagement((sessionManage) -> sessionManage.disable());
        
        return httpSecurity.build();
    }
httpSecurity.sessionManagement((sessionManage) -> sessionManage.disable());
  • 위 코드를 작성하면 비활성화 됨.
@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		filterChain.doFilter(request, new DisableEncodeUrlResponseWrapper(response));
	}

	private static final class DisableEncodeUrlResponseWrapper extends HttpServletResponseWrapper {

		private DisableEncodeUrlResponseWrapper(HttpServletResponse response) {
			super(response);
		}

		@Override
		public String encodeRedirectURL(String url) {
			return url;
		}

		@Override
		public String encodeURL(String url) {
			return url;
		}

	}
  • doFilterInternal이라는 필터 메서드로, HTTP 요청과 응답을 처리하는 과정에서 URL 인코딩을 비활성화하는 역할을 함.
    • HttpServletRequest request
      • 클라이언트로부터 들어오는 HTTP 요청 객체.
    • HttpServletResponse response
      • 서버에서 클라이언트로 보낼 HTTP 응답 객체.
    • FilterChain filterChain
      • 다음 필터로 요청을 전달하는 객체.
    • filterChain.doFilter(request, new DisableEncodeUrlResponseWrapper(response));
      • 기존의 response 객체를 DisableEncodeUrlResponseWrapper라는 래퍼(wrapper) 객체로 감싸서 필터 체인에 전달.
  • @Override된 두 개의 메서드(encodeRedirectURL, encodeURL)를 보면 URL을 그대로 반환하여 세션 ID가 URL에 포함되지 않도록 방지함.

HttpServletResponse 인터페이스를 구현한 org.apache.catalina.connector.Response 클래스

package org.apache.catalina.connector;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.SessionTrackingMode;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.catalina.Context;
import org.apache.catalina.Session;
import org.apache.catalina.security.SecurityUtil;
import org.apache.catalina.util.SessionConfig;
import org.apache.coyote.ActionCode;
import org.apache.coyote.Constants;
import org.apache.coyote.ContinueResponseTiming;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.CharChunk;
import org.apache.tomcat.util.buf.UEncoder;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.buf.UEncoder.SafeCharsSet;
import org.apache.tomcat.util.http.FastHttpDateFormat;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.http.parser.MediaTypeCache;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.security.Escape;

public class Response implements HttpServletResponse {
    private static final Log log = LogFactory.getLog(Response.class);
    protected static final StringManager sm = StringManager.getManager(Response.class);
    private static final MediaTypeCache MEDIA_TYPE_CACHE = new MediaTypeCache(100);
    protected static final int SC_EARLY_HINTS = 103;
    protected org.apache.coyote.Response coyoteResponse;
    protected final OutputBuffer outputBuffer;
    protected CoyoteOutputStream outputStream;
    protected CoyoteWriter writer;
    protected boolean appCommitted;
    protected boolean included;
    private boolean isCharacterEncodingSet;
    protected boolean usingOutputStream;
    protected boolean usingWriter;
    protected final UEncoder urlEncoder;
    protected final CharChunk redirectURLCC;
    private final List<Cookie> cookies;
    private HttpServletResponse applicationResponse;
    protected Request request;
    protected ResponseFacade facade;

    public Response() {
        this(8192);
    }
    
    
    		.....
            
	@Override
    public String encodeRedirectURL(String url) {
        if (isEncodeable(toAbsolute(url))) {
            return toEncoded(url, request.getSessionInternal().getIdInternal());
        } else {
            return url;
        }
    }


    @Override
    public String encodeURL(String url) {

        String absolute;
        try {
            absolute = toAbsolute(url);
        } catch (IllegalArgumentException iae) {
            // Relative URL
            return url;
        }

        if (isEncodeable(absolute)) {
            // W3c spec clearly said
            if (url.equalsIgnoreCase("")) {
                url = absolute;
            } else if (url.equals(absolute) && !hasPath(url)) {
                url += '/';
            }
            return toEncoded(url, request.getSessionInternal().getIdInternal());
        } else {
            return url;
        }

    }
return toEncoded(url, request.getSessionInternal().getIdInternal());
  • 위에서 본 encodeURL, encodeRedirectURL과는 다르게 세션ID 값과 관련된 코드가 있음을 확인.
profile
Every cloud has a silver lining.

0개의 댓글