Java Spring how to force https ssl in webapplicationinitializer? - java

To force https in web.xml i was using this code snippet:
<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? I already figured out that i need a 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. You just needed to add the security element you defined to the servlet. On another note I noticed you had "" as the roleNames parameter to the HttpConstraintElement. 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. 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. 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.
<security:intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
with http to https port mappings as well.
<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.
Remember that WebApplicationInitializer implementations are detected
automatically -- so you are free to package them within your
application as you see fit.
See:
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:
#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.
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.
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. Here the correct configuration:
super() must be called and the existing dispatcher servlet must be taken from the existing container.
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);
}

Related

Spring Security: redirect user based on request body

I have a scenario that based on the request body content, user should be allowed to access certain resource on SOAP services. I can't achieve this using antMatcher(**) because the path is same for all request.
I tried by adding a filter:
public class MyFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest r = (HttpServletRequest)request;
MyRequestWrapper req = new MyRequestWrapper(r);
String body = req.getBody();
if(body.indexOf("searchKeyOnBody")!=0){
//Need to check if user has specified role or not
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Set<String> roles = authentication.getAuthorities().stream()
.map(r -> r.getAuthority()).collect(Collectors.toSet());
boolean hasManagerRole = authentication.getAuthorities().stream()
.anyMatch(r -> r.getAuthority().equals("ROLE_MANAGER"));
if(!hasManagerRole){
throwUnauthorized(response);
return;
}
}
chain.doFilter(req, response);
}
In spring security config:
#Configuration
public class MyAppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.**.addFilterAfter(new MyFilter (), UsernamePasswordAuthenticationFilter.class)
The problem here is Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); in filter class is null. So, I am not able to retrive the user info and it's role.
Question:
Is there any way to retrieve the user info in the filter?
Anybody have better idea for this?

Java Spring boot - OnceRequestPerFilter allow only controller requestmappings

I'm currently implementing audit trail in my project, I tried using HandlerInterceptor and it seems it won't work in my project, so i looked for another way and I discovered that it's possible with OncePerRequestFilter.
Here's the code of my OncePerRequestFilter class:
#Component
#Order
public class LogFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String method = request.getMethod();
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String url = request.getRequestURL().toString();
// Log the info you need
// ...
filterChain.doFilter(request, response);
}
}
The only problem so far that I see with my current configuration of OncePerRequestFilter is it also includes the resources such as css / javascripts.
example these links will be also go to the filter:
http://localhost:8000/project/css/style.css
http://localhost:8000/project/3277a64fcca0dbde907d8684aed8f170.png
http://localhost:8000/project/js/script.js.map
What i want is to filter only the controller request mappings, and ignore the resources
example:
http://localhost:8000/project/accounts/client-users
http://localhost:8000/project/accounts
This code is a workaround to ignore resource file. not sure if it's the best practice tho.
#Component
#Order
public class LogFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String method = request.getMethod();
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String url = request.getRequestURL().toString();
filterChain.doFilter(request, response);
}
protected boolean shouldNotFilter(HttpServletRequest request)
throws ServletException {
String url = request.getRequestURL().toString();
return isResourceUrl(url);
}
private boolean isResourceUrl(String url) {
boolean isResourceUrl = false;
List<String> resourceRequests = Arrays.asList(
"/css/", "/js/", "/scss/", "/fonts/", "/emails/",
".css", ".js", ".scss", ".eot", ".svg", ".ttf", ".woff", ".otf", ".ico", ".png");
for (String resourceRequest : resourceRequests) {
if (url.contains(resourceRequest)) {
isResourceUrl = true;
}
}
return isResourceUrl;
}
}
Use something like this:
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/example/docs",
"/swagger-resources/**",
"/swagger-ui.html");
}

Call ContainerRequestFilter on Tomcat's own DefaultServlet

