I am currently developing an app that has a login, after the person logs in I want to remove the login page from history so that if they choose the back button (after login) it wont go back to the login page, but the one before it.
In javascript the code is location.replace('whatever_url_you_are_forwarding_to'); I want to know the equivalent in Java / JSF
There is no way for server-side Java code to modify the history of the client-side browser.
I wouldn't mess with the browser history: users expect a consistent and predictable behavior for the back button. If they want to go back to the login page, let them go back to the login page.
You can solve this the following way:
Tell the browser to not cache the login page, so that it will always send a fullworthy GET request on it instead of loading from the browser cache.
When the login page is requested while the user is already logged-in, then send a redirect to the desired page (which you've remembered in the session when the user logs in).
This could be done in one and same filter (although I'd personally prefer to disable browser caching on all change-sensitive dynamic pages, so that would then be done in another filter on a more generic URL pattern covering those pages).
Here's a kickoff example assuming that you're using container managed authentication (for homegrown authentication wherein you manually put the user in the session, just check the presence of the session attribute instead):
#WebFilter("/login.xhtml")
public class LoginPageFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession();
// Tell browser to not cache this page.
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.
// Check if user is logged in.
if (request.getRemoteUser() == null) {
// Not logged in, so remember previous URL and continue request.
session.setAttribute("referrer", request.getHeader("referer")); // Yes, the misspelling is "correct"!
chain.doFilter(request, response);
}
else {
// Logged in, so redirect to initial referring URL, if any.
String referrer = (String) session.getAttribute("referrer");
if (referrer != null) {
response.sendRedirect(referrer);
}
else {
// There was no referring page. Just continue request?
chain.doFilter(request, response);
}
}
}
// ...
}
Related
I have a Java filter that allows continue or not the request depending of the URL, however I have a problem when the request come from a form.
Let's say I have a HTML form with an action and a submit button, then the filter evaluate the request, if the request is invalid I need to stop the request:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String requestDestination = ((HttpServletRequest) servletRequest).getRequestURI();
if ( requestDestination.contains("/url") ) {
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
Cookie denied = new Cookie("denied", "url");
httpResponse.addCookie(denied);
return;
}
}
The problem is that despite this action, the browser goes to this URL showing an empty page off course, but what I need is to stop this default behavior, just leaving the user in the same page.
I can't use JavaScript since I don't know exactly who is triggering the request.
You cannot "stop" the request. Once the browser has submitted the form, it will await a response and will render the content of that response body.
Thus if your servlet filter is blocking the request, it is the responsibility of your filter to also return appropriate content to the browser. This is typically some type of error page, the content of which is entirely up to you.
If you want to make the user return back to the previous page, you can try redirecting the user to the url taken from the Referer header:
if ( requestDestination.contains("/url") ) {
String referer = request.getHeader("Referer");
if (referer != null && referer.length() > 0) {
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
httpResponse.sendRedirect(httpResponse.encodeRedirectURL(referer));
} else {
// just do nothing and display a blank page if there is no Referer
}
}
But for this to work, you need to be sure that the 'previous page' always accepts such a duplicated request using GET method.
It's not possible to do on server side -- because whatever server response is (and there is always a response, even for stopped requests), your browser will display it. Like empty response in your example.
There only thing you can try to archieve without JavaScript is to show user the same page he comes from:
you can just display the same page he comes from (with form, etc.)
you can redirect user to the same page with httpResponse.sendRedirect(httpRequest.getRequestURI())
I need to detect the page reload event on the servlet. I was wondering if there is any method in request object that can help me to do so or if there is not.... is there any other way to do the same?
When you tap the "Reload" button in a browser, it specifically ignores the cache-control directives and requests a fresh copy. Therefore you can inspect the request headers and look for an absent If-Modified-Since header:
public void doGet( final HttpServletRequest request, final HttpServletResponse response ) throws ServletException, IOException
{
if ( isReloaded(request) ) handleReload( request, response );
else handleNormal( request, response );
}
boolean isReloaded( final HttpServletRequest request )
{
return request.getParameter("If-Modified-Since") == null;
}
/**
* You may need to modify this function or you may not need it at all.
* Let me know and I'll edit the solution.
*/
protected long getLastModified( final HttpServletRequest request )
{
return new Date().getTime();
}
If you're behind a proxy such as Apache mod_proxy,
you may also need to set ExpiresActive off in the Apache files.
Also there's no way to distinguish between the first time someone visits a page and the "Reload" button without the use of cookies.
Cache control directives don't come into play on doPost, so the same trick won't work for doPost.
If you have a small server with a limited number of URLs, you could consider setting a cookie for every URL. If the cookie is present it's a reload event. If it's absent, it's a first time visit. That would work for both doGet and doPost.
The negative is that it seems there's a maximum limit in most browsers on how many cookies you can set per domain. For Chrome, the limit seems to be 180 cookies per domain, and 4096 bytes per cookie.
I've read the Tomcat CSRF protection fileter documentation and to my understanding the entry point must be pages that do not perform a security function.
I understand that when used any of the entry points are part of the nonce in the current session.
However as per the OWASP top 10 recommendations, I'm invalidating the session when a user logs in and generating a new session.
The issue I find is that when this is done, if a user clicks on a link that is only available once logged in, for example change password, which does not form part of the entry points a 403 is returned, due to the new session.
If the user clicks on one of the entry point urls first, they can then click on the change password link and access the page because a new nonce has been created and the change password link will be covered by this, but if the welcome page has other links that the user needs to use they cannot use the back button and click on the link(s) then as the nonce is not the same.
My question is how to handle the fact that when the new session is created the change password page will not be accessible without first clicking on one of the entry point urls.
I have looked at encoding the url, but it maybe my code is not written to handle this as correctly also I'm using JSTL due to its escaping properties to help prevent XSS and I can't seem to find a way to encode the urls with this.
Can anyone offer some advice or option as what would be best to do.
If it will help I'm including the login section of my controller servlet, which is reached via a login form post method.
public class UserController extends HttpServlet {
final static Logger log = LogManager.getLogger(UserController.class);
#Override
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
String requestURI = request.getRequestURI();
String url = "";
// Register a new user
if (requestURI.endsWith("/subscribeToSite")) {
url = subscribeToSite(request, response);
}
// Login
if(requestURI.endsWith("/logInToSite")){
url = logInToSite(request, response);
}
//try to login
User user = UserDB.loginUser(mPNum, upwd);
if(user==null){
url = "/loginerror.jsp";
}else{
HttpSession session = request.getSession();
session.invalidate();
session=request.getSession(true);
session.setAttribute("loggedUsrID", user.getUserID());
session.setAttribute("loggedUsrFName", user.getFName());
url="/schedule/welcome.jsp";
}
return url;
}//EO user login
OK worked around this by invalidating the session when logging in. Then creating the token off another page that is accessible only through the landing page.
I have the requirement that the end user should not be able to go back to the restricted page after logout/sign out. But currently the end user is able to do that by the browser back button, visiting browser history or even by re-entering the URL in browser's address bar.
Basically, I want that the end user should not be able to access the restricted page in any way after sign out. How can I achieve this the best? Can I disable the back button with JavaScript?
You can and should not disable the browser back button or history. That's bad for user experience. There are JavaScript hacks, but they are not reliable and will also not work when the client has JS disabled.
Your concrete problem is that the requested page is been loaded from the browser cache instead of straight from the server. This is essentially harmless, but indeed confusing to the enduser, because s/he incorrectly thinks that it's really coming from the server.
You just need to instruct the browser to not cache all the restricted JSP pages (and thus not only the logout page/action itself!). This way the browser is forced to request the page from the server instead of from the cache and hence all login checks on the server will be executed. You can do this using a Filter which sets the necessary response headers in the doFilter() method:
#WebFilter
public class NoCacheFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
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);
}
// ...
}
Map this Filter on an url-pattern of interest, for example *.jsp.
#WebFilter("*.jsp")
Or if you want to put this restriction on secured pages only, then you should specify an URL pattern which covers all those secured pages. For example, when they are all in the folder /app, then you need to specify the URL pattern of /app/*.
#WebFilter("/app/*")
Even more, you can do this job in the same Filter as where you're checking the presence of the logged-in user.
Don't forget to clear browser cache before testing! ;)
See also:
Authentication filter and servlet for login
How to control web page caching, across all browsers?
*.jsp in Url Pattern won't work if you forward a page. Try to include your servlet too.. that will make your application secure from this back button problem.
The simplest way to do it without disabling the browser back buton is by adding this code to the page_load event for the page that you don't want the user to go back to after logging out:
if (!IsPostBack)
{
if (Session["userId"] == null)
{
Response.Redirect("Login.aspx");
}
else
{
Response.ClearHeaders();
Response.ClearContent();
Response.Clear();
Session.Abandon();
Session.Remove("\\w+");
Response.AddHeader("Cache-Control", "no-cache, no-store, max-age = 0, must-revalidate");
Response.AddHeader("Pragma", "no-cache");
Response.AddHeader("Expires", "0");
}
}
The correct way to do this is to add the
Vary: Cookie
header on secured pages. When the user logs out, clear their session cookie. Then, when they navigate back after logging out, the browser cache will miss. This also has the benefit of not completely defeating caching.
You can try telling the browser not to cache the homepage (using the appropriate headers - Expires, Cache-Control, Pragma). But it is not guaranteed to work. What you can do, is make an ajax call to the server on page load to check if the user is logged, and if not - redirect.
An alternative to implementing a Filter is to set a 'no-cache' filter on all the secured JSPs, or on all paths. This may be a good idea if the application is small, and if you would like to customize this property for a specific pages only. We can add the following Java snippet on every secured JSP that should not be cached:
<%
response.addHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setDateHeader("Expires", 0);
%>
If not on JSP, this could also be used in Controllers where routing is defined and set the headers for the 'HttpServletResponse' object.
For me the problem was , I didn't want to set headers on all pages , so I just set this header on page when logout is clicked and it clears everything related to the site :)
// Removes all site data
response.setHeader ("Clear-Site-Data", "\"cache\"");
Please read more about it over here :
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
response.setContentType("text/html");
Cookie[] cookies = request.getCookies();
if(null != cookies) {
for(Cookie cookie : cookies) {
out.println(cookie);
out.println("<br>");
}
} else {
//Set the Cookie in response, so that you can retrieve this in subsequent requests
Cookie PegaRULES = new Cookie("PegaRULES", "This is PegaRULES Cookie");
response.addCookie(PegaRULES);
}
}
Hello everyone,
I've above code, Ideally it is supposed to do following
for 1st request, as there would be no PegaRULES cookie, it should add one in the response
from second request on wards, same cookie should be printed on browser (i mean the ClassName#hashcode of cookie like Cookie#Ac35c)
My observations
For first request, blank page (as expected) and in the response the cookie is present
for second request, Cookie#af33d (say)
for all subsequent requests, it should not change (i.e. same Cookie#af33d should be printed on browser)
But, I see the value changed on each subsequent request (i do refresh the browser each time, to fire subsequent requests)
FYI, I'm using Fiddler to trace the requests and responses
and I don't see any cookie in subsequent responses from 2nd request on wards (and this is as expected).. I'm more concerned about the value printed on browser, which is changed each time i refresh the browser (i.e. fire a new request)
PS : same is the behavior with both IE and Google Chrome
According to documentation, The javax.servlet.http.Cookieclass does not override Object#hashCode, therefore the hash code of its instance is not based on the values set on it, but is more or less randomly assigned to each individual instance.
Since each request gets a fresh instance of Cookie, the result is a different hash code each time.