One way to do this is to create an HTTP filter inside your application:
@Component @ConfigurationProperties("security.http") public class ForceHTTPSFilter implements Filter { public static final String X_FORWARDED_PROTO_HEADER = "x-forwarded-proto"; private boolean forceHttps = false; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if(forceHttps && !request.getProtocol().toUpperCase().contains("HTTPS") && request instanceof HttpServletRequest) { Optional<String> protocol = Optional.ofNullable(((HttpServletRequest)request).getHeader(X_FORWARDED_PROTO_HEADER)); if(!protocol.orElse("http").equals("https")){ ((HttpServletResponse)response).sendError(HttpStatus.FORBIDDEN.value(), "Please use HTTPS when submitting data to this server."); return; } } chain.doFilter(request, response); } @Override public void destroy() { } public boolean isForceHttps() { return forceHttps; } public void setForceHttps(boolean forceHttps) { this.forceHttps = forceHttps; } }
You can enable / disable the filter using the @ConfigurationProperties property.
In addition, you should check the x-forwarded-proto header, because some proxies (like Heroku) remove the protocol from the URL and store it in that header.
And of course, here is the unit test of this filter:
public class ForceHTTPSFilterTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); @InjectMocks private ForceHTTPSFilter filter; @Test public void testAcceptHTTPRequestWhenFlagIsDisabled() throws Exception{ HttpServletRequest request = mock(HttpServletRequest.class); when(request.getProtocol()).thenReturn("HTTP/1.1"); HttpServletResponse response = mock(HttpServletResponse.class); FilterChain chain = mock(FilterChain.class); filter.doFilter(request, response, chain); verify(chain, times(1)).doFilter(any(), any()); verify(response, never()).sendError(eq(403), anyString()); } @Test public void testAcceptHTTPRequestWhenFlagIsEnableAndItHasForwardedProtoHeader() throws Exception{ HttpServletRequest request = mock(HttpServletRequest.class); when(request.getProtocol()).thenReturn("HTTP/1.1"); when(request.getHeader(ForceHTTPSFilter.X_FORWARDED_PROTO_HEADER)).thenReturn("https"); HttpServletResponse response = mock(HttpServletResponse.class); filter.setForceHttps(true); FilterChain chain = mock(FilterChain.class); filter.doFilter(request, response, chain); verify(chain, times(1)).doFilter(any(), any()); verify(response, never()).sendError(eq(403), anyString()); } @Test public void testAcceptHTTPSRequest() throws Exception{ HttpServletRequest request = mock(HttpServletRequest.class); when(request.getProtocol()).thenReturn("HTTPS/1.1"); HttpServletResponse response = mock(HttpServletResponse.class); filter.setForceHttps(true); FilterChain chain = mock(FilterChain.class); filter.doFilter(request, response, chain); verify(chain, times(1)).doFilter(any(), any()); verify(response, never()).sendError(eq(403), anyString()); } @Test public void testRejectHTTPRequestWhenFlagIsEnableAndItDoesntHaveForwardedProtoHeader() throws Exception{ HttpServletRequest request = mock(HttpServletRequest.class); when(request.getProtocol()).thenReturn("HTTP/1.1"); HttpServletResponse response = mock(HttpServletResponse.class); filter.setForceHttps(true); FilterChain chain = mock(FilterChain.class); filter.doFilter(request, response, chain); verify(chain, never()).doFilter(any(), any()); verify(response, times(1)).sendError(eq(403), anyString()); } }
jfcorugedo
source share