Following BalusC's instructions on that answer:
How to stream audio/video files such as MP3, MP4, AVI, etc using a Servlet
I added the following Context element to my Tomcat server.xml to make my media files available to Tomcat's own DefaultServlet.
<Context docBase="/home/jwi/media" path="/service/media" />
This works like charm and the media is available at:
http://localhost:8080/service/media/example.mp4
The ApplicationPath from my application (build on Jersey 2.x) is set to: #ApplicationPath("service").
Within that application I have a request filter that checks every incoming request for a valid user session.
#Provider
#PreMatching
#Priority(1)
public class SessionFilter implements ContainerRequestFilter {
#Context
private ServletContext _context;
#Context
private HttpServletRequest _request;
#Context
private HttpServletResponse _response;
public void filter(ContainerRequestContext requestContext) throws IOException {
HttpSession session = _request.getSession(false);
boolean isLoggedIn = session != null && session.getAttribute("username") != null;
boolean isLoginRequest = _request.getRequestURI().contains("login");
if (isLoggedIn || isLoginRequest) {
// Since filter chain is invoked by #Priority annotation here's nothing to do.
} else {
URI indexPage = UriBuilder.fromUri("/index.html").build();
requestContext.abortWith(Response.temporaryRedirect(indexPage).build());
}
}
}
My problem is, that filter is never called on the media elements. So when I open http://localhost:8080/service/media/example.mp4 the filter isn't called at all.
How do I add Tomcat's DefaultServlet to my request filter?
Have you considered a Servlet Filter instead?
#WebFilter("/service/media/*")
public class SessionFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
...
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}

Filter in Spring with Java configuration

I'm in a Spring project, and I need of filters.
Now I have already read about "Interceptor Vs Filter" and for now I choose filter.
So I have this class as filter
public class LogFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String ipAddress = request.getRemoteAddr();
this.log.info("IP " + ipAddress + ", Time " + new Date().toString());
chain.doFilter(req, res);
}
public void init(FilterConfig config) throws ServletException {
// Get init parameter
String testParam = config.getInitParameter("test-param");
this.log.info("Test Param:" + testParam);
}
public void destroy() {
// Add code to release any resource
}
}
And this method for registration of filter's bean in java config ( no xml configuration )
#Bean
public LogFilter filter() {
LogFilter filter = new LogFilter();
this.beanFactory.autowireBean(filter);
return filter;
}
Now this filter works for every url of my app, how can I choose which url should be "under filter" ?
EDIT
I solved this in this way
#Bean
public FilterRegistrationBean regFilter() {
FilterRegistrationBean regFilter = new FilterRegistrationBean();
regFilter.setFilter(new LogFilter());
regFilter.addUrlPatterns("/test");
return regFilter;
}
Thanks to the hint in comment, I found this
If you're using annotation to configure your web #WebFilter to annotate your Filter as per http://docs.oracle.com/javaee/6/tutorial/doc/bnagb.html.
Note that those annotation should be put in the LogFilter itself. And that it has no relation to spring bean or spring context. Those configuration are scanned and processed by the application server, e.g Tomcat, but Spring.

jhipster injection on servlet

I created a servlet, but I'm not able to do the injection of a repository object. Has anyone had this problem?
my config in WebConfigurer:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
log.info("Web application configuration, using profiles: {}", Arrays.toString(env.getActiveProfiles()));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
dispatcherServlet.register(WebMvcAutoConfiguration.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
my servlet:
#WebServlet(urlPatterns = "/servlet/preview")
public class ServletPreViewHtml extends HttpServlet {
private static final long serialVersionUID = 1L;
#Autowired
private PagecontentRepository pagecontentRepository;
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException{
doGet(request,response);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
Pagecontent p = pagecontentRepository.getOne(2l); //line 34
out.println(p.getFullHtml());
always returns me the following error:
java.lang.NullPointerException: null at
br.com.dmsolutions.netfarmacontentmanager.web.rest.ServletPreViewHtml.doGet(ServletPreViewHtml.java:34)
line of NPE: pagecontentRepository.getOne(2L);
I agree with Gaƫl Marziou, but you can also use #ServletComponentScan, part of Spring Boot 1.3.
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.3-Release-Notes#support-for-webservlet-webfilter-and-weblistener
Rather than using #WebServlet, I would rather use #Controller so that Spring can inject your repository.

Categories