避免在JSF网络应用程序中使用后退按钮

49

我展示了非常敏感的数据。在用户从我的服务器注销后,我不希望另一个用户能够通过浏览器的“返回”按钮查看数据。

我该如何做到这一点?

2个回答

83

默认情况下,浏览器的返回按钮根本不会向服务器发送HTTP请求。相反,它会从浏览器缓存中检索页面。这基本上是无害的,但确实会让最终用户感到困惑,因为他们错误地认为它真的来自服务器。

你所需要做的就是指示浏览器不要缓存受限页面。你可以使用一个简单的servlet过滤器来设置适当的响应头

@WebFilter
public class NoCacheFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        if (!request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) { // Skip JSF resources (CSS/JS/Images/etc)
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
            response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
            response.setDateHeader("Expires", 0); // Proxies.
        }

        chain.doFilter(req, res);
    }

    // ...
}

(请注意,此过滤器跳过JSF资源请求,其缓存实际上需要单独配置)

要使它在每个JSF请求上运行,请在过滤器类上设置以下注释,假设您的Web应用程序的web.xmlFacesServlet<servlet-name>值为facesServlet:

@WebFilter(servletNames={"facesServlet"})

或者,要使它仅在特定的URL模式上运行,例如与受限页面匹配的模式,例如/app/*/private/*/secured/*等,请在过滤器类上设置以下注释:
@WebFilter("/app/*")

你甚至可以在检查已登录用户的过滤器中完成完全相同的工作,如果你已经有一个。
如果你恰好使用JSF实用程序库OmniFaces,那么你也可以直接获取它的CacheControlFilter。这也透明地考虑了JSF资源。
另请参阅:

我已经尝试过这个 - 当我在调试器中运行它时,页面加载时它会停在noCacheFilter: doFilter方法内部。然后当我在Firefox中点击返回按钮(本地测试)时,它再次运行该函数。但最终显示的是上一页。我应该注意什么? - Jan
在Firefox中发生的另一个错误是,每隔三次左右点击“返回”按钮时,它会告诉你页面已过期。然后,当你点击错误页面提供的“重新加载”按钮时,它仍会加载上一页。 - Jan
1
@bluelabel:你要找的关键词是“自动填充”。另请参阅https://dev59.com/gWkw5IYBdhLWcg3wY5nq。 - BalusC
@BalusC,难道不应该在chain.doFilter(req, res)中传递“response”对象而不是“res”吗?因为它对我不起作用,我不知道为什么。 - Talib
1
@Talib:它们都指的是完全相同的对象。这是Java,一种面向对象的语言,而不是PHP或其他语言。如果对您不起作用,则可能是这些标头在稍后被覆盖,或旧页面仍然在浏览器缓存中,或者您只是错误地观察了返回按钮的行为(即它实际上确实与服务器连接,但反过来,服务器端代码中存在某些错误,使其看起来像是缓存)。在完全空白的Web应用程序中,上述答案有效。 - BalusC
显示剩余8条评论

10

我还发现了另一个好的解决方案。

在 faces-config.xml 中添加以下内容:

<lifecycle>
    <phase-listener id="nocache">client.security.CacheControlPhaseListener</phase-listener>
</lifecycle>

并且实现以下类:

package client.security;

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
public class CacheControlPhaseListener implements PhaseListener
{
    public PhaseId getPhaseId()
    {
        return PhaseId.RENDER_RESPONSE;
    }

    public void afterPhase(PhaseEvent event)        
    {
    }

    public void beforePhase(PhaseEvent event)
    {
       FacesContext facesContext = event.getFacesContext();
       HttpServletResponse response = (HttpServletResponse) facesContext
                .getExternalContext().getResponse();
       response.addHeader("Pragma", "no-cache");
       response.addHeader("Cache-Control", "no-cache");
       // Stronger according to blog comment below that references HTTP spec
       response.addHeader("Cache-Control", "no-store");
       response.addHeader("Cache-Control", "must-revalidate");
       // some date in the past
       response.addHeader("Expires", "Mon, 8 Aug 2006 10:00:00 GMT");
    }
} 

8
虽然这样做可能可行,但是使用JSF阶段监听器实现的功能,也可以通过servlet过滤器来实现,这就像用锤子代替螺丝刀拧螺丝一样。 - BalusC
虽然那可能起作用,但是使用JSF阶段监听器,而同时相同的功能也可以用servlet过滤器实现,就像用锤子代替螺丝刀来拧螺丝一样。 - snabel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接