Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request - ajax

I find this article to be useful for non-ajax request How to handle session expiration and ViewExpiredException in JSF 2?
but I can't make use of this when I am submitting using an AJAX call.
Suppose in a primefaces dialog, I am making a post request using AJAX and session has already timed out.
I see my page getting stuck.
How to fix this kind of scenario such that when I post using AJAX, I could redirect him to my view expired page and
then forward him to the login page similar to the solution in the link above?
JSF2/Primefaces/Glassfish

Exceptions which are thrown during ajax requests have by default totally no feedback in the client side. Only when you run Mojarra with project stage set to Development and use <f:ajax>, then you will get a bare JavaScript alert with the exception type and message. But other than that, and in PrimeFaces, there's by default no feedback at all. You can however see the exception in the server log and in the ajax response (in the webbrowser's developer toolset's "Network" section).
You need to implement a custom ExceptionHandler which does basically the following job when there's a ViewExpiredException in the queue:
String errorPageLocation = "/WEB-INF/errorpages/expired.xhtml";
context.setViewRoot(context.getApplication().getViewHandler().createView(context, errorPageLocation));
context.getPartialViewContext().setRenderAll(true);
context.renderResponse();
Alternatively, you could use the JSF utility library OmniFaces. It has a FullAjaxExceptionHandler for exactly this purpose (source code here, showcase demo here).
See also:
Why use a JSF ExceptionHandlerFactory instead of <error-page> redirection?
What is the correct way to deal with JSF 2.0 exceptions for AJAXified components?

A merge between the answer of #BalusC and this post, I solved my problem!
My ExceptionHandlerWrapper:
public class CustomExceptionHandler extends ExceptionHandlerWrapper {
private ExceptionHandler wrapped;
CustomExceptionHandler(ExceptionHandler exception) {
this.wrapped = exception;
}
#Override
public ExceptionHandler getWrapped() {
return wrapped;
}
#Override
public void handle() throws FacesException {
final Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator();
while (i.hasNext()) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context
= (ExceptionQueuedEventContext) event.getSource();
// get the exception from context
Throwable t = context.getException();
final FacesContext fc = FacesContext.getCurrentInstance();
final Map<String, Object> requestMap = fc.getExternalContext().getRequestMap();
final NavigationHandler nav = fc.getApplication().getNavigationHandler();
//here you do what ever you want with exception
try {
//log error ?
//log.log(Level.SEVERE, "Critical Exception!", t);
if (t instanceof ViewExpiredException) {
requestMap.put("javax.servlet.error.message", "Session expired, try again!");
String errorPageLocation = "/erro.xhtml";
fc.setViewRoot(fc.getApplication().getViewHandler().createView(fc, errorPageLocation));
fc.getPartialViewContext().setRenderAll(true);
fc.renderResponse();
} else {
//redirect error page
requestMap.put("javax.servlet.error.message", t.getMessage());
nav.handleNavigation(fc, null, "/erro.xhtml");
}
fc.renderResponse();
// remove the comment below if you want to report the error in a jsf error message
//JsfUtil.addErrorMessage(t.getMessage());
} finally {
//remove it from queue
i.remove();
}
}
//parent hanle
getWrapped().handle();
}
}
My ExceptionHandlerFactory:
public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {
private ExceptionHandlerFactory parent;
// this injection handles jsf
public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) {
this.parent = parent;
}
#Override
public ExceptionHandler getExceptionHandler() {
ExceptionHandler handler = new CustomExceptionHandler(parent.getExceptionHandler());
return handler;
}
}
My faces-config.xml
<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
<factory>
<exception-handler-factory>
your.package.here.CustomExceptionHandlerFactory
</exception-handler-factory>
</factory>
</faces-config>

I am using Mojarra 2.1.7 in Production mode with JBoss 7. After the session expires, AJAX calls return an error XML document. You can easily catch this error using the usual onerror handler of f:ajax.
<script type="text/javascript">
function showError(data) {
alert("An error happened");
console.log(data);
}
</script>
<h:commandLink action="...">
<f:ajax execute="..." render="..." onerror="showError"/>
</h:commandLink>

