I couldn't really get specific with a problem, but SOMETIMES, literally from time to time when i start my jetty server, my login servlet gets GET request twice.
When it happens, login page also doesn't get the css because of this
"Resource interpreted as Stylesheet but transferred with MIME type
text/html".
What i know is that when i disable my filter servlet, everything works fine.
Here's how i add stuff into server:
UserService userService = new UserService();
CharactersService charactersService = new CharactersService();
SessionsService sessionsService = new SessionsService();
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.addServlet(new ServletHolder(new LoginServlet(userService, charactersService, sessionsService)), "/login");
context.addServlet(new ServletHolder(new MainMenuServlet(sessionsService)), "/main");
context.addServlet(new ServletHolder(new DuelServlet(userService)), "/duel");
context.addServlet(new ServletHolder(new FightServlet(charactersService, sessionsService, userService)),"/duel/fight");
context.setContextPath("/");
context.setBaseResource(Resource.newResource("src/main/resources"));
server.setHandler(context);
ServletHolder defaultHolder = new ServletHolder("default", new DefaultServlet());
defaultHolder.setInitParameter("dirAllowed", "true");
context.addServlet(defaultHolder, "/");
context.addFilter(new FilterHolder(new AuthenticationFilter()), "/*", EnumSet.of(DispatcherType.REQUEST));
server.start();
server.join();
If i start my server and see that login page get's called twice and doesn't load css, i comment line with addFilter() method, start server, shut it down, uncomment, start it again and login pages loads just fine, which is quite weird.
Is there something wrong with how i add all the stuff to server, or what may even cause such behavior?
Filter servlet code:
public class AuthenticationFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String uri = req.getRequestURI();
HttpSession session = req.getSession(false);
if (session == null && !uri.equals("/login")) {
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
resp.sendRedirect("/login");
} else if (session != null && uri.equals("/login")) {
resp.setStatus(HttpServletResponse.SC_OK);
resp.sendRedirect("/main");
} else {
chain.doFilter(request, response);
}
}
#Override
public void destroy() {}
}
I think you are not writing logic of Server creation in a static context (static method/block).
Hence, for each request a new server is created. Which results in 2 requests.
Here is an example of creating a server in static method.
Related
My goal is to log the incoming http requests to my Spring (5.0.7) MVC Web / Spring security (4.2.3) application. I want to save the requestdata in a database, containing IP, request method, headers, body and the URL. The critical requests are the login attempts so I need to fetch the POST request to the /login URL.
Therefore I wrote a filter to get this done because an interceptor is applied after the filter chain.
I looked at the solution at this SO question and I also tried the variant with an interceptor.
WebAppInitializer
public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
...
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
...
filterRegistration = servletContext.addFilter("logFilter", new APILoggingFilter() );
String[] mappings = new String[] {"/login", "/logout", "/data"};
filterRegistration.addMappingForUrlPatterns(null, false, mappings);
}
}
LoggingFilter
public class APILoggingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
boolean isFirstRequest = !isAsyncDispatch(request);
HttpServletRequest requestToUse = request;
HttpServletResponse responseToUse = response;
if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
requestToUse = new ContentCachingRequestWrapper(request);
}
if (isFirstRequest && !(response instanceof ContentCachingResponseWrapper)) {
responseToUse = new ContentCachingResponseWrapper(response);
}
filterChain.doFilter(requestToUse, responseToUse);
String user = SecurityContextHolder.getContext().getAuthentication().getName();
// This is were the logging to the database should take place
if (!isAsyncStarted(request)) {
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(responseToUse, ContentCachingResponseWrapper.class);
responseWrapper.copyBodyToResponse();
}
}
#Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
}
log4j.properties
log4j.logger.org.springframework=INFO
log4j.logger.org.springframework.web.filter=DEBUG
With this code I am able to log all request to the database with almost all the data I wanted. I see GET requests and POST requests.
The problem or question is: why is it not possible to see the username? I tried to get the username via
request.getRemoteUser();
and with
String user = SecurityContextHolder.getContext().getAuthentication().getName();
It is always null. And here is the curious thing. If I disable the second entry in my log4j.properties (log4j.logger.org.springframework.web.filter=DEBUG) then I always get the username with both options BUT I never fetch a POST request anymore only GET requests.
How do I achieve both goals? Fetch all requests AND get the username?
I have embedded Jetty server and I added servlet mapping.
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(RegisterServlet.class, "/user/register");
I want to make the Dependency Injection in servlet with spring framework configuring ApplicationContext.xml. It should work the same as here:
public class RegisterServlet extends HttpServlet {
private Service service;
#Override
public void init() throws ServletException {
super.init();
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
service = context.getBean("service", Service.class);
}
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
...
}
but without using context.getBean("service").
This way you can have the control of servlet instantiation
Server server = new Server(port);
ServletHandler handler = new ServletHandler();
handler.addServletWithMapping(new ServletHolder(new RegisterServlet()), "/user/register");
server.setHandler(handler);
server.start();
So now you can get the servlet instance from a DI container of something
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);
}
Simple instance of Embedded Tomcat with a Servlet and a Filter:
Tomcat tomcat = new Tomcat();
Context rootCtx = tomcat.addContext("", base.getAbsolutePath());
FilterDef filterDefinition = new FilterDef();
filterDefinition.setFilterName(URLFilter.class.getSimpleName());
filterDefinition.setFilterClass(URLFilter.class.getName());
rootCtx.addFilterDef(filterDefinition);
FilterMap filter1mapping = new FilterMap();
filter1mapping.setFilterName(URLFilter.class.getSimpleName());
filter1mapping.addURLPattern("/*");
rootCtx.addFilterMap(filter1mapping);
Tomcat.addServlet(rootCtx, "Servlet1", new Servlet1());
rootCtx.addServletMapping("/Servlet1", "Servlet1");
URL Filter Implementation:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Boolean filtered = false;
System.out.println("request intercepted");
if (request.getAttribute("filtered") != null) {
filtered = true;
request.setAttribute("filtered", filtered);
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendRedirect("/Servlet1");
return;
} else {
filterChain.doFilter(request, response);
}
}
For some reason this ends up in an infinite loop and never arrives at Servlet1. What is the correct way of implementing a URL Filter so that I can double check the parameters around the request before delivering the response?
Look at the JavaDocs for sendRedirect(). You're telling the client on each request to go back to /Servlet1, but then you're filtering it indefinitely. At some point you need to stop sending redirects in your filter!
To clarify some more. If you don't intend to actively filter a request, the only thing you need to do is call
filterChain.dofilter(request, response);
Don't send back a redirect unless you really mean to send an HTTP 302 (Temporary Redirect). The Servlet Filter Chain is an important part of the servlet process and although it may seem counterintuitive, you can make your filter appear to do nothing by calling the filterChain.doFilter command, which allows the request to proceed to other filters that are configured by your application server.
What's not clear to me is what filtering your filter is attempting to do. If you intend to filter by URL then you should look for matching URLs and then redirect only if there's a match.
As this solved your problem, posting it as an answer.
Firstly, you need to ensure that you're either passing the request along the application filter chain, or you're redirecting it:
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
Boolean filtered = false;
System.out.println("request intercepted");
if (!filtered) {
filtered = true;
((HttpServletResponse) response).sendRedirect("/Servlet1");
return;
}
filterChain.doFilter(request, response);
}
Then you need to ensure that the filter knows when an incoming request has already been redirected, so that it doesn't redirect it again:
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
Boolean filtered = false;
System.out.println("request intercepted");
if (request.getAttribute("filtered") != null) {
filtered = (Boolean) request.getAttribute("filtered");
}
if (!filtered) {
request.setAttribute("filtered", Boolean.TRUE);
((HttpServletResponse) response).sendRedirect("/Servlet1");
return;
}
filterChain.doFilter(request, response);
}
I have a filter which processes requests in order to log them, so I can keep track of which session hit a page at what time with what request parameters. works great... posting from jsp to jsp, or making a direct call to a jsp. When a form is posted to a servlet which forwards that request to a new jsp, however, I am unable to see which jsp the request was forwarded to.
For example, suppose I have a login page, which posts to a LoginServlet, which then forwards the request to either index.jsp or index1.jsp. How can I determine from the request whether LoginServlet is returning index.jsp or index1.jsp?
This is in a java 1.5 environment using the 2.3 servlet specification.
public class PageLogFilter implements Filter {
FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
if (request instanceof HttpServletRequest) {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession(false);
//For non-forwards, I can call req.getRequestURI() to determine which
//page was returned. For forwards, it returns me the URI of the
//servlet which processed the post. I'd like to also get the URI
//of the jsp to which the request was forwarded by the servlet, for
//example "index.jsp" or "index1.jsp"
}
} catch (Exception e {
System.out.println("-- ERROR IN PageLogFilter: " + e.getLocalizedMessage());
}
chain.doFilter(request, response);
}
}
If you are OK with performing an additional check then you can use attribute to set/get original request URI.
In your LoginServlet set the attribute:
//LoginServlet
public void doFilter(...) {
HttpServletRequest oReq = (HttpServletRequest)request;
..
...
//save original request URI
oReq.setAttribute("originalUri", oReq.getRequestURI());
}
and in your PageLogFilter check if originalUri attribute has value then consider this value as the request URI
//PageLogFilter
public void doFilter(...) {
HttpServletRequest req = (HttpServletRequest) request;
if(req.getAttribute("originalUri") != null) {
String strOriginalUri = (String) req.getAttribute("originalUri");
//set it back to null
req.setAttribute("originalUri", null);
}
}
Although it wont help solve your immediate problem, Servlet 2.4 added further detailed control on the dispatcher, which is what you want.
With it, you can configure the filter to add the following dispatcher element, which would cause the filter to also apply to forwards.
<filter-mapping>
<filter-name>PageLogFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
The following article explains it in more detail
http://www.ibm.com/developerworks/java/library/j-tomcat2/