Das Problem: Es wird gar keine Exception geworfen wenn ich z. B. versuche nach einem SessionTimeout einen bereiets geladenen DataTable weiter zu blättern. Folglich kann der Exceptionhandler auch keinen Redirect machen.
Geprüft habe ich das mit diversen Sysouts - Der Handler wird aufgerufen aber der ExceptionStack ist leer .hasNext() = false.
packagede.firma.onlineshop.filter;importjavax.faces.context.ExceptionHandler;importjavax.faces.context.ExceptionHandlerFactory;publicclassViewExpiredExceptionExceptionHandlerFactoryextendsExceptionHandlerFactory{privateExceptionHandlerFactory parent;publicViewExpiredExceptionExceptionHandlerFactory(ExceptionHandlerFactory parent){this.parent = parent;}@OverridepublicExceptionHandlergetExceptionHandler(){ExceptionHandler result = parent.getExceptionHandler();
result =newViewExpiredExceptionExceptionHandler(result);return result;}}
ExceptionHandler
Java:
packagede.firma.onlineshop.filter;importjava.util.Iterator;importjava.util.Map;importjavax.faces.FacesException;importjavax.faces.application.NavigationHandler;importjavax.faces.application.ViewExpiredException;importjavax.faces.context.ExceptionHandler;importjavax.faces.context.ExceptionHandlerWrapper;importjavax.faces.context.FacesContext;importjavax.faces.event.ExceptionQueuedEvent;importjavax.faces.event.ExceptionQueuedEventContext;publicclassViewExpiredExceptionExceptionHandlerextendsExceptionHandlerWrapper{privateExceptionHandler wrapped;publicViewExpiredExceptionExceptionHandler(ExceptionHandler wrapped){this.wrapped = wrapped;}@OverridepublicExceptionHandlergetWrapped(){returnthis.wrapped;}@Overridepublicvoidhandle()throwsFacesException{System.out.println("Listener is called...");for(Iterator<ExceptionQueuedEvent> i =getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();){ExceptionQueuedEvent event = i.next();ExceptionQueuedEventContext context =(ExceptionQueuedEventContext) event.getSource();Throwable t = context.getException();if(t instanceofViewExpiredException){ViewExpiredException vee =(ViewExpiredException) t;FacesContext fc =FacesContext.getCurrentInstance();Map<String,Object> requestMap = fc.getExternalContext().getRequestMap();NavigationHandler nav =
fc.getApplication().getNavigationHandler();try{// Push some useful stuff to the request scope for// use in the page
requestMap.put("currentViewId", vee.getViewId());
nav.handleNavigation(fc,null,"viewExpired");
fc.renderResponse();}finally{
i.remove();}}}// At this point, the queue will not contain any ViewExpiredEvents.// Therefore, let the parent handle them.getWrapped().handle();}}
Weder auf der Console, im Log oder im ExceptionStack eine Exception wenn die Session in den TimeOut geht...
auf anhieb finde ich jetzt nichts außer das der Eintrag fehlt wohin deine App navigieren soll bei dem Kommando 'viewExpired'. Die Frage ist wird die Session jemals erstellt? Ich denke du hast testweise den session timeout auf 1 gestellt. Welche Sicherheitsimpl. verwendest du denn? JAAS? Vielleicht fängt der Filter die Exception vorher ab.
+ du mappst das FacesServlet auf *.xhtml -> besser wäre *.jsf oder /faces/*.
Testweise kannst du noch eine Session listener rauf setzen um zu sehen wann die session genau abläuft.
Haha, genau das hab ich jetzt auch probiert bevor ich hier rein geschaut habe ,-)
Funktioniert aber leider nicht (ganz).
Die Lösung des Problems bestand erstmal darin, dass der Tomcat gar keine ViewExpiredException werfen kann, da es sich um Ajax-Requests aus Primefaces handelt. In meinem Szenario war die Lösung die Folgende:
Alles was nicht Ajax ist, kann ich getrost aussen vor lassen, da sich die User bei mir anmelden müssen.
Inhalte von Seiten die loggedout aufgerufen werden können sind egal. In den gesicherten Bereichen wird man bei einem HttpRequest sowieso wieder zur Login-Seite geleitet wenn die Session abgelaufen ist.
Das Problem waren nur die Ajax-Requests. Da bleibt Primefaces dann einfach stehen.
Ich habe probiert @WebListener und @WebFilter. Bei ersterem ist das redirecten sehr schwer, letzteres hört auf zu funktionieren wenn die Session abgelaufen ist.
Die Lösung war ein PhaseListener mit entsprechendem Eintrag in der faces-config-xml.
Zuerst wird überprüft ob es sich um einen Ajax-Request handelt und dann ob die Session noch valid ist.
Falls nicht wird über FacesContext.getCurr[...]redirect("/index.jsf") umgeleitet. Und das funktioniert jetzt wunderbar.
Dadurch kommt auch auf den Seiten auf denen man nicht eingeloggt ist nur ein Redirect zu stande, wenn ein Ajax-Request ausgeführt wird.
Die Lösung war ein PhaseListener mit entsprechendem Eintrag in der faces-config-xml.
Zuerst wird überprüft ob es sich um einen Ajax-Request handelt und dann ob die Session noch valid ist.
Falls nicht wird über FacesContext.getCurr[...]redirect("/index.jsf") umgeleitet. Und das funktioniert jetzt wunderbar.
Dadurch kommt auch auf den Seiten auf denen man nicht eingeloggt ist nur ein Redirect zu stande, wenn ein Ajax-Request ausgeführt wird.
Kannst du das vielleicht noch etwas genauer ausführen? Das klingt nämlich genau nach dem, was ich momentan suche ... ich weiß nur trotzdem noch nicht so wirklich, wie ich das nun umzusetzen hätte.
Soweit bin ich jetzt - der Phaselistener muss in der faces-config.xml konfiguriert werden.
Allerdings funktioniert der jetzt "nicht mehr" (hatte ich aber schon mal) auf der Startseite. In einem gesicherten Bereich funktioniert er super.
Das Problem ist, dass beim ersten Aufruf der App noch keine Session existiert.
if session == null funktioniert dann leider nicht. isRequestedSessionIdValid funktioniert dann natürlich auch nicht. Hier muss ich noch rausfinden welchen Wert die jeweiligen Vars zu den relevanten Zeitpunkten haben.
Ein Filter hat nicht funktioniert, weil der bei mir nicht mehr funktionierte sobald die Session abgelaufen war. Ein Weblistener mit Annotations hat auch nicht mehr funktioniert sobald die Session weg war.
Falls du das Problem mit dem ersten Aufruf gelöst bekommst wäre ich für einen Tip auch dankbar...
publicclassSecurityPhaseListenerimplementsPhaseListener,Serializable{@OverridepublicvoidafterPhase(PhaseEvent arg0){finalFacesContext facescontext = arg0.getFacesContext();// not exist yet (non-faces-request) if(facescontext.getViewRoot()==null){
facescontext.getApplication().getNavigationHandler().handleNavigation(facescontext,null,"index");// ignore other phases
facescontext.renderResponse();}else{// current view identifierfinalString viewId = facescontext.getViewRoot().getViewId();finalboolean loginPage = viewId.lastIndexOf("index")>-1;// JAASfinalboolean login = facescontext.getExternalContext().getRemoteUser()!=null;// or (facescontext.getExternalContext().getSessionMap().get("security-token") != null);// not login page and user is not login - redirect to login pageif(!loginPage &&!login){
facescontext.getApplication().getNavigationHandler().handleNavigation(facescontext,null,"index");// ignore other phases
facescontext.renderResponse();// login page and user is login - redirect to start page}elseif(loginPage && login){
facescontext.getApplication().getNavigationHandler().handleNavigation(facescontext,null,"start");// ignore other phases
facescontext.renderResponse();}}// go on}/**
* @see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
*/@OverridepublicvoidbeforePhase(PhaseEvent arg0){// nothing to do -> view will be restore or new created}/**
* @see javax.faces.event.PhaseListener#getPhaseId()
*/@OverridepublicPhaseIdgetPhaseId(){// before the view will be createdreturnPhaseId.RESTORE_VIEW;}}
faces-config.xml
[XML]
<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" Java EE : XML Schemas for Java EE Deployment Descriptors
http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<lifecycle>
<phase-listener>de.my.package.SecurityPhaseListener</phase-listener>
</lifecycle>
<navigation-rule>
<from-view-id>/*</from-view-id>
<navigation-case>
<from-outcome>index</from-outcome>
<to-view-id>/index.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<from-outcome>start</from-outcome>
<to-view-id>/public/start.xhtml</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
</faces-config>
Im Prinzip macht dein Code genau das was er soll, ich war auf einem leicht anderen Weg auch schon soweit, aber das Problem ist, dass das nur funktioniert wenn man in einem gesicherten Bereich unterwegs ist.
Wenn ich die ganz normale Startseite aufrufe, und dort z. B. einen DataTable mit Ajax habe funktioniert der auch nur, so lange die Session existiert. Läuft die Session ab, funktioniert auch der DataTable nicht mehr (z. B. Blättern).
Dafür suche ich eine Lösung, habe aber noch keine gefunden.
Eine Möglichkeit wäre natürlich die Beans RequestScoped zu machen, oder? Aber ich hätte halt schon gerne, das z. B. Filtereinstellungen erhalten bleiben solange die Session läuft.
Die User können bei mir die App teilweise schon benutzen ohne sich anmelden zu müssen, müssten sich alle automatisch anmelden würde deine Lösung super funktionieren...