I have included this in my ViewExpiredExceptionHandler class and it worked fine for me in WAS
public void handle() throws FacesException {
FacesContext facesContext = FacesContext.getCurrentInstance();
for (Iterator<ExceptionQueuedEvent> iter = getUnhandledExceptionQueuedEvents()
.iterator(); iter.hasNext();) {
Throwable exception = iter.next().getContext().getException();
if (exception instanceof ViewExpiredException) {
final ExternalContext externalContext = facesContext
.getExternalContext();
try {
facesContext.setViewRoot(facesContext.getApplication()
.getViewHandler()
.createView(facesContext, "/Login.xhtml")); //Login.xhtml is the page to to be viewed. Better not to give /WEB-INF/Login.xhtml
externalContext.redirect("ibm_security_logout?logoutExitPage=/Login.xhtml"); // when browser back button is pressed after session timeout, I used this.
facesContext.getPartialViewContext().setRenderAll(true);
facesContext.renderResponse();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
iter.remove();
}
}
}
getWrapped().handle();
}
Hope this helps

I faced this problem, Requirement need to display a confirmation popup when user do any action after session gets timed out, my proposed solution was:
<security:http use-expressions="true" auto-config="true" entry-point-ref="authenticationEntryPoint">
<security:intercept-url pattern="/common/auth/**" access="permitAll" />
<security:intercept-url pattern="/javax.faces.resource/**" access="permitAll" />
<security:intercept-url pattern="/**/ *.*" access="hasRole('ROLE_ADMIN')" />
<security:form-login login-page="/common/auth/login.jsf" />
<!-- <security:remember-me key="secret" services-ref="rememberMeServices" /> -->
<security:logout invalidate-session="true" logout-success-url="/common/auth/login.jsf" />
</security:http>
<bean id="authenticationEntryPoint" class="com.x.y.MyRedirectEntryPoint" >
<property name="loginFormUrl" value="/common/auth/login.jsf"/>
</bean>
The MyRedirectEntryPoint should extends AuthenticationProcessingFilterEntryPoint and override commence method
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
boolean ajaxRedirect = request.getHeader("faces-request") != null
&& request.getHeader("faces-request").toLowerCase().indexOf("ajax") > -1;
if (ajaxRedirect) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
response.sendError(403);
}
} else {
super.commence(request, response, authException);
}
}
Now you can simply bind a callback javascript function to catch the thrown 403 error and do what ever you want:
$(document).bind('ajaxError',
function(event, request, settings, exception){
if (request.status==403){
//do whatever you wanted may be show a popup or just redirect
window.location = '#{request.contextPath}/';
}
});

For me a simple client side javascript handler worked:
function handleAjaxExpired(xhr,status,args) {
// handler for "oncomplete" ajax callback
if ( xhr.responseXML.getElementsByTagName('error-name').length ) {
// "<error-name>" tag is present -> check for "view expired" exception
html = xhr.responseXML.getElementsByTagName('error-name')[0].innerHTML;
if ( html.indexOf('ViewExpiredException') > -1 ) {
// view expired exception thrown
// do something / reload page
if ( confirm('session expired -> reload page?') ) {
document.location.href=document.location.href;
}
}
}
}
This handler is called from "oncomplete" attribute in triggering UI element, e.g. here from a rowSelect event in a Primefaces datatable:
<p:ajax event="rowSelect" oncomplete="handleAjaxExpired(xhr,status,args)" />
Update: To avoid adding "oncomplete" attributes to every ajax-enabled element, this javascript code searches globally in all ajax responses for errors:
(function() {
// intercept all ajax requests
var origXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
this.addEventListener('load', function() {
handleAjaxExpired(this);
});
origXHROpen.apply(this, arguments);
};
})();
This code makes "oncomplete" attributes in PrimeFaces UI-elements obsolete.

Related

How to return JSON response for unauthorized AJAX calls instead of login page as AJAX response?

