본문 바로가기

아키텍처 Architecture/Software Architecture

Spring Boot HTTP, HTTPS 모두 사용하기 ( tomcat redirection )

0. 결론 요약

1) Spring Boot에 @Configuration 파일을 생성해 TomcatServletWebServerFactory의 설정을 변경 → 리다이렉션 적용

2) application.properties에 server.port=443, server.port.http=80 둘 다 명시

3) Docker 환경의 경우 추가설정 : docker-compose.yml에 port 2개 다 명시 80:80, 443:443

 

- Docker 사용 유무와 상관없이 적용 가능. 

- 스프링부트 내장 tomcat의 옵션을 조정하는 방법

 

1. 문제상황

SSL 적용으로 HTTPS 접속이 가능해졌으나 HTTP로 접속한 사용자들은 접속이 안되는 상황.

* HTTPS 적용 방법 : https://subbak2.tistory.com/110

 

SpringBoot 무료 SSL 인증서 적용하기 (Certbot)

1. 필요성 SSL 인증서 없이 웹 어플리케이션을 서버에 올리면 http로 접속이 되고 아래와 같은 경고가 나타난다. "주의요함" 클릭해보면 보안이 취약하다는 문구가 뜨는게 내가 만들었지만 들어가

subbak2.tistory.com

 

 

2. 시행착오 (실패한 방법)

실패했던 방법이지만 비슷한 시행착오를 겪을 수 있으니 기록.

 

* Docker의 port 설정을 이용하면 되지 않을까?

  → docker-compose.yml에서 ports 부분에 80:443, 443:443 으로 설정 

      → 실패

HTTP로 접속했을때  

Bad Request This Combination of host and port requires TLS. 라는 문구가 뜨며 실패한다.

 

 

3. 스프링부트 embeded tomcat을 활용하는 방법 

1) application.properties에 http, https 포트를 모두 사용한다.

server.port=443
server.port.http=80

 

2) Configuration 파일을 아래처럼 작성한다.

import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConnectorConfig {
	
	 @Bean
	    public ServletWebServerFactory servletContainer() {

	        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(){
	            @Override
	            protected void postProcessContext(Context context) {
	                SecurityConstraint securityConstraint = new SecurityConstraint();
	                securityConstraint.setUserConstraint("CONFIDENTIAL");
	                SecurityCollection collection = new SecurityCollection();
	                collection.addPattern("/*");
	                securityConstraint.addCollection(collection);
	                context.addConstraint(securityConstraint);
	            }
	        };
	        tomcat.addAdditionalTomcatConnectors(createSslConnector());
	        return tomcat;
	    }

	    private Connector createSslConnector() {
	        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
	        connector.setScheme("http");
	        connector.setSecure(false);
	        connector.setPort(80);
	        connector.setRedirectPort(443);
	        return connector;
	    }
}

 

3) 브라우저 개발자도구에서 302 Redirect를 통한 https 접속 확인 ( URL : http → https ) 

 

4) Docker를 활용할 경우 아래와 같이 docker-compose.yml에 작성했다. 

* docker-compose.yml
ports:
      - 80:80
      - 443:443

 

 

 

 

 

+ 2024.02.25 내용 추가 : 

댓글 문의>

 

위 방법은 302 방식으로 redirect 하기 때문에 POST로 요청하더라도 redirect 과정에서 메소드가 GET으로 바뀐다는 단점이 있습니다.

 

최초 페이지 진입시에 GET 접근으로 한다고 가정하고 위같은 설정을 사용했는데, 

POST 메소드를 유지해야하는 경우 아래 2가지 방법이 있습니다.

 

1. nginx와 같은 웹서버를 앞단에 둬서 307 / 308 을 통한 redirect 처리 (GET 메소드 유지 가능)

 

2. 위 방법이 아닌 servlet 필터를 이용해 307을 명시해 redirect

    - 현재는 스프링에서 제공하는 메소드로는 리다이렉션 코드를 명시하는 메소드를 찾을수가 없어 servlet filter에서 아래처럼 정의하면 될 것 같습니다.

 

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HttpsRedirectFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        if (!httpRequest.isSecure()) {
            String location = "https://" + httpRequest.getServerName() + httpRequest.getRequestURI();
            if (httpRequest.getQueryString() != null) {
                location += "?" + httpRequest.getQueryString();
            }
            httpResponse.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT); // 307
            httpResponse.setHeader("Location", location);
            return;
        }

        chain.doFilter(request, response);
    }

    // init과 destroy 메소드는 필터 초기화와 정리를 위해 구현될 수 있습니다.
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 필터 초기화 코드
    }

    @Override
    public void destroy() {
        // 필터 정리 코드
    }
}

 

 

반응형