Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tomcat secured static content

I'm making a service that among other has the "photo albums" feature that serve photos to users. User has to be "allowed" to see the photo from the album. So sending the direct link to other person shouldn't allow to view photo.

Photos are stored in the folder outside of the context.

What I need to do is to perform some checks when user requests the photo and then if checks are OK - serve the file. I want to avoid making a wheel and just let tomcat serve the image as it usually does for static files. Can you give some advice on that?

like image 727
Juriy Avatar asked Jun 02 '10 13:06

Juriy


People also ask

Can Tomcat serve static files?

Tomcat will serve any static content from a WAR file using the DefaultServlet.

Where do I put static files in Tomcat?

css , which should serve the file at /var/lib/tomcat6/webapps/ROOT/extjs/welcome/css/welcome. css , return a 404 . Tomcat serves static files only at top-level directory.

In which file in Tomcat we can enable the security option?

The SecurityManager is normally controlled by a file called "java. policy," which is distributed with the SDK. Tomcat uses the file $CATALINA_BASE/conf/catalina.


1 Answers

Ok, guys.

After struggling hard with this question I think I've finally found out what to do to solve it. First of all it looks like the question actually decomposes into two independent tasks. One of them is securing access to some resources and second one is feeding resources from the folder outside of the context.

First task is trivial and can be solved by writing a simple filter hanged to "/".

Second task is much less trivial but fortunately also can be resolved. Tomcat uses the implementation of javax.naming.directory.DirContext to load all resources of the given web application including class files. It also allows you to provide the custom implementation of this interface and configure it in the context.xml file. The default implementation is org.apache.naming.resources.FileDirContext. Details here: http://tomcat.apache.org/tomcat-6.0-doc/config/resources.html

I've created my own implementation of DirContext by simply extending FileDirContext. Luckily enough there was a single method that had to be overwritten in order to "hook up" file discovery. The method is called file().

I'm posting my test code here. It is far from perfect and does not take into account the corner cases like renaming files but I don't think that these are needed under a normal run of the server.

The basic idea under this code is to check if the path starts with "virtual directory" prefix and if it is - search for file in the other place in the filesystem (I know there is some duplicate code there but I hope you're not that lazy to remove it if you ever want to use it :-). setVirtualName and setVirtualBase are called automatically to inject the configuration params.

/**
 * TODO: add javadocs
 *
 * @author Juriy Bura
 */
public class VirtualFolderDirContext extends FileDirContext {
    private String virtualName;
    private String realName;

    private File virtualBase;
    private String absoluteVirtualBase;


    public VirtualFolderDirContext() {
        super();
    }

    public VirtualFolderDirContext(Hashtable env) {
        super(env);
    }

    public void setVirtualName(String path) {
        virtualName = path;
    }

    public void setVirtualBase(String base) {
        this.realName = base;
        virtualBase = new File(realName);
        try {
            virtualBase = virtualBase.getCanonicalFile();
        } catch (IOException e) {
            // Ignore
        }
        this.absoluteVirtualBase = virtualBase.getAbsolutePath();
    }

    protected File file(String name) {
        File file = null;
        boolean virtualFile = name.startsWith(virtualName + "/");
        if (virtualFile) {
            file = new File(virtualBase, name.substring(virtualName.length()));
        } else {
            file = new File(base, name);
        }

        if (file.exists() && file.canRead()) {

            if (allowLinking)
                return file;

            // Check that this file belongs to our root path
            String canPath = null;
            try {
                canPath = file.getCanonicalPath();
            } catch (IOException e) {
            }
            if (canPath == null)
                return null;

            // Check to see if going outside of the web application root
            if (!canPath.startsWith(absoluteBase) && !canPath.startsWith(absoluteVirtualBase)) {
                return null;
            }

            // Case sensitivity check
            if (caseSensitive) {
                String fileAbsPath = file.getAbsolutePath();
                if (fileAbsPath.endsWith("."))
                    fileAbsPath = fileAbsPath + "/";
                String absPath = normalize(fileAbsPath);
                if (canPath != null)
                    canPath = normalize(canPath);
                if (virtualFile) {
                    if ((absoluteVirtualBase.length() < absPath.length())
                        && (absoluteVirtualBase.length() < canPath.length())) {
                        absPath = absPath.substring(absoluteVirtualBase.length() + 1);
                        if ((canPath == null) || (absPath == null))
                            return null;
                        if (absPath.equals(""))
                            absPath = "/";
                        canPath = canPath.substring(absoluteVirtualBase.length() + 1);
                        if (canPath.equals(""))
                            canPath = "/";
                        if (!canPath.equals(absPath))
                            return null;
                    }                   
                } else {
                    if ((absoluteBase.length() < absPath.length())
                        && (absoluteBase.length() < canPath.length())) {
                        absPath = absPath.substring(absoluteBase.length() + 1);
                        if ((canPath == null) || (absPath == null))
                            return null;
                        if (absPath.equals(""))
                            absPath = "/";
                        canPath = canPath.substring(absoluteBase.length() + 1);
                        if (canPath.equals(""))
                            canPath = "/";
                        if (!canPath.equals(absPath))
                            return null;
                    }
                }
            }

        } else {
            return null;
        }
        return file;

    }
}

After you have this class in place you have to jar it and put that jar into the Tomcat lib folder. For obvious reasons it cannot go together with war file. In your context.xml you should add a config lines like these:

<?xml version="1.0" encoding="UTF-8"?>
<Context antiResourceLocking="true" antiJARLocking="true">

    <Resources
            className="com.juriy.tomcat.virtualdir.VirtualFolderDirContext"
            virtualName="/upload"
            virtualBase="c:/temp/up">
    </Resources>
    ...
    ...

Now any time user asks for /upload/ it will be resolved to c:\temp. With this technique you can implement loading resources from virtually any location: http, shared folder, database, even version control system. So it is pretty cool.

P.S. I've killed the whole day to make this all work together so don't hesitate to give me your vote if you like the answer :-))

Cheers

Juriy

like image 117
Juriy Avatar answered Oct 13 '22 01:10

Juriy