I have implemented Spring Security in my application. Whenever someone tries to access any url if authentication is required for these urls user will be redirected to login page. Now, if AJAX call is made for any such url I would like to return JSON response instead of login page's HTML as AJAX response. How can I do that ?
You have to create json for this i am doing here with .net
var url="url";
$.ajax({
type: "get",
dataType: "json",
data:url,
async: true,
url: "testCall",//this can be your api or any server side call
success: function (data) {
},
failure: function () {
alert(textStatus);
}
});
//here is server side code for creating json
[WebMethod(EnableSession = true)]
[ScriptMethod(UseHttpGet = true)]
public void testCall(string url)
{
Context.Response.Write()//here your will just hard code the json data
//it will receive by ajax success method.
}
Faced the same thing not long ago, came out with this solution.
You'll have to redefine the authentication entry point to handle the exception and returning a proper JSON response.
First create a class for your response. Needs to be a POJO.
public class MyErrorResponse {
// your stuff here, and getters / setters
}
Then go define the authentication entry point
public class MyBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
private List<HttpMessageConverter<Object>> messageConverters = new ArrayList<>();
private MediaType retrieveRequestMediaType(HttpServletRequest request) {
String accept = request.getHeader("accept");
if(Strings.isNullOrEmpty(accept))
accept = MediaType.APPLICATION_JSON_VALUE;
MediaType requestMediaType = MediaType.valueOf(accept);
return requestMediaType;
}
private HttpMessageConverter<Object> retrieveMessageConverter(List<HttpMessageConverter<Object>> messageConverters, Class<?> clazz, MediaType mediaType) {
for (HttpMessageConverter<Object> httpMessageConverter : messageConverters) {
if(httpMessageConverter.canWrite(clazz, mediaType)) {
return httpMessageConverter;
}
}
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.warn(String.format("Unauthorized access with session id '%s'", request.getSession().getId()));
MyErrorResponse esponse = new MyErrorResponse();
// populate your response object with all the info you need
MediaType mediaType = MediaType.APPLICATION_JSON;
try{
mediaType = retrieveRequestMediaType(request);
} catch(InvalidMediaTypeException imte) {
// log, do nothing
}
// getting the best fitting message converter, according to the "accept" header of the request
HttpMessageConverter<Object> httpMessageConverter = retrieveMessageConverter(messageConverters, MyErrorResponse.class, mediaType);
if(httpMessageConverter == null) {
log.info("Could not find specific handler. Using JSON.");
httpMessageConverter = retrieveMessageConverter(messageConverters, MyErrorResponse.class, MediaType.APPLICATION_JSON);
}
response.setStatus(HttpStatus.UNAUTHORIZED.value());
ServletServerHttpResponse serverHttpResponse = new ServletServerHttpResponse(errorResponse);
httpMessageConverter.write(response, mediaType, serverHttpResponse);
}
}
Once you got your bean set up, time to wire it up in the security context:
<beans:bean class="[fully qualified name of the entry point class]" id="myBasicAuthenticationEntryPoint">
<beans:property name="messageConverters">
<beans:list>
<!-- add message converters here -->
<!-- Spring provide lots of them, google it -->
</beans:list>
</beans:property>
</beans:bean>
<http use-expressions="true">
<http-basic entry-point-ref="myBasicAuthenticationEntryPoint" />
<!-- add other stuff here, if needed -->
</http>
Hope it helps

handle Exception in Spring Web Flow

Hello Friends Iam trying to handle
org.springframework.webflow.execution.repository.snapshot.SnapshotNotFoundException
this exception but iam failed to do so.
In this way i handle SnapshotNotFoundException.
<transition on-exception="org.springframework.webflow.execution.repository.snapshot.SnapshotNotFoundException"
to="exceptionHandler" />
That declarative Exception handling does not seem to work for internal Web Flow exceptions. For this particular case, we had to implement a custom FlowHandler.handleException().
Something like:
public class CustomFlowHandler extends AbstractFlowHandler
{
#Override
public String handleException(FlowException e, HttpServletRequest request,
HttpServletResponse response)
{
if (e instanceof FlowExecutionRestorationFailureException)
{
if (e.getCause() instanceof SnapshotNotFoundException)
{
// TODO return the desired location string. See javadoc for options
return "serverRelative:/missingSnapshot.html";
}
}
return super.handleException(e, request, response);
}
}
And in Spring configuration file:
<!-- custom flow handler -->
<bean name="your-flow-name" class="yourpackage.CustomFlowHandler"/>

JSF 2 - Display dialog upon session timeout on ajax calls

