We have a JSF 2.0, Primefaces 5.0, Spring Security 3.2.3.RELEASE application.
To handle session timeout, I am using primefaces idleMonitor and p:dialog & javascript to display a countdown popup and redirect them back to login page.
I have also implemented a custom CacheControlPhaseListener so that the pages are not cached. I set the no-cache in the response headers in the CacheControlPhaseListener.
<lifecycle><phase-listener id="nocache">com..filter.CacheControlPhaseListener</phase-listener></lifecycle>
I also have error handling configured in my web.xml:
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/error.jsf</location></error-page>
I have also implemented a ViewExpiredHandler that extends ViewHandlerWrapper
#Override
public UIViewRoot restoreView(FacesContext ctx, String viewId)
{
UIViewRoot viewRoot = super.restoreView(ctx, viewId);
try
{
if (viewRoot == null)
{
viewRoot = super.createView(ctx, viewId);
ctx.setViewRoot(viewRoot);
}
}
catch (Exception e)
{
e.printStackTrace();
}
return viewRoot;
}
The problem I am still having is:
1. When the session expires on a idle page (E.g. Search page) and if some ajax action is triggered on a page, even though I logout, when I navigate back to the page (e.g. Login-> Home-> Search page). I see a partial-response xml error:
<partial-response><changes><update id="blGridId"><table id="blGridId" style="width:100%;">
<tbody>
<tr>
<td><div id="blTableId" class="ui-datatable ui-widget ui-datatable-scrollable ui-datatable-resizable"><div id="sublTableId_paginator_top" class="ui-paginator ui-paginator-top ui-widget-header ui-corner-top" role="navigation"><span class="ui-paginator-prev ui-state-default ui-corner-all ui-state-disabled"><span class="ui-icon ui-icon-seek-prev">p</span></span><span class="ui-paginator-next ui-state-default ui-corner-all ui-state-disabled"><span class="ui-icon ui-icon-seek-next">p</span></span></div><div class="ui-widget-header ui-datatable-scrollable-header"><div class="ui-datatable-scrollable-header-box"><table role="grid"><thead id="blTableId_head"><tr role="row"><th id="blTableId:j_idt101" class="ui-state-default ui-resizable-column" role="columnheader" style="width:34px; #width:37px;"><span class="ui-column-title"><span style="word-wrap: break-word;white-space: normal;">Client </span></span></th><th id="blTableId:j_idt104" class="ui-state-default
2. If I hit a browser refresh, it loads back the page and I can continue with the actions.
Please let me know what I need to do in addition to the above to resolve the partial-response error. Do I need to add a servlet filter to invalidate the session?
I would really appreciate any help and feedback on this as it is high priority.
I had got the same issue when session had been expired. I thought it was too late, but maybe would be helpful for others who has issues like me.
The root cause is Spring Security saves the last request before redirecting client to do the authentication. After then, Spring security would try to perform the request again when user visits the page of last request. Unfortunately, the request was ajax/partial and its view was expired -> partial xml content was returned.
Easy way to get rid of this issue is removing the saving behavior of Spring Security. SavedRequestAwareAuthenticationSuccessHandler class is used to handle these kind of behaviours. Configure as:
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
p:authenticationManager-ref="authenticationManager"
p:authenticationFailureHandler-ref="authenticationFailureHandler"
p:authenticationSuccessHandler-ref="authenticationSuccessHandler"
p:usernameParameter="username"
p:passwordParameter="password">
</bean>
...
<bean id="authenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"
p:defaultTargetUrl="/"
p:alwaysUseDefaultTargetUrl="true"/>
Hope it would help.
Related
I'm using Spring Security with CAS and have the following issue. When the authentication error is thrown from CAS Server (e.g. invalid username/password) it is shown well in form and is displayed correctly using tag:
<form:errors path="*" id="msg" cssClass="alert alert-danger" element="div"/>
But in cases when CAS Server returns success and the AuthenticationException is thrown on CAS Client none of the errors are displayed as basically CAS Client redirects back to http://localhost:8080/cas/login?service=http%3A%2F%2Flocalhost%3A8080%2Fj_spring_cas_security_check
So I can't really display what went wrong on the client side. Is it somehow possible to display an error from client on the same JSP in case it throws AuthenticationException?
Not sure if that's the super clean and right way to do it, but the way I've managed to do that is using cookies.
All I had to do is to extend SimpleUrlAuthenticationFailureHandler, get there last authentication exception using request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION)
and write my custom error code cookie. The code is in Scala, but is pretty straightforward:
class CookieAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
val clientErrorCookie = "clientError"
override def onAuthenticationFailure(request: HttpServletRequest, response: HttpServletResponse, exception: AuthenticationException): Unit = {
val authenticationException = SecurityUtility.getSessionAuthException(request).getOrElse(exception)
ClientErrors.values
.filter(clientError => clientError.exceptionClass.equals(authenticationException.getClass))
.foreach(clientError => response.addCookie(new Cookie(clientErrorCookie, clientError.errorCode)))
super.onAuthenticationFailure(request, response, authenticationException)
}
}
Then, on the CAS server side I've displayed error on JSP in the following way:
<c:set var="clientErrorCookie" value="clientError"/>
<c:if test="${cookie.containsKey(clientErrorCookie)}">
<div class="alert alert-danger">
<spring:message code="error.client.authentication.${cookie.get(clientErrorCookie).value}"
text="Client authentication error"/>
</div>
</c:if>
And after the page is loaded and error is shown, I've just removed that cookie in JS:
function deleteCookie(name) {
document.cookie = name + '=; Path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
$(document).ready(function(){
deleteCookie('${clientErrorCookie}');
}
Version :
Apache MyFaces 2.1.14
Rich Faces 4.3.5
Issue :
I want to invalidate JSF session when user clicks on Logout page.
I have decided to use PreRenderViewEvent for the same.
Below is the design for the same :
When logout page is being rendered , call a listener method in back end bean through PreRenderViewEvent.
Invalidate JSF session in java method like : FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
My question is : is this a right approach in invalidating session ? Or is there any better approach available ?
Code :
<!-- call this in Logout.xhtml page -->
<f:event listener="#{bean.invalidateSession}" type="preRenderView" />
EDIT 1:
Let me elaborate the context of the question.
I have two web applications in my system.
When a user clicks on Logout button it always goes to webApp1.
The requirement is to invalidate session in webApp1 and then redirect to webApp2 where again invalidate the session.
The preRenderView event will be used when webapp1 LogOut page is being rendered. The listener there will invalidate the session and redirect to webapp2 LogOut page like below code :
public void logOut(ComponentSystemEvent event) throws IOException{
//logoutAction param will be read from request
String redirectUrl = "/webapp2/Logout.xhtml?action="+logoutAction;
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
FacesContext.getCurrentInstance().getExternalContext().redirect(redirectUrl);
}
I usually just add a button to my template :
<h:commandLink action="#{loginBean.logout()}" value="Logout" rendered="#{!empty loginBean.account}" />
This button calls my "login" manager bean, which contains an attribute Account account, populated with the user's parameters. It's also only rendered if the user is connected.
public String logout()
{
setAccount(null);
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().clear();
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
NavigationUtils.redirect("/admin/login");
return "";
}
Current status :
If any session time out occurs, redirectToLogin function in FacesAjaxAwareUserFilter will be executed. From there I can redirect to any page I need. I get the URI in all cases.
It's all working fine and big thanks to BalusC. :)
Now the second part which makes the issue
Redirect to login and come back to the previous page.
For Eg :
Page 5----> Login------> Page 5
I have appended the redirect URI to the login URI and retrieved the values from the bean.
But the problem is that I have 2 pages before the user login. A login mode selection page (ie; google authentication or default login) and the page that reads the username and password.
How can I pass the redirect URI through both the pages.?
This is the code I have used to redirect at Ajax time out and in normal session time out.
Class FacesAjaxAwareUserFilter
if ("partial/ajax".equals(request.getHeader("Faces-Request"))) {
res.setContentType("text/xml");
res.setCharacterEncoding("UTF-8");
res.getWriter().printf(FACES_REDIRECT_XML, request.getContextPath() + getLoginUrl()+"?redirectUrlPattern="+request.getRequestURI());
}
else {
if (request.getRequestedSessionId()!=null && (!SecurityUtils.getSubject().isAuthenticated() || !request.isRequestedSessionIdValid())) {
response.sendRedirect(getLoginUrl()+"?redirectUrlPattern="+request.getRequestURI());
return;
}
super.redirectToLogin(req, res);
}
Method used is FullAjaxExceptionHandlerFactory in Omniface.
I have used a method of appending the values to the URI, but it fails to identify whether the session is expired or a session not created (when a user logs in at first).
Problem Code
if (request.getRequestedSessionId()!=null && (!SecurityUtils.getSubject().isAuthenticated() || !request.isRequestedSessionIdValid()))
I am looking for a way to identify the expired session from new session created before login.
Wish to implement this in a Better Way.
A method which will not append present URI with the redirect URL is most welcomed.
I have founded out a solution but still i don't find it as the best solution. Any other answer for doing it is greatly appreciated.
Step 1
Configure the application to detect Ajax Timeout and normal timeout. This is how i did it..!!
Step 2
Finding the URI when session timeout occurs, using this.
FacesAjaxAwareUserFilter will grab the ServletRequest and ServletResponse for you.
form that you can convert that to HttpServletRequest and HttpServletResponse using this
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response= (HttpServletResponse) res;
The request.getRequestURI() will get you the redirect URI
String redirectURI=request.getRequestURI();
Now you want to save this to some storage so that you can use it in any place you want. The only way i found is to append it with URL using this.
res.getWriter().printf(FACES_REDIRECT_XML, request.getContextPath() + "/sessionexpired"+"?redirectUrlPattern="+request.getRequestURI());
or
response.sendRedirect("/sessionexpired"+"?redirectUrlPattern="+request.getRequestURI());
Now on the page load of my sessionExpiry page bean, i used this to grab the value passed through the URL
redirectUrl=FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("redirectUrlPattern");
Now using JavaScript i set that value of redirectUrl to localStorage. This is how you can save values to local storage.
This is my sessionexpiry.xhtml page.
<script type="text/javascript">
function setRedirectURIToLocalStorage(){
if(typeof(Storage)!=="undefined")
{
localStorage.redirectURI=document.getElementById("redirectUrl").value;
window.location.href="/login";
}
}
</script>
</h:head>
<h:body id="body" onload="setRedirectURIToLocalStorage()">
<f:view contentType="text/html">
<h:form prependId="false" >
<h:inputHidden id="redirectUrl" value="#{sessionExpBean.redirectUrl}" />
<h:outputText value="Session Expired... Redirecting...!!"></h:outputText>
</h:form>
</f:view>
</h:body>
The page will call setRedirectURIToLocalStorage() function onLoad
and then it set the values to the localStorage. Thus the redirect
value is available all across the browser. You can use that in any
page you need.!! All you need to do is to check in this variable
localStorage.redirectURI
I am implementing an auth system in my angular js app.
What I am planning it like below:
Get user info(name and pass from login form)
Check whether user exists or not
if exists server respond with a session cookie and frontend will redirect to a certain page.
then user will do some task which will generate API request
API request should have cookie information that was sent on step 3
server check whether the cookie was generated or not and if cookie was found then respond with the API request results. And in my service I am doing something like
MyApp.service('myAuth', function($http, $q) {
this.authHeader = null;
this.checkAuth = function(){
//do api call and if success sets this.authHeader = response
}
this.isAuthenticaed = function(){
this.authHeader ? return this.authHeder : return false;
}
After submitting the login form I will call checkAuth and get my session cookie back from my server, how I can add the cookie information while doing the next REST call and also when user will navigate throughout the application after log in I do want to check each time isAuthenticaed true or false, in Angularjs when it will navigate to another page does it resets after setting it true from the first call? And is my approach 1-6 good or do you have any specific suggestions?
Btw I checked previous so entries but those are not what I want to know.
I am not sure about your backend, but this is how I would do it
Create a separate login page (dedicated url not angular sub view or
modal dialog).
If the user is not authenticated redirect to this login
page. This is done by server redirects. This page may or may not use
angular framework, as it just involves sending a user\password to
server.
Make a POST (not AJAX request) from the login page, and verify on server.
On the server set the auth cookie. (Different frameworks do it differently. ASP.Net sets form authentication cookie.)
Once the user is authenticated redirect user to the actual angular app and load all its components.
This saves any code require to manage authentication on client side in Angular. If the user lands on this page he is authenticated and has the cookie.
Also default browser behavior is to send all cookies associated with a domain with each request, so you don't have to worry if angular is sending some cookie or not.
I use the http-auth-interceptor. http://ngmodules.org/modules/http-auth-interceptor
In my backend (asp.net mvc) I build a simple Authentication Service and return an http error 401 if the user is not authenticated.
Then I handle the error with a login-view in the SPA site.
The ideas put forth by the previous answers will work, but I think they're overkill. You don't need anything this complex.
how I can add the cookie information while doing the next REST call
Turn on withCredentials by default inside $httpProvider like so:
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}]);
Then remove the wildcard (if you had one) from the CORS-related headers, and set allow-credentials, on the server side. In my case, using Python + Flask + Flask-Restful, it's super easy and looks like this:
import Flask
from flask_restful import Api
app = Flask(__name__)
api = Api(app)
api.decorators = [cors.crossdomain(origin='http://localhost:8100', credentials=True)]
Now cookies will be set and returned automatically and transparently by the browser. See these threads for more info:
$http response Set-Cookie not accessible
Angularjs $http does not seem to understand "Set-Cookie" in the response
when user will navigate throughout the application after log in I do want to check each time isAuthenticaed true or false
As suggested above, have the server return 401 if the auth session expires or is deleted, and use $httpInterceptor in Angular to catch this like so:
app.config(function($httpProvider) {
var interceptor =
function($q, $rootScope) {
return {
'response': function(response) {
return response;
},
'responseError': function(rejection) {
if (rejection.status==401) {
// Modify this part to suit your needs.
// In my case I broadcast a message which is
// picked up elsewhere to show the login screen.
if (!rejection.config.url.endsWith('/login'))
{
$rootScope.$broadcast('auth:loginRequired');
}
}
return $q.reject(rejection)
}
}
};
$httpProvider.interceptors.push(interceptor);
});
(Disclosure: I'm one of the developers of UserApp)
You could use the third-party service UserApp for this, together with the AngularJS module.
Check out the getting started guide, or take the course on Codecademy. Here's some examples of how it works:
Login form with error handling:
<form ua-login ua-error="error-msg">
<input name="login" placeholder="Username"><br>
<input name="password" placeholder="Password" type="password"><br>
<button type="submit">Log in</button>
<p id="error-msg"></p>
</form>
User info is accessed using the user service: user.current.email
Or in the template: <span>{{ user.email }}</span>
Signup form with error handling:
<form ua-signup ua-error="error-msg">
<input name="first_name" placeholder="Your name"><br>
<input name="login" ua-is-email placeholder="Email"><br>
<input name="password" placeholder="Password" type="password"><br>
<button type="submit">Create account</button>
<p id="error-msg"></p>
</form>
ua-is-email means that the username is the same as the email.
How to specify which routes that should be public, and which route that is the login form:
$routeProvider.when('/login', {templateUrl: 'partials/login.html', public: true, login: true});
$routeProvider.when('/signup', {templateUrl: 'partials/signup.html', public: true});
The .otherwise() route should be set to where you want your users to be redirected after login. Example:
$routeProvider.otherwise({redirectTo: '/home'});
Log out link:
<a href="#" ua-logout>Log Out</a>
Hide elements that should only be visible when logged in:
<div ng-show="user.authorized">Welcome {{ user.first_name }}!</div>
And to authenticate to your back-end services, just use user.token() to get the session token and send it with the AJAX request. At the back-end, use the UserApp API to check if the token is valid or not.
If you need any help, just let me know :)
----------------UPDATE----------------
I've entirely re-written my question in attempt to clarify what was apparently not well written. I've also included even more code in the hopes that someone can offer some help. My apologies for the confusing stuff provided earlier.
Basically my problem seems to be that I don't entirely understand Spring and REST. So I'm hoping someone can perhaps clarify things for me and perhaps look over my code and inform me of specifically why it doesn't work. Though I have some idea of the cause I don't understand why it is the way it is.
I've got a very basic Spring app. The user is displayed a page that lists (from the DB) a table made up of two columns filled with usernams and a boolean of whether or not they're enabled.
#RequestMapping(value="/admin/modifyUser", method=RequestMethod.GET)
public void showModifyUser() {}
Clicking on a link in the enabled column simply switches their status. The link is created by sending the user to /admin/access and appending the username and access variable. So, an example would be http://localhost:8080/myApp/admin/access?username=test&access=true (or whatever the exact syntax is). That code was:
#RequestMapping(value="/admin/access",method=RequestMethod.GET)
public String submitModifyAccess(#RequestParam("username")String username,
#RequestParam("access")String access) {
....
return "redirect:/admin/modifyUser";
}
That worked fine. It would update the user's access and return to the page with the table and user data. (Maybe not the best way to implement it?) Later on I wanted to populate data in a Dojo grid and therefore needed the data put into JSON format. Hence I read up in my Spring book on REST and such. So, to start out easy, I decided to make the above Handler RESTful. So I changed it to:
#RequestMapping(value="/admin/access/{username}/{access}",method=RequestMethod.GET)
public String submitModifyAccess(#PathVariable String username,
#PathVariable String access) {
....
return "redirect:/admin/modifyUser";
}
I also updated the JSP to make the link go to /admin/access/username/access, so for example: http://localhost:8080/myApp/admin/access/test/true. And voila, things still worked. I assumed I had made it RESTful. A couple of things did strike me as odd though.
First, when clicking on the link, it did update the status properly, but when returning to the /admin/modifyUser page (which is where it sends you), the two variables would be appended to the URL. So instead of showing http://localhost:8080/myApp/admin/modifyUser, it showed http://localhost:8080/myApp/admin/modifyUser?username=test&access=true. Pretty sure that wasn't supposed to be happening.
Second, I realized that the RequestMethod for submitModifyAccess should be POST (or perhaps PUT).
But as I said, it still worked so I didn't worry about it too much.
Next I tried to modify the other link, the username link. When clicking on that link the user is taken to a form populated with the data of that person. Originally that was called by just appending the username to the URL with a GET request to display the form. So the code was:
#RequestMapping(value="/admin/editUser", method=RequestMethod.GET)
public void showEditUser(Model model, #RequestParam("username") String username) {
NFIUser user = userService.getUser(username);
UserDetails userDetails = userDetailsManager.loadUserByUsername(username);
....
model.addAttribute("user", user);
}
Worked fine. So I updated the JSP so the username links called the proper URL and then I tried to RESTify this method by changing it to:
#RequestMapping(value="/admin/editUser/{username}", method=RequestMethod.GET)
public String showEditUser(Model model, #PathVariable String username) {
NFIUser user = userService.getUser(username);
UserDetails userDetails = userDetailsManager.loadUserByUsername(username);
....
model.addAttribute("user", user);
return "redirect:/admin/editUser";
}
Upon doing that I started to see 405 errors and I now realize I'm clearly not understanding something. First, I believe that in order to do a REST PUT or POST you have to have a GET of that exact same URL. Is that correct? What do people think I should do in this situation?
Oh, and in case anyone wants it, the form I was sending people to is as follows (though it's not ever getting loaded as when the user clicks on the link they get the 405 error):
<div align="center">
<b>If you change the Username you MUST change the password as well.</b>
<s:url value="/admin/editUser" var="edit_url" />
<sf:form method="POST" modelAttribute="user" dojoType="dijit.form.Form" action="${edit_url}">
<script type="dojo/method" event="onSubmit">
if (!this.validate()) {
return false;
}
return true;
</script>
<sf:hidden path="username"/>
<table>
<tr>
<td align="right">Username: </td>
<td>
<sf:input path="newUsername" dojoType="dijit.form.ValidationTextBox" trim="true" required="true" value="${user.username}"/>
</td>
</tr>
<tr>
<td align="right">Password: </td>
<td>
<sf:input path="password" type="password" dojoType="dijit.form.ValidationTextBox" required="true"/>
</td>
</tr>
<tr>
<td align="right">Enabled: </td>
<td>
Yes<sf:radiobutton path="enabled" value="true" dojoType="dijit.form.RadioButton"/>
No<sf:radiobutton path="enabled" value="false" dojoType="dijit.form.RadioButton"/>
</td>
</tr>
<tr>
<td align="right">Admin: </td>
<td>
Yes<sf:radiobutton path="isAdmin" value="true" dojoType="dijit.form.RadioButton"/>
No<sf:radiobutton path="isAdmin" value="false" dojoType="dijit.form.RadioButton"/>
</td>
</tr>
<tr>
<td align="right" colspan="2">
<button dojoType="dijit.form.Button" type="submit">Submit</button>
</td>
</tr>
</table>
</sf:form>
</div>
So hopefully that makes things more clear. Again, if you've got some idea of what I'm doing wrong, if you can explain what I clearly don't get about REST, or any other comments that would improve my code, by all means let me know. Thank you very much.
First the main issue, your 405 error. 405 means "method not supported", i.e. the HTTP method (GET/PUT/POST/etc.) is not supported. Your redirect to /admin/editUser will return a 302 to the client (browser) with a header indicating the redirect URL. The browser will then issue a GET against the URL (i.e. it will redirect itself). From your mappings it looks like the closest matching request you can handle is GET /admin/editUser/{username} - but you are redirecting to GET /admin/editUser. My guess is that is the problem - your redirect does not match any endpoints you've declared.
Note I'm assuming your /admin/editUser/username URL in your example should really have been /admin/editUser/{username} because you need to use curly braces for #PathVariable's.
Also note that it is a bit uncommon to redirect from a GET. You just got something, presumably to return to the client - why redirect to something else?
Regarding whether something like http://localhost:8080/myApp/admin/access/test/true is "RESTful" may be an opinion, but my take is that it is not. The resource to me is the user's "access". To grant access I would probably do a PUT /users/{username}/access. To deny access I would probably do a DELETE /users/{username}/access. Note the uniform interface: different HTTP methods operating on the same URL.
If you have verbs in your URLs (e.g. "modifyUser") or pass data in your URLs (e.g. the true in access=true or /access/true) you are probably not adhering to REST principles.
Lastly, I feel you could benefit from a good book on REST rather than what I am guessing you are using is blogs/articles found online. I find Rest in Practice to be the best so far.
modifyUser vs. editUser ?
You wrote that the link is:
http://localhost:8080/myApp/admin/modifyUser
but you mapped to
/admin/editUser/{username}
If this is not the problem, then try to make your question a bit more clear - especially what is the current implementation and what does not work currently.