Navigate conditionally depending on whether login is successful or not - jsf

I have this login form:
<h:form>
<h:panelGrid columns="2" >
<h:outputLabel for="username" value="Login:"/>
<h:inputText id="username" value="#{userController.userName}" required="true"/>
<h:outputLabel for="password" value="#{msg.password}"/>
<h:inputSecret id="password" value="#{userController.password}" required="true"/>
<h:column/>
<h:commandButton value="#{msg.login}" action="#{userController.login}"/>
</h:panelGrid>
</h:form>
With this backing bean:
#ManagedBean(name = "userController")
#SessionScoped
public class UserController {
private String userName = "";
private String password = "";
//getter, setters
public String login(){
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getRequest();
try {
request.login(userName, password);
} catch (ServletException e) {
}
return "next-page.xhtml"; //if login processes is proper, i redirect to next page
}
}
I read in Best practices in JSF: model, actions, getters, navigation, phaselisteners that
I always post back to the same view (return null or void and then render/include the result conditionally. For page-to-page navigation I don't use POST requests (for which navigation cases are mandatory) simply because that's plain bad for UX (User eXperience; browser back button doesn't behave as it should and URL's in browser address bar are always one step behind because it are by default forwards, not redirects) and SEO (Search Engine Optimization; searchbots doesn't index POST requests). I just use outputlinks or even plain HTML elements for page-to-page navigation.
So, what should I do when my login is proper and I want to immediately redirect to next-page.xhtml?

In the end of the try, perform the navigation with ?faces-redirect=true so that a redirect is performed. In the catch, return null so that it stays in the same page.
try {
request.login(userName, password);
return "next-page.xhtml?faces-redirect=true";
} catch (ServletException e) {
context.addMessage(null, new FacesMessage("Unknown login"));
return null;
}
For the sake of completeness, I added a faces message on login failure, otherwise the enduser would have no clue why the page seemingly reloads itself without any form of feedback. This message will be shown in a <h:messages globalOnly="true">.
See also:
Performing user authentication in Java EE / JSF using j_security_check (the 2nd half of the answer)
How to navigate in JSF? How to make URL reflect current page (and not previous one)

Related

Show alert() after executing managed bean action method

I've a command link:
<h:commandLink value="Delete"
onclick="return confirm('Are you sure?');"
action="#{bean.deleteDestination(destination, '/destination/show')}" />
It invokes this managed bean action method:
public String deleteDestination(Destination selected, String action) {
List<Flight> flights = getEjbFlightFacade().findFlightWithDestination(selected);
if (flights.isEmpty()) {
getEjbDestinationFacade().remove(selected);
setDestinations(getEjbDestinationFacade().findAll());
}
else {
// Here need to show an alert() that user can't remove the item.
}
return action;
}
As indicated by the comment, I'd like to show an alert() that the enduser can't remove the item. How can I achieve that?
Let JSF conditionally render the desired script based on a bean property.
E.g.
this.undeleteable = true;
<h:outputScript rendered="#{bean.undeleteable}">
alert("You can't delete it.");
</h:outputScript>
The canonical way, however, is to just show a (global) faces message.
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, new FacesMessage("You can't delete it."));
<h:messages globalOnly="true" />
Alerts are namely soo 1990.

Pass parameters from bean to navigated view

What I do now:
call an action method
<p:commandButton value="Fine"
action="#{dtIndexBean.forwardAction}"
styleClass="ui-priority-primary"
ajax="false">
<f:param name="result" value="fine"/>
</p:commandButton>
in forwardAction method, validate the input and forward to another view if validation goes fine
String result = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("result");
if (result.equals("wrong")) {
System.out.println("WRONG!!");
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN,
"", "WRONG!!"));
return "";
} else{
paramBean.putForwardParameter("message", "Hello!");
return "forwarded.xhtml";
}
I need to pass some parameters to forwarded view, now I put them in a session bean (here its name is "paramBean") so I can read them in the backing bean of the forwarded view:
String message;
#PostConstruct
private void init(){
message = paramBean.getParameter("message");
paramBean.clearForwardParameter();
}
I only feel that this is clumsy due to the presence of an additional bean. Is there a less clumsy way to do this?

Problem using HttpServletRequest authenticate() within JSF lifecycle

