可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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
回答1:
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?
回答2:
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>
回答3:
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>
回答4:
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
回答5:
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}/\';
}
});