I am new to JSF and I am working on handling session timeout for a JSF application.
I am trying to get the code to work for ajax calls and not able to achieve that so far. I have tried two approaches:
Approach 1: SessionListener (for cleanup work) and SessionFilter (for filtering every request and checking if session timed out)
My Code snippet for Filter:
if ((request instanceof HttpServletRequest)
&& (response instanceof HttpServletResponse)) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// is session expire control required for this request? (not required for home page or timeout page or JSF resources)
if (isSessionControlRequired(httpServletRequest)) {
// is session invalid?
if (isSessionInvalid(httpServletRequest)) {
String timeoutUrl = httpServletRequest.getContextPath() + "/timeout.html";
logger.info("session is invalid! redirecting to timeoutpage : " + timeoutUrl);
//httpServletResponse.sendRedirect(timeoutUrl);
//final FacesContext facesContext = FacesContext.getCurrentInstance();
//final ExternalContext externalContext = facesContext.getExternalContext();
//externalContext.dispatch("/start.jsp");
RequestDispatcher rd = httpServletRequest.getRequestDispatcher("/timeout.html");
rd.forward(httpServletRequest, httpServletResponse);
return;
}
}
}
chain.doFilter(request, response);
Approach 2: CustomeExceptionHandler handling ViewExpiredException
Code snippet of handle() :
for (final Iterator<ExceptionQueuedEvent> it = getUnhandledExceptionQueuedEvents().iterator(); it.hasNext();) {
Throwable t = it.next().getContext().getException();
while ((t instanceof FacesException || t instanceof ELException) && null != t.getCause()) {
t = t.getCause();
}
if (t instanceof ViewExpiredException) {
final FacesContext facesContext = FacesContext.getCurrentInstance();
final ExternalContext externalContext = facesContext.getExternalContext();
final Map<String, Object> requestMap = externalContext.getRequestMap();
NavigationHandler nh = facesContext.getApplication().getNavigationHandler();
try {
final String viewId = ((ViewExpiredException) t).getViewId();
String message = "View has expired. " + viewId;
logger.error(message);
requestMap.put("errorMsg", message);
try {
requestMap.put("currentViewId", viewId);
nh.handleNavigation(facesContext, null, "/timeout.html");
facesContext.renderResponse();
// Force JSF to render the error page in its entirety to the ajax response.
//facesContext.setViewRoot(facesContext.getApplication().getViewHandler().createView(facesContext, "/timeout.html"));
//facesContext.getPartialViewContext().setRenderAll(true);
//facesContext.renderResponse();
//RequestContext rc = RequestContext.getCurrentInstance();
//rc.execute("addTitleDialog.show()");
//externalContext.dispatch("/start.jsp");
}
catch (final Exception e) {
logger.error("Cannot dispatch to /start.jsp");
}
facesContext.responseComplete();
}
finally {
it.remove();
}
}
else {
logger.error(t.getMessage(), t);
}
}
getWrapped().handle();
}
Both these approaches work for non-ajax POST calls but not for ajax calls. When I run my app in debug mode, I can step through all the statements for ajax calls also, which gives me an idea that the control does come to my code, executes it but for some reason, nothing happens on the UI.
I have been trying to redirect user to a timeout page but the ideal thing would be to display a JSF dialog and upon hitting 'OK' take user to Home Screen (My app does not have a login screen.)
I have a basic questions also, is view expiring exactly same as session timeout?
Any help would be much appreciated, thanks,
Swati.

Invalidating session with CDI+JSF not working

I'm trying to implement a logout in my application, so I made this:
public String logout(){
try{
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext ex = facesContext .getExternalContext();
ex.invalidateSession();
return "success";
}catch(Exception e){
return "error";
}
}
But when I check if the user is logged, it says yes:
public class AuthenticateListener implements PhaseListener {
#Override
public void afterPhase(PhaseEvent event) {
AuthorizedUser authorized = (AuthorizedUser) Util.getHandler("authorized");
if (authorized.getUser() == null) {
System.out.println("Not Logged");
} else {
System.out.println("Logged");
}
}
#Override
public void beforePhase(PhaseEvent event) {
// TODO Auto-generated method stub
}
#Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
}
Am I missing something? Shouldn't I get a new instance of AuthorizedUser (sessionScoped) after invalidating my session?
EDIT: Adding the getHandler, if someone needs it ;)
public static Object getHandler(String handlerName) {
FacesContext facesContext = FacesContext.getCurrentInstance();
ELContext elContext = facesContext.getELContext();
ELResolver resolver = facesContext.getApplication().getELResolver();
Object uh = resolver.getValue(elContext, null, handlerName);
return uh;
}
The session is still available in the current request-response. It's not available anymore in the next request. You need to send a redirect after the invalidate so that the browser will be instructed to send a new request on the given URL.
return "success?faces-redirect=true";
Or if you're still using old fashioned navigation cases (the return values namely suggests that; it's strange to have a view with filename "success"), then add <redirect/> to the navigation case instead.
If that still doesn't work, then the bug is in how you're storing the user in session. For example, it's instead actually been stored in the application scope which may happen when you mix CDI #Named with JSF #SessionScoped, or when you assigned the logged-in user as a static variable instead of an instance variable.
See also:
How to invalidate session in JSF 2.0?
Performing user authentication in Java EE / JSF using j_security_check
Use this piece of code inside logout method:
HttpSession oldsession = (HttpSession) FacesContext
.getCurrentInstance().getExternalContext().getSession(false);
oldsession.invalidate();
This will work. Let me know please if it was helpful for you.

