简体   繁体   English

Java Spring如何在webapplicationinitializer中强制使用https ssl?

[英]Java Spring how to force https ssl in webapplicationinitializer?

To force https in web.xml i was using this code snippet: 要在web.xml中强制使用https,我正在使用以下代码段:

<security-constraint>
  <web-resource-collection>
      <url-pattern>/*</url-pattern>
  </web-resource-collection>
  <user-data-constraint>
      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
  </user-data-constraint>
</security-constraint>

Is there an equivalent for this in Spring Java Config? 在Spring Java Config中有与此等效的功能吗? I already figured out that i need a ServletSecurityElement . 我已经弄清楚我需要一个ServletSecurityElement But how do i connect it to the rest? 但是我如何连接到其余部分?

public class WebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext container) throws ServletException {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        container.addListener(new ContextLoaderListener(context));
        context.register(PersistenceJPAConfig.class);

        FilterRegistration filter = container.addFilter("wicket.myproject", WicketFilter.class);
        filter.setInitParameter("applicationClassName", WicketApplication.class.getName());
        filter.setInitParameter(WicketFilter.FILTER_MAPPING_PARAM, "/*");
        filter.addMappingForUrlPatterns(null, false, "/*");

        HttpConstraintElement forceHttpsConstraint = new HttpConstraintElement(ServletSecurity.TransportGuarantee.CONFIDENTIAL, "");
        ServletSecurityElement securityElement = new ServletSecurityElement(forceHttpsConstraint);
    }
}

As John Thompson pointed out you were right there. 正如约翰·汤普森(John Thompson)指出的那样,您就在那里。 You just needed to add the security element you defined to the servlet. 您只需要将定义的安全性元素添加到servlet。 On another note I noticed you had "" as the roleNames parameter to the HttpConstraintElement . 在另一个注释上,我注意到您在HttpConstraintElement的roleNames参数中使用了"" This would actually cause everyone who didn't have the role "" to be denied. 实际上,这将导致所有不具有角色""人被拒绝。 If you want this to work like normal (force https) don't give any roles. 如果您希望此功能像正常情况一样(强制使用https),请不要设置任何角色。 In the end this worked for me: 最后,这对我有用:

public class ApplicationInitializer implements WebApplicationInitializer {

    private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
    private static final String DISPATCHER_SERVLET_MAPPING = "/";

    @Override
    public void onStartup(ServletContext container) throws ServletException {
        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(ApplicationConfiguration.class);

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher = container.addServlet(DISPATCHER_SERVLET_NAME, new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);

        // Force HTTPS, and don't specify any roles for this constraint
        HttpConstraintElement forceHttpsConstraint = new HttpConstraintElement(ServletSecurity.TransportGuarantee.CONFIDENTIAL);
        ServletSecurityElement securityElement = new ServletSecurityElement(forceHttpsConstraint);

        // Add the security element to the servlet
        dispatcher.setServletSecurity(securityElement);
    }
}

I think you need to get a handle on the servlet registration, then register the security element. 我认为您需要获取有关servlet注册的句柄,然后注册安全元素。 Try something like this: 尝试这样的事情:

ServletRegistration.Dynamic registration 
     = container.addServlet("dispatcher", new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.setServletSecurity(securityElement); //your prev defined securityElement

In the case if you use Spring Security 3.2 you could do this as follows. 如果您使用的是Spring Security 3.2,则可以按照以下步骤进行操作。

<security:intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>

with http to https port mappings as well. 以及http到https端口的映射。

<security:port-mappings>
            <security:port-mapping http="${port.mapping.http.port}"
                https="${port.mapping.https.port}" />
private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
private static final String DISPATCHER_SERVLET_MAPPING = "/";    
@Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
            rootContext.register(ApplicationContext.class);

            ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME,
                    new DispatcherServlet(rootContext));
            dispatcher.setLoadOnStartup(1);
            dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);



             HttpConstraintElement forceHttpsConstraint = new HttpConstraintElement(TransportGuarantee.CONFIDENTIAL);
                ServletSecurityElement securityElement = new ServletSecurityElement(forceHttpsConstraint);
               dispatcher.setServletSecurity(securityElement);


        }

What do you mean, connect it to the rest? 您是什么意思,将其连接到其余部分? Looks like you should be set. 看起来您应该被设置。 Spring will auto-detect the configuration of the Java configured WebApplicationInitializer. Spring将自动检测Java配置的WebApplicationInitializer的配置。

Remember that WebApplicationInitializer implementations are detected automatically -- so you are free to package them within your application as you see fit. 请记住,WebApplicationInitializer实现是自动检测到的,因此您可以随意将其打包到应用程序中。

See: 看到:

http://docs.spring.io/spring-framework/docs/3.1.x/javadoc-api/org/springframework/web/WebApplicationInitializer.html#onStartup(javax.servlet.ServletContext) http://docs.spring.io/spring-framework/docs/3.1.x/javadoc-api/org/springframework/web/WebApplicationInitializer.html#onStartup(javax.servlet.ServletContext)

One way of do this is creating an HTTP filter inside your application: 一种方法是在应用程序内部创建HTTP过滤器:

@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 switch on/off the filter with a property by using @ConfigurationProperties . 您可以使用@ConfigurationProperties使用属性打开/关闭过滤器。

Moreover, you should inspect the header x-forwarded-proto because some proxies (like Heroku) remove the protocol from the URL and store it into this header. 此外,您应该检查标题x-forwarded-proto因为某些代理(例如Heroku)会从URL中删除协议并将其存储在此标题中。

And, of course here's a 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());
    }
}

None of the above answer are good enough when using Spring Boot and External Tomcat. 当使用Spring Boot和External Tomcat时,以上答案都不够好。 Here the correct configuration: 这里是正确的配置:

super() must be called and the existing dispatcher servlet must be taken from the existing container. 必须调用super(),并且必须从现有容器中获取现有调度程序servlet。

private static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";

@Override
public void onStartup(ServletContext container) throws ServletException {
    super.onStartup(container);

    // Get the existing dispatcher servlet
    ServletRegistration.Dynamic dispatcher = (ServletRegistration.Dynamic)
                        container.getServletRegistration(DISPATCHER_SERVLET_NAME);

    // Force HTTPS, and don't specify any roles for this constraint
    HttpConstraintElement forceHttpsConstraint = 
         new HttpConstraintElement(ServletSecurity.TransportGuarantee.CONFIDENTIAL);
    ServletSecurityElement securityElement = 
         new ServletSecurityElement(forceHttpsConstraint);

    // Add the security element to the servlet
    dispatcher.setServletSecurity(securityElement);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM