views:

494

answers:

6

Hi,

I'm doing a simple forum with a series of Servlets that each represent a home, topic, postedit, login and userlist page. On some of these pages there is a link that appears when a user isn't logged in.

What I'd like to achieve is to trigger a redirection (using forward() on a RequestDispatcher) after a login so the browser goes back to the page where a user was before clicking the login link. In order to do this, I see two solutions.

The first solution is to have an HTML Form with a login button and an invisible field that will contain information that will say what page to redirect as a Parameter. This is doable but I'd like to try something else.

The second solution is to add an Attribute to the session that represents the first "page" in some way. This could contain a String but this is no different from the first approach. Another twist would be to add a reference to the HttpServlet and to use instanceof or a static String variable that could be used to identify the Servlet in some way. However, this would require creating a common ancestor class for all the Servlets.

Perhaps there is another simple solution that you can see that would form a good compromise ? Or, maybe one of the above solutions is perfectly acceptable ?

+1  A: 

If you want to do this with pages, your login page could look at the referer (sic) header in the request that loaded it (request.getHeader("referer")) to see if that's a page on your site (if not -- or if the header is missing -- use a default of some kind). Then it would store that URL (I'd probably use a hidden field, as you said, in the login form; but a session var would work too). When the login is complete, issue a redirect to the stored URL.

These days, I'd probably use all of that as a fallback mechanism if I couldn't do the login by overlaying a dialog on the page and logging in via Ajax -- and so, never leaving the page at all.


Edit Or better yet, as Bozho points out, encode the target page into your link to the login page. Although it's not true that IE doesn't set the "referer" header (it does), referer is not required and can be disabled, and since you're already dynamically creating the page linking to the login form, why be vulnerable to that if you don't need to be.

T.J. Crowder
referer isn't required, and IE doesn't happen to set it. So redirecting to a default page in 50+% of the cases isn't preferable
Bozho
IE8 appears to return the same referer value as Firefox. From your reply, I imagine that this isn't the case with other versions of IE.
James P.
@Bozho: Not sure what you're referring to (um, as it were). Just tested it to be sure: IE6 sets it, IE7 sets it, IE8 sets it.
T.J. Crowder
@Bozho: But it's true that it's not required, just incredibly widely supported. But since James is in control of the page providing the link, yeah, probably best to encode that information in the link instead.
T.J. Crowder
well, I can't find a reference as of when and why IE doesn't set it, but it's what I remembered from own experience.
Bozho
@T.J. Crowder: Whatever the consensus is on the referer value, I consider that the information you have given is useful.
James P.
Thanks James. And Bozho was right to question using referer, too, it's just not necessary in your case.
T.J. Crowder
@Bozho @T.J. Crowder: This exchange is a good example of why it's preferable to speak in positive terms when trying to convince someone, especially in the context of a development team. That means avoiding the NOT word as much as possible. That way the other person feels that they are being respected and is able to accept an idea more easily.
James P.
+1  A: 

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

chapter: Application Flow on Authentication Success and Failure

... If authentication is successful, the resulting Authentication object will be placed into the SecurityContextHolder. The configured AuthenticationSuccessHandler will then be called to either redirect or forward the user to the approprate destination. By default a SavedRequestAwareAuthenticationSuccessHandler is used, which means that the user will be redirected to the original destination they requested before they were asked to login. ...

nils petersohn
Thanks Nils. I've been experimenting with Spring so this will be useful later on.
James P.
+4  A: 

I would prefer the first above the second solution. This is request scoped information and really doesn't belong in the session, it would only lead to "wtf?" experiences when you have multiple windows/tabs open in the same session.

On the link to the login page, just pass the current URL as request parameter:

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

Or if it is a POST form to the login page:

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

In the login form, transfer it to the next request as hidden variable:

<input type="hidden" name="from" value="${param.from}">

In the login servlet, make use of it:

User user = userDAO.find(username, password);
if (user != null) {
    request.getSession().setAttribute("user", user);
    response.sendRedirect(request.getParameter("from"));
} else {
    // Show error.
}

Fairly simple, isn't it? :)

Some may suggest to use request.getHeader("referer") for this inside the login form instead of request.getRequestURI() in the link/button before login, but I wouldn't do that as this is client-controlled and doesn't always return reliable information. Some clients have disabled it or are using some software which spoofes it with an invalid value, such as most of the (cough) Symantec products do.

BalusC
Great answer, plus it gives me a snapshot of what to aim for once a DAO is added.
James P.
+1  A: 

Using the hidden field in the form is pretty standard. Why try reinventing the wheel?

Jeremy Raymond
Yes, it's a standard practise with HTML but I wasn't so sure when it comes to web applications. Also, one reason to reinvent the wheel is to better understand why the wheel was needed in the first place ;)
James P.
+1  A: 

Your first suggested approach is the best one. Have a hidden field with value=request.getRequestURI() and redirect to that URI after login.

Using refererwon't work, because IE (at least some of its versions) doesn't set the referer header.

Storing a parameter in the session would cause strange behaviour if the user opens multiple tabs.

Edit: To illustrate the question better:

some resource -> (requests protected resource) -> (gets forwarded to the login page) -> (should be redirected to the original resource)

Most answers assume that a "login" link/button is clicked, and then the login page is opened. This is only one side of the story. In that case the original resource URL can be added as a parameter, and placed in the login form (in a hidden field).

But in case of forwarding from a protected resource to the login page, the hidden field should contain the immediate request URL.

This, of course, isn't what's in the question, but will eventually arise as a situation and should be considered as well.

Bozho
Um, *what* versions? Reference? I've just tested IE6, IE7, and IE8. They all do. (Not that it wouldn't be better to encode the link into the URL for the login page.)
T.J. Crowder
Having a form just to encode the backlink seems overkill. Just encode it in the login form link as a query param.
T.J. Crowder
Most clients set the referer header, but of course it can be overriden / set to blank at the client side, so I would definitely go for <code>request.getRequestURI()</code>
Ben Poole
I just remembered working with referer a while back, and when we tested with IE, the referer was null. Might be something related to this http://stackoverflow.com/questions/402065/internet-explorer-http-referer-issue
Bozho
the form = the login form. not some addtional form.
Bozho
Well, whatever your specific scenario was, fundamentally in the normal course of things, IE *does* set the referer. But you're right, why rely on something less-than-reliable when he's producing the page with the login link anyway?
T.J. Crowder
It appears that the referer value is too client dependent (if such an expression exists). BalusC's reply gives some insights as to what can happen.It's a similar issue to what can happen in online games with variables that are shared/replicated between client and server. These can be used to "hook" into the game client-side.
James P.
A: 

Ok, here's what I've done. Login is a two-step process with one Servlet showing a a form and another controlling if the username+password combination is correct. The two could be combined.

A parameter can be sent through a form or through a link (JSP hasn't been added yet):

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

The parameter is then retrieved as follows:

String comeback = request.getParameter("comeback");

Once the login information has been checked, redirection can be done as follows:

RequestDispatcher rd = request.getRequestDispatcher( redirectionPath );

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