I'm unable to access Spring Security information during a servlet multipart post. The spring security information is available during regular get and post methods, but is not available for a multipart post method. I tried unsuccessfully to access this security information directly through SecurityContextHolder.getContext().getAuthentication() and through an injected service that accesses SecurityContextHolder.getContext().getAuthentication().
I also implemented an HttpRequestHandler and a ServletWrappingController. Once again, I was able to successuly inject spring beans into them and access Spring Security info for regular get and post methods, but I was not able access Spring Security info for a multipart posts. I know that there are new MultiPart capabilities built into Spring 3.0 but because our website will require full access to the file upload stream I won't be able to use them. For that reason, I am focusing on the HttpServlet, HttpRequestHandler and the ServletWrappingController.
The code I'm posting here is all test code written to solve this specific problem I'm facing with security information not being available during a multipart upload (not meant to be of production quality). It is for an HttpServlet.
Please let me know if there's something I'm doing wrong. Or if not, if there's a workaround or a better way to accomplish a multipart upload with access to Spring Security info while maintaining access to the file upload stream? Any assistance that someone can offer with this problem will be greatly appreciated!
Below is the test servlet code. Comments below as to what works and what doesn't is based on a user logged in to the website using Spring Security 3.1:
//many import statements not displayed
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
public class UploadServlet extends HttpServlet {
public void service(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
super.service(req, res);
}
public void init(ServletConfig config) throws ServletException {
super.init(config);
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
config.getServletContext());
}
//The following is always injected and available
//however, it only returns valid security information for regular get and post methods,
//not for multipart post methods
#Autowired
private CustomUserService customUserService;
//The following is always injected and available and always returns the expected data
#Autowired
private GuideService guideService;
//the following does not work when the client issues a multipart post, it does work for non-multipart
public boolean getAuthenticated(){
boolean authorized = false;
for (GrantedAuthority authority : SecurityContextHolder.getContext().getAuthentication().getAuthorities()) {
if(authority.getAuthority().equals("ROLE_USER") || authority.getAuthority().equals("ROLE_ADMIN")) {
authorized = true;
break;
}
}
return authorized;
}
//The following test get method works fine
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(getAuthenticated()){
PrintWriter out = resp.getWriter();
out.write("<h1>Guide Info</h1><br/>");
Guide guide = guideService.findById(2l);
out.write(guide.getName() + "<br/>");
out.write(guide.getDescription() + "<br/>");
out.write("UserName: " + customUserService.getCurrentUser().getUsername() + "<br/>");
}
else{
PrintWriter out = resp.getWriter();
out.write("<h1>You're not authorized</h1><br/>");
}
}
//This post method
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//the following always works, whether the clients posts using multipart or not
String guideName = guideService.findById(2l).getName();
//the following does not work when the client issues a multipart post, it does work for non-multipart
String userName = customUserService.getCurrentUser().getUsername();
//the following does not work when the client issues a multipart post, it does work for non-multipart
if(getAuthenticated()){
String responseString = RESP_SUCCESS;
boolean isMultipart = ServletFileUpload.isMultipartContent(req);
if (isMultipart) {
ServletFileUpload upload = new ServletFileUpload();
//commmons fileupload code
// Not a multi-part MIME request.
else {
//...
}
//...
}
else{
//...
}
}
}
Here is the relevant portion of web.xml:
<servlet>
<servlet-name>fgm</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/spring/webmvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>fgm</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>com.guides.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
I can confirm that Spring 3.0.x and Spring Security 3.0.x together work with multipart posts as well as they work with other types of requests. I've run into similar behavior, and in our case, the security filter wasn't getting applied to the request due to our mistake in the filter mappings.
Can you post the parts of your web.xml that define the security filter, and map it to the desired paths?
This might help you, if you're using Spring MVC:
{
#RequestMapping(method = RequestMethod.POST, value = "/some/post/url")
public void postFile(MultipartHttpServletRequest request) {
MultipartFile multipartFile = request.getFileMap().get("fileControlName");
...
}
}
Security details as provided by SecurityContextHolder are (by default) stored in a ThreadLocal.
Does upload servlet creates a new thread to handle multiparts ? Try changing the SecurityContextHolderStrategy to MODE_INHERITABLETHREADLOCAL
Similar issues: How to set up Spring Security SecurityContextHolder strategy?
It might be worth checking how your client is performing the multi-part post, are you using a different mechanism/library to your standard post?
If I had to guess I would say your client code isn't authenticating correctly for the multi-part use-case.
E.g. Using standard Java for the normal post and Apache libs for the multipart post and forgetting to set the appropriate http headers when using the Apache stuff.
Related
How can i create a full web application with Java and React without having to create a rest API, not even a private API with username:password authentication.
I want it to be as it is created with JSP.
Is it possible call Java methods with react locally ?
Or even creating a restfull API that can only be called locally
Thank you
I don't think it's possible to communicate with Java in a client library such as React without having to create a HTTP API.
But you could make one and add a bit of extra layer of security to ensure that only your application could call your Java API by checking the remote address of each call and verifying that's the caller is indeed your server.
You can do this in Java using the getRemoteAddr() method from the HttpServletRequest object.
The best way to do this is to create a filter class that map all the API links and verify the remote address in each calls and then decide if it should allow it or not.
Here's an example:
import javax.servlet.*;
public class RequestFilter implements Filter{
public void init(FilterConfig config) throws ServletException {}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
String callerIp = request.getRemoteAddr();
if(callerIp.equalsIgnoreCase("MY-SERVER-IP-ADDRESS")) {
chain.doFilter(req, res);
}
else {
((HttpServletResponse)res).sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied !");
return;
}
}
public void destroy() {}
}
Replace "MY-SERVER-IP-ADDRESS" with your server ip.
And to map all the calls, set the filter tag in your web.xml as follows:
<filter>
<filter-name>RequestFilter</filter-name>
<filter-class>com.myPackage.requestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RequestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
This should satisfy your need, but if you found another way please share it with us.
the first request is no, as far as I'm aware. But as for the local rest api, I know most web servers can check CORS headers and restrict to only serve certain origins on answering requests. So whichever JRE Web Server you're using, check it's API for accessing the origin in the request header, and route those to the rest code.
I am wondering if it is possible to dispatch a request from a servlet to a Jersey (JAX-RS implementation) resource class. I am trying to do it but it doesn't seem to work and according to my logging, the jersey resource is never reached.
Code examples are below. Is what I'm trying to do impossible for some reason?
Please note, the Jersey resource works correctly when I access it directly in a web browser via the address bar.
Also please note that 'RequestDispatcher.forward()' works as expected. It is just 'include' that doesn't.
The servlet
//The Jersey dispatcher url-filter is set to '/api/*'
String servletID = "/api/items";
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(servletID);
dispatcher.include(request, response);
The Jersey resource
#GET #Path("/items")
#Produces ({MediaType.TEXT_XML})
public JAXBElement<Items> getItems(#PathParam("project") String project) throws IOException, JAXBException {
log.debug("reached getItems");
//Omitted code that returns 'Items' object wrapped in JAXBElement
}
Relevant parts of web.xml
<servlet>
<servlet-name>jerseyDispatcher</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>uk.co.web.api.resource</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>jerseyDispatcher</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
It is possible you forward the request.
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
RequestDispatcher requestDispatcher = null;
requestDispatcher = httpServletRequest.getRequestDispatcher("/items");
dispatcher.forward(request, response);
return;
However note if you receive a GET request and try to forward to a POST resource,
It will throw a 405 error.
Edit:
Let me understand what you are trying to achieve, if you need write content to response output stream you could use jersey Resource Filter.
public class YourResourceFilter implements ResourceFilter
{
public ContainerRequestFilter getRequestFilter()
{
return new ContainerRequestFilter()
{
#Override
public ContainerRequest filter(ContainerRequest containerRequest)
{
//Pre- editing the request
return containerRequest;
}
};
}
#Override
public ContainerResponseFilter getResponseFilter()
{
return new ContainerResponseFilter()
{
#Override
public ContainerResponse filter(ContainerRequest containerRequest, ContainerResponse containerResponse)
{
// after the request has been completed by your jersey resource
return containerResponse;
}
};
}
}
I got it to work, sort of (Jersey 2.13) by configuring Jersey as a filter, and not a servlet, in web.xml. Then, you can tell the container to apply the filter to included requests too:
<filter-mapping>
<filter-name>Jersey Web Application</filter-name>
<url-pattern>/api/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
RequestDispatcher.include will then work for request handled by Jersey, too. There's a caveat, though. Jersey calls response.getOutputStream, so all output must be performed through said output stream - this rules out JSP pages, that use response.getWriter instead. So, unless you figure out how to work around the problem, forget about including a Jersey resource in a JSP page or, vice versa, including the result of evaluating a JSP as part of a REST response.
I have just finished writing a filter to gzip my responses with amazing results. My largest JSP page has gone from 80K to 5.7K. However, the filter I've written gzip's all responses from the server including images. However, Yahoo says this is a very bad practice.
Image and PDF files should not be gzipped because they are already compressed.
Trying to gzip them not only wastes CPU but can potentially increase file sizes.
Here is my web.xml footprint of the filter I've written
<!-- THIS FILTER WILL TAKE MY OUTPUT AND GZIP IT TO LESSEN MY BANDWIDTH FOOTPRINT -->
<filter>
<filter-name>GZip</filter-name>
<filter-class>org.instride.gzip.GZIPFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GZip</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
So as you can see everything that is a response get's gzipped. However, I only want to gzip css js and jsp pages. So you'd think I could just add these guys
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.css</url-pattern>
<url-pattern>*.js</url-pattern>
But that would give me an issue b/c all of my jsp pages hide extensions b/c they are servlets, and I also use tuckey's URL rewrite. So for example the URL of a page I'd want gzipped looks like this.
https://example.com/user/1/admin
So the servlet (user) doesen't have an extension to pinpoint in the web.xml file, and the servlet is followed by forward slash parameters. So I'm thinking of two possible solutions.
Instead of including file types I'd like gziped I include file types I want skipped like jpeg or gif.
I specify to only gzip files based on their content type like text/html;charset=UTF-8 for example.
Any guidance on how I could do either of those things, or a much better solution to my problem would be greatly appreciated. Thank you all for reading this.
I went off of the idea I had from number two and wrote this solution. In my solution I look at the accept attribute within the request header. From what I've gathered looking at firebug all of the images on my site no matter what their format have an accept attribute of
image/png,image/*;q=0.8,*/*;q=0.5
I honestly have no idea what that means exactly but all jpg's png's and gif's have it and no other file types do so this is what I did.
...
public class GZIPFilter implements Filter
{
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
{
if (servletRequest instanceof HttpServletRequest)
{
HttpServletRequest httpRequest;
HttpServletResponse httpResponse;
String acceptedEncoding, accept;
GZIPResponseWrapper wrappedResponse;
httpRequest = (HttpServletRequest) servletRequest;
httpResponse = (HttpServletResponse) servletResponse;
accept = httpRequest.getHeader("accept");
acceptedEncoding = httpRequest.getHeader("accept-encoding");
if (acceptedEncoding != null && acceptedEncoding.indexOf("gzip") != -1 && !accept.equals("image/png,image/*;q=0.8,*/*;q=0.5"))
{
wrappedResponse = new GZIPResponseWrapper(httpResponse);
filterChain.doFilter(servletRequest, wrappedResponse);
wrappedResponse.finishResponse();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
public void init(FilterConfig filterConfig)
{
}
public void destroy()
{
}
}
I have an apache webserver that is used to serve php and static web files. In order to use active directory authentication i've written some code that can connect to AD through JNDI and authenticate usernames passwords and groups. What I would like is to map all requests to pages in apache through my servlet in order to make sure that a valid session is present and then if they have to login again that they have the correct AD group to visit a particular url. My issue is that when I map my servlet to every url with /* it cannot forward requests to the actual pages that I'm trying to get. It just keeps forwarding the request to my servlet and calling its doGet method till a servlet exception occurs. I want the functionality of a transparent proxy but I cannot seem to get that from this. Does anyone have any concrete examples of a transparent proxy servlet or know a way to do this with servlets. The forwarding functionality of a servlet seems to make this a perfect vehicle for doing it but I seem to be stuck.
Filter code
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
boolean authenticated = false; //should be false when testing is done.
//skip if its the login page
if(req.getRequestURI().equals("/auth/login.jsp") || authenticated){
chain.doFilter(req, res);
}else{
req.setAttribute("protectedUrl", req.getRequestURI());
res.sendRedirect("/auth/login.jsp");
}
}
Web.xml
(snip)
<filter-mapping>
<filter-name>SessionFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
Because the servlet is mapped on /*, the RequestDispatcher#forward() will call it again, resulting in an infinite loop and finally a StackOverflowError (or some other exception depending on the servletcontainer in question which might have some recursion prevention builtin which kicks in after a certain amount of recursive calls).
After all, the Servlet is not entirely the right tool for the job, you'd rather like to use a Filter here. Implement javax.servlet.Filter and do the same job in doFilter() method. It won't call itself recursively when mapped on /* since it by default listens on requests only, not on forwards or includes.
Seems like a stupid question to which the answer would be "Don't use encodeURL()!" but I'm working with a codebase that uses netui anchor tags in the JSPs and I need to disable the writing of JSESSIONID into the URLs as it is a security risk.
In WebLogic, you can configure this by configuring url-rewriting-enabled in weblogic.xml (I know because I wrote that feature in the WebLogic server!). However, I can't find an equivalent config option for Tomcat.
Tomcat 6 supports the disableURLRewriting attribute that can be set to true in your Context element:
http://tomcat.apache.org/tomcat-6.0-doc/config/context.html#Common_Attributes
No setting comes to mind. But this is fairly easy to do by creating a first-entry Filter listening on the url-pattern of interest (maybe /* ?) and replaces the ServletResponse by a HttpServletResponseWrapper implementation where the encodeURL() returns the very same argument unmodified back.
Kickoff example:
public void doFilter(ServletRequest request, ServletResponse response) throws ServletException, IOException {
chain.doFilter(request, new HttpServletResponseWrapper((HttpServletResponse) response) {
public String encodeURL(String url) {
return url;
}
});
}
As found in https://fralef.me/tomcat-disable-jsessionid-in-url.html - There is a servlet spec feature to do this
<session-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>