Session management in gwt

I am using GWT for my client side application. However, I am not sure how I can handle session management. The GWT application resides on one page, all server calls are done via AJAX. If a session expires on the server. let's assume the user didn't close the browser, and sending some request to server using RPC, how could my server notify the application that the session has expired and that the client side portion should show the login screen again?My sample code :
ContactDataServiceAsync contactDataService = GWT
.create(ContactDataService.class);
((ServiceDefTarget) contactDataService).setServiceEntryPoint(GWT
.getModuleBaseURL()
+ "contactDatas");
contactDataService.getContact(2,
new AsyncCallback<ContactData>() {
public void onFailure(Throwable caught) {
//code to show error if problem in connection or redirect to login page
}
public void onSuccess(ContactData result) {
displayContact(result);
}
});
If session expires only it has to show login screen, otherwise it wants to show some error using Window.alert().
How to do this and what are all the codes needed in server side and client side?
You could have the server throw an AuthenticationException to the client in case the user has been logged out.
This will be catched in the callbacks onFailure method, which then can redirect the user to the login-page.
Edit:
AuthenticationException is not a standard exception of course, i was just making an example. It might be best to stick with the standard exceptions.
To try if you caught an specific exception you could use the instanceof operator
public void onFailure(Throwable e) {
if(e instanceof AuthenticationException) {
redirecttoLogin();
}
else {
showError(),
}
}
This does not directly apply to those using RPC, but for those of you who are not using RPC, you should send a HTTP 401 from the server. Then you can check that status code in your RequestBuilder callback.
Client: All Callbacks extend a Abstract Callback where you implement the onFailur()
public abstract class AbstrCallback<T> implements AsyncCallback<T> {
#Override
public void onFailure(Throwable caught) {
//SessionData Expired Redirect
if (caught.getMessage().equals("500 " + YourConfig.ERROR_MESSAGE_NOT_LOGGED_IN)) {
Window.Location.assign(ConfigStatic.LOGIN_PAGE);
}
// else{}: Other Error, if you want you could log it on the client
}
}
Server: All your ServiceImplementations extend AbstractServicesImpl where you have access to your SessionData. Override onBeforeRequestDeserialized(String serializedRequest) and check the SessionData there. If the SessionData has expire then write a spacific error message to the client. This error message is getting checkt in your AbstrCallback and redirect to the Login Page.
public abstract class AbstractServicesImpl extends RemoteServiceServlet {
protected ServerSessionData sessionData;
#Override
protected void onBeforeRequestDeserialized(String serializedRequest) {
sessionData = getYourSessionDataHere()
if (this.sessionData == null){
// Write error to the client, just copy paste
this.getThreadLocalResponse().reset();
ServletContext servletContext = this.getServletContext();
HttpServletResponse response = this.getThreadLocalResponse();
try {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
try {
response.getOutputStream().write(
ConfigStatic.ERROR_MESSAGE_NOT_LOGGED_IN.getBytes("UTF-8"));
response.flushBuffer();
} catch (IllegalStateException e) {
// Handle the (unexpected) case where getWriter() was previously used
response.getWriter().write(YourConfig.ERROR_MESSAGE_NOT_LOGGED_IN);
response.flushBuffer();
}
} catch (IOException ex) {
servletContext.log(
"respondWithUnexpectedFailure failed while sending the previous failure to the client",
ex);
}
//Throw Exception to stop the execution of the Servlet
throw new NullPointerException();
}
}
}
In Addition you can also Override doUnexpectedFailure(Throwable t) to avoid logging the thrown NullPointerException.
#Override
protected void doUnexpectedFailure(Throwable t) {
if (this.sessionData != null) {
super.doUnexpectedFailure(t);
}
}

Resources