I'm trying to use HttpServletRequest authenticate within a JSF managed bean to implement fine-grained authentication, depending on the particular object requested.
When I call authenticate within a preRenderView event listener, if authenticate causes a redirect to the login page, an exception is thrown. I can't call responseComplete after the call to authenticate, because FacesContext.getCurrentInstance returns null. Is it possible to call authenticate() at all in JSF, or do I have to use a ServletFilter? HttpServletRequest login and logout work within JSF, so I think it's reasonable to assume authenticate should work. Is this a bug in Mojarra JSF?
Here is my code:
The page where event listener is registered:
<ui:composition template="/template.xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ice="http://www.icesoft.com/icefaces/component">
<ui:param name="pageTitle" value="Text Clustering Home Page"/>
<ui:define name="metadata">
<f:metadata>
<f:event type="preRenderView" listener="#{permissionBean.preRender}"/>
</f:metadata>
</ui:define>
<ui:define name="body">
Text Clustering Home Page
<h:form>
<h:panelGrid columns="1">
<ice:outputText rendered="#{loginService.loggedIn}" value="Logged in User: #{loginService.currentUser.username}"/>
<h:link rendered="#{!loginService.loggedIn}" value="Register" outcome="Register"/>
<h:commandLink value="Logout" rendered="#{loginService.loggedIn}" action="#{loginService.logout}"/>
<h:link value="Login" rendered="#{!loginService.loggedIn}" outcome="Login"/>
</h:panelGrid>
</h:form>
The bean that contains the listener:
#Named
#RequestScoped
public class PermissionBean implements java.io.Serializable {
public void preRender(CompenentSystemEvent event) {
HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
HttpServletResponse response = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
try {
if (!request.authenticate(response)) {
System.out.println("After authenticate, context = " +FacesContext.getCurrentInstance());
if (FacesContext.getCurrentInstance()!=null) {
FacesContext.getCurrentInstance().responseComplete();
}
}
} catch (Exception e) { // may throw ServletException or IOException
System.out.println("EXCEPTION calling authenticate");
e.printStackTrace();
}
}
}
The call to authenticate() doesn't throw an exception, but if it returns false, then FacesContext.getCurrentInstance() also returns null, and after the method exits
I get this error:
javax.enterprise.resource.webcontainer.jsf.context|_ThreadID=23;_Thread
Name=Thread-3;|Exception when handling error trying to reset the
response.
java.lang.NullPointerException
at
com.sun.faces.facelets.tag.jsf.core.DeclarativeSystemEventListener.processEvent(EventHandler.java:126 )
Thanks,
Ellen
From HttpServletRequest#authenticate() javadoc:
boolean authenticate(HttpServletResponse response)
throws java.io.IOException,
ServletException
Use the container login mechanism configured for the ServletContext to authenticate the user making this request.
This method may modify and commit the argument HttpServletResponse.
(emphasis mine)
The container's implementation will redirect the response to the configured login page when the user has not been authenticated. This has likely also caused the HttpServletRequest to be destroyed and thus the current FacesContext instance to be completely disappeared.
Your best bet is to let JSF perform the checking the presence of the logged-in user and doing the redirect instead of the container. True, this requires duplicating the login page location in the JSF side, but I don't see other ways which are workable in JSF without moving the task to a Filter.
public void preRender(CompenentSystemEvent event) {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
if (ec.getUserPrincipal() == null) {
ec.redirect(ec.getRequestContextPath() + "/login.xhtml");
}
}
The ExternalContext#redirect() will already implicitly call FacesContext#responseComplete(), so you don't need to do it yourself.

logout commandLink logs the user out, but doesn't update the page

I have a logout link in my JSF app that invalidates the session to log the user out. It works but it doesn't redirect the user to the logon page. It stays on the same page. If I try to access the same page again it does direct back to the logon. I want this to happen immediately.
logout link:
<h:form>
<h:panelGroup id="loginout">
<h:outputText value="#{todoController.loggedInUser}" />
<h:commandLink value="logout" action="#{todoController.logout}" />
</h:panelGroup>
</h:form>
logout code:
public String logout()
{
System.out.println("testing logout");
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
final HttpServletRequest r = (HttpServletRequest)ec.getRequest();
r.getSession( false ).invalidate();
return "../login.html?faces-redirect=true";
}
This can happen if the outcome is invalid. login.html doesn't seem to be a JSF page, so JSF navigation will simply fail.
You want to use ExternalContext#redirect() instead.
public void logout() throws IOException {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
ec.redirect("../login.html");
}
Note that the above also demonstrates a more JSF-ish way to invalidate the session. Whenever you need to haul the raw javax.servlet.* API from under the JSF hoods, you should always ask yourself twice: "Is there really not a JSF-provided API for this?"

Confirmation link Email in JSF

How do you make a link which you can email to users to confirm their email address is clicked in JSF? i.e. once they click on the link their account will be activated.
Assuming you're already on JSF 2.0, you could grab #ManagedProperty and #PostConstruct.
#ManagedBean
#RequestScoped
public class Activation {
#ManagedProperty(value="#{param.key}")
private String key;
private boolean valid;
#PostConstruct
public void init() {
valid = check(key); // And auto-login if valid?
}
// ...
}
and then in JSF which is accessed by http://example.com/activate.jsf?key=somelonggeneratedkey
<h:panelGroup layout="block" rendered="#{activation.valid}">
<p>Your account is successfully activated!</p>
<p><h:link outcome="home">Go to home page</h:link></p>
</h:panelGroup>
<h:panelGroup layout="block" rendered="#{!activation.valid}">
<p>Activation failed! Please enter your email address to try once again.</p>
<h:form>
...
</h:form>
</h:panelGroup>
You can implement it by creating a page (.jsp for ex) that has:
<f:view beforePhaseListener="#{userActivationController.performActivation}">
(this is for facelets; for jsp the attribute is just beforePhase). And then, in the managed bean's method use FacesContext.getCurrentContext().getExternalContext().getParameterMap() to obtain the request patameters and get the activation code, which is passed like:
http://yoursite.com/activate.jsp?code=54gfd54tgdgfd

Resources