登录后重定向回页面

17

我正在做一个简单的论坛,使用一系列Servlets来代表主页、主题、帖子编辑、登录和用户列表页面。在其中一些页面上,当用户未登录时会出现一个链接。

我想要实现的是,在登录后触发重定向(使用RequestDispatcher上的forward()),使浏览器返回到用户在单击登录链接之前所在的页面。为了实现这一点,我有两个解决方案。

第一种解决方案是使用HTML Form,其中包含一个登录按钮和一个不可见字段,该字段包含将作为Parameter重定向页面的信息。虽然这是可行的,但我想尝试其他方法。

第二种解决方案是向session添加一个Attribute,以某种方式表示第一个“页面”。这可以包含字符串,但这与第一种方法没有区别。另一个变通方法是添加对HttpServlet的引用,并使用instanceof或静态字符串变量来以某种方式识别Servlet。但是,这将需要为所有Servlets创建一个共同的祖先类。

也许您能看到另一个简单的解决方案,它能形成一个良好的折衷方案吗?或者,也许上述解决方案中的一个是完全可接受的?

6个回答

28

我更倾向于第一个解决方案。这是请求范围内的信息,真的不属于会话,如果您在同一会话中打开多个窗口/选项卡,它只会导致“ wtf?”体验。

在登录页面的链接上,只需将当前URL作为请求参数传递:

<a href="/login?from=${pageContext.request.requestURI}">Login</a>

如果它是提交到登录页面的POST表单:

<input type="hidden" name="from" value="${pageContext.request.requestURI}">

在登录表单中,将其作为隐藏变量传递到下一个请求:

<input type="hidden" name="from" value="${param.from}">
在登录servlet中,利用它:
User user = userDAO.find(username, password);
if (user != null) {
    request.getSession().setAttribute("user", user);
    response.sendRedirect(request.getParameter("from"));
} else {
    // Show error.
}

相当简单,不是吗? :)

有些人可能建议在登录表单中使用request.getHeader("referer")而不是在登录前的链接/按钮中使用request.getRequestURI(),但我不会这样做,因为它是由客户端控制的,并不总是返回可靠的信息。一些客户端已经禁用了它,或者正在使用某些欺骗性值的软件来欺骗它,比如大多数(咳咳)Symantec产品。


很棒的答案,而且它给了我一个目标快照,一旦添加了DAO就知道该怎么做了。 - James P.
如果您正在使用容器管理的安全性,则将请求发布到j_security_check将不允许您执行此操作;而是使用Servlet版本3编程登录:HttpServletRequest.login。 - Ryan

5
您的第一种建议方法是最好的。有一个带有value=request.getRequestURI()的隐藏字段,并在登录后重定向到该URI。
使用referer不起作用,因为IE(至少某些版本)不设置referer头。
将参数存储在会话中会导致用户打开多个标签时出现奇怪的行为。
编辑: 为了更好地说明问题: 某些资源 ->(请求受保护的资源) ->(转发到登录页面) ->(应重定向到原始资源) 大多数答案都假设单击“登录”链接/按钮,然后打开登录页面。这只是故事的一面。在这种情况下,原始资源URL可以作为参数添加,并放置在登录表单中(在隐藏字段中)。
但是,在从受保护的资源转发到登录页面的情况下,隐藏字段应包含直接请求的URL。
当然,这不是问题中的内容,但最终会出现这种情况,因此也应予以考虑。

嗯,“什么”版本?参考资料在哪里?我刚测试了IE6、IE7和IE8。它们都可以。 (虽然将链接编码到登录页面的URL中可能会更好。) - T.J. Crowder
在表单中只为编码后向链接添加一个表单似乎有些过度设计了。可以将其作为查询参数编码到登录表单链接中。 - T.J. Crowder
大多数客户端会设置referer头,但当然客户端可以重写或将其设置为空白。因此,我肯定会选择<code>request.getRequestURI()</code>。 - Ben
表单 = 登录表单。不是其他附加表单。 - Bozho
似乎referer值太依赖客户端(如果这样的表达存在的话)。BalusC的回复提供了一些关于可能发生的事情的见解。这是与在线游戏中共享/复制客户端和服务器之间变量可能发生的问题类似的问题。这些可以用于“钩入”游戏客户端。 - James P.
显示剩余2条评论

3
如果您想要使用页面实现此功能,您的登录页面可以查看请求中的referer头信息(request.getHeader("referer"))以确定它是否是您站点上的页面(如果不是或者header信息不存在,则使用默认值)。然后将该URL存储下来(我可能会在登录表单中使用一个隐藏字段,正如您所说的那样,但也可以使用一个session变量)。当登录完成时,发出重定向到存储的URL。
现在,如果我无法通过在页面上覆盖对话框并通过Ajax进行登录来执行登录,则可能会将所有这些用作后备机制,从而根本不离开页面。
编辑或更好的方法是,如Bozho所指出的那样,将目标页面编码到链接到登录页面的链接中。虽然IE确实设置了“referer”头信息(它确实设置了),但referer不是必需的,可以被禁用,而且由于您已经动态创建了链接到登录表单的页面,如果您不需要,为什么要容易受攻击呢?

1
引荐者并非必需,IE浏览器也不会自动设置。因此,在50%以上的情况下重定向到默认页面并不理想。 - Bozho
1
@Bozho:不确定你指的是什么(嗯,就这样)。只是为了确保进行了测试:IE6设置它,IE7设置它,IE8设置它。 - T.J. Crowder
@Bozho:但事实上并不需要,只是得到了广泛的支持。但既然James控制着提供链接的页面,最好还是将该信息编码在链接中。 - T.J. Crowder
谢谢James。Bozho对于使用referer的质疑是正确的,因为在你的情况下它并不必要。 - T.J. Crowder
1
@Bozho @T.J. Crowder:这个交流是一个很好的例子,说明在试图说服某人时最好用积极的语言,特别是在开发团队的背景下。这意味着尽可能避免使用“不”这个词。这样,对方会感到受到尊重,并能更容易地接受一个想法。 - James P.
显示剩余3条评论

1

来自: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/springsecurity.pdf

章节:身份验证成功和失败的应用程序流程

... 如果身份验证成功,将把生成的Authentication对象放入SecurityContextHolder中。然后将调用配置的AuthenticationSuccessHandler以将用户重定向或转发到适当的目标。默认情况下,使用SavedRequestAwareAuthenticationSuccessHandler,这意味着用户将被重定向到他们在被要求登录之前请求的原始目标。 ...


谢谢你,尼尔斯。我一直在尝试使用Spring,所以这对我以后很有用。 - James P.

1

在表单中使用隐藏字段是相当标准的。为什么要试图重新发明轮子呢?


是的,这是HTML的标准做法,但当涉及到Web应用程序时,我并不确定。此外,重新发明轮子的原因之一是更好地理解为什么需要轮子 ;) - James P.

0

好的,这是我所做的。登录是一个两步骤的过程,其中一个Servlet显示一个表单,另一个控制用户名+密码组合是否正确。这两个可以合并。

参数可以通过表单或链接发送(尚未添加JSP):

out.println( "Login <a href='LoginServlet?comeback=home'>here</a><br>" );  

参数可以通过以下方式获取:
String comeback = request.getParameter("comeback");

一旦登录信息已经被验证,可以按照以下方式进行重定向:

RequestDispatcher rd = request.getRequestDispatcher( redirectionPath );

if( rd != null )
   rd.forward(request, response);

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