The Source for Java Technology Collaboration
User: Password:



Jayson Falkner

Jayson Falkner's Blog

Blarg #23: Can't use the back button filter.

Posted by jfalkner on March 27, 2006 at 10:52 PM | Comments (2)

This is an example Filter that uses JavaScript to try and ensure that people can't click the back button to revisit pages on your website. It was originally encoded as an example during a Develop Mentor course I taught. It is a nice example of a servlet filter that buffers a response, locates the body tag using a simple regular expression, and inserts a call to 'history.forward()'. As with all my stuff, it is free for commercial and non-commercial use. If you like it, please remember to mention who originally wrote the code.

The code. Remember each filter has three parts: the Filter, ServletResponseWrapper, and ServletOutputStream sub-class.

CantGoBackFilter.java

This filter wraps the HttpServletResponse in order to buffer text sent out to a client.

package example.cgb;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class CantGoBackFilter implements Filter {
    ServletContext sc = null;
    
    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        // check that it is a HTTP request
        if (req instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            
            // nonce encode the normal output
            CantGoBackResponseWrapper wrappedResponse = new CantGoBackResponseWrapper(
                    response, sc);

            chain.doFilter(req, wrappedResponse);
            // finish the respone
            wrappedResponse.finishResponse();
        }
    }
    
    public void init(FilterConfig filterConfig) {
        // reference the context
        sc = filterConfig.getServletContext();
    }
    
    public void destroy() {
        // noop
    }
}

CantGoBackResponseWrapper.java

This wrapper sends back a custom ServletOutputStream object in order to buffer all text that is being sent to the client.

package example.cgb;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class CantGoBackResponseWrapper extends HttpServletResponseWrapper {
    protected HttpServletResponse origResponse = null;
    protected ServletOutputStream stream = null;
    protected PrintWriter writer = null;
    ServletContext sc;
    
    public CantGoBackResponseWrapper(HttpServletResponse response, ServletContext sc) {
        super(response);
        this.sc = sc;
        origResponse = response;
    }
    
    public ServletOutputStream createOutputStream() throws IOException {
        return (new CantGoBackResponseStream(origResponse, sc));
    }
    
    public void finishResponse() {
        try {
            if (writer != null) {
                writer.close();
            } else {
                if (stream != null) {
                    stream.close();
                }
            }
        } catch (IOException e) {}
    }
    
    public void flushBuffer() throws IOException {
        stream.flush();
    }
    
    public ServletOutputStream getOutputStream() throws IOException {
        if (writer != null) {
            throw new IllegalStateException("getWriter() has already been called!");
        }
        
        if (stream == null)
            stream = createOutputStream();
        return (stream);
    }
    
    public PrintWriter getWriter() throws IOException {
        if (writer != null) {
            return (writer);
        }
        
        if (stream != null) {
            throw new IllegalStateException("getOutputStream() has already been called!");
        }
        
        stream = createOutputStream();
        // BUG FIX 2003-12-01 Reuse content's encoding, don't assume UTF-8
        writer = new PrintWriter(new OutputStreamWriter(stream, origResponse.getCharacterEncoding()));
        return (writer);
    }
    
    public void setContentLength(int length) {}
}

CantGoBackResponseStream.java

This response stream buffers all text that is send to the client and uses a regular expression to locate the body tag and inject a JavaScript call to 'history.forward()', which fools most users in to thinking that they can't use the back button.

package example.cgb;

import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.*;
import javax.servlet.http.*;

/**
 *
 * @author Jayson Falkner - jayson@jspinsider.com
 */
public class CantGoBackResponseStream extends ServletOutputStream {
    // abstraction of the output stream used for compression
    protected OutputStream bufferedOutput = null;
    
    // state keeping variable for if close() has been called
    protected boolean closed = false;
    
    // reference to original response
    protected HttpServletResponse response = null;
    
    // reference to the output stream to the client's browser
    protected ServletOutputStream output = null;
    
    // default size of the in-memory buffer
    private int bufferSize = 50000;
    
    ServletContext sc;
    
    public CantGoBackResponseStream(HttpServletResponse response, ServletContext sc) throws IOException {
        super();
        closed = false;
        this.sc = sc;
        this.response = response;
        this.output = response.getOutputStream();
        bufferedOutput = new ByteArrayOutputStream();
    }
    
    public void close() throws IOException {
        // make up a nonce
        String nonce = Integer.toString((int)(Math.random()*Integer.MAX_VALUE));
        // set the nonce in app scope
        sc.setAttribute("nonce", nonce);
        
        // get the content
        ByteArrayOutputStream baos = (ByteArrayOutputStream) bufferedOutput;
        
        // make a string out of the response
        String pageText = new String(baos.toByteArray());
        
        // use regex to find the links
        Pattern p = Pattern.compile("]*");
        Matcher m = p.matcher(pageText);
        
        String newText = "";
        int offset = 0;
        while (m.find(offset)) {
            // update the text
            newText += pageText.substring(offset, m.start());
            // update the offset
            offset = m.end();
            // get the matching string
            String match = pageText.substring(m.start(), m.end());
            // replace the body tag
            newText += "


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • In what situations would you use this? Why would you want to hurt your users' experience like this?

    Posted by: keithkml on March 28, 2006 at 06:46 AM

  • I personally would hope to never use it; however, the case that came up during the course was how to ensure that a user can't browse away from your site then browse back without logging on. The obvious answer to this issue is that HTTP simply doesn't provide a method notifying you that a user has left the site. The student was not happy with this, and the normal tricks such as a suggested "logout" button or a really short session timeout were not considered adequate.

    In exploring exactly how to handle this problem we hacked out a few filters that explored exactly how well you could do in preventing a user from being able to use the back button to view cached content on the client-side. This was one of the few bits of new code we put together and that is why I posted it.

    Posted by: jfalkner on March 28, 2006 at 12:22 PM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds