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?"
Related
I have the following issue:
I have a page on which I create a offer (page1).
Now I have a button on page1 to select a customer (page2).
So I press the button and my new page (page2) appears to select a customer.
With another button I select the customer and redirect to my first page (offer page). But now all my entered values are not there anymore.
I tried the following:
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.context.SessionScoped;
import javax.faces.view.ViewScoped;
My button is the following:
<p:commandButton value="Select customer" ajax="true" process="#all"
actionListener="#{offerEditController.doSelectCustomerForDocument}"
update=":addOfferForm, growl" immediate="true">
</p:commandButton>
And here my method for go to the page2:
public void doSelectCustomerForDocument() {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.redirect(ec.getRequestContextPath() + Constants.CUSTOMER_LIST
+ "?create_document=/offerEdit.jsf&document_id=" + offer.getId());
}
#SessionScoped works not for all inputFields, e.g.
For
<p:inputText id="offer_subject"
value="#{offerEditController.offer.title}" >
<p:ajax events="blur" update="offer_subject_panel"
global="false" />
</p:inputText>
Any ideas how can I solve this? I know I could use a p:dialog, but I donĀ“t like this.
Thank you for all help.
The better ideia would use a p:dialog, because it fits perfectly on your case but if you don't like it, you should improve the way you call your pages. In your case, when you do the following:
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.redirect(ec.getRequestContextPath() + Constants.CUSTOMER_LIST
+ "?create_document=/offerEdit.jsf&document_id=" + offer.getId())
you force the page to reload and recall the #PostConstruct method, cleaning the whole backing bean (controller). That's why you lose the values entered.
If you want to keep this approach of redirection you have to store the page data somewhere before it get cleaned. I suggest storing at requestContext passing parameters using JSON from page2 to page1 using <p:remoteCommand>, then, when you reload on #PostConstruct method you look for parameters in the requestContext and reset the fields.
Put this on Page2:
<p:commandButton id="buttonOnPage2" onclick = "remoteCommandFunction([{name:'value1',value:'#{bean.value1}'},{name:'value2',value:'#{bean.value2}'}])"/>
<p:remoteCommand name="remoteCommandFunction" actionListener="#{bean.storeData()}"/>
On your bean:
public void storeData(){
Map<String, String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
if(params.get("value1") !=null && !params.get("value1").isEmpty())
this.value1= params.get("value1");
if(params.get("value2") !=null && !params.get("value2").isEmpty())
this.value2= params.get("value2");
}
You have to ensure that storeData() method is called after #PostConstruct, if its called after post construct, everything will be null. If this happens you should put the storeData() method in a #SessionScoped bean and retrieve it on your #ViewScoped inside #PostConstruct method like this:
#PostConstruct
public void init(){
...
this.value1 = getSessionScopedBean().getValue1();
this.value2 = getSessionScopedBean().getValue2();
getSessionScopedBean().setValue1(null);
getSessionScopedBean().setValue2(null);
}
I have already a login functional screen built using JSF 2.2 and PrimeFaces 5.3. However, I have a problem. Say the user enters username and password but one of them is incorrect. When that happens, I display an error message to the user: "Username and/or password is incorrect". The problem is that at that moment I am storing the session. I ONLY want to store a session once the user successfully is logged in. My Login form looks like this:
<f:metadata>
<f:viewParam name="tipo" value="#{loginTipoController.tipo.tipo}" />
</f:metadata>
<h:form id="login">
<p:focus context="login" />
<p:graphicImage url="img/logog.JPG" width="448" height="119" />
<p>Digite su usuario y password.</p>
<p:outputLabel for="usuario" value="Usuario:" />
<p:inputText id="usuario" value="#{loginController.login.username}" />
<p:outputLabel for="password" value="Password:" />
<p:password id="password" value="#{loginController.login.password}" />
<p:messages autoUpdate="true" for="usuarioPassword" />
<p:commandButton value="Login" action="#{loginController.login()}" />
<p:button value="Regresar" outcome="index.jsf" />
</h:form>
The controller class (loginController), looks like this:
#ManagedBean
#SessionScoped
public class LoginController {
private Login login;
private LoginTipo loginTipo;
private LoginService service;
public Login getLogin() {
return login;
}
public void setLogin(Login login) {
this.login = login;
}
public LoginService getService() {
return service;
}
public void setService(LoginService service) {
this.service = service;
}
#PostConstruct
public void init()
{
login = new Login();
service = new LoginService();
}
public String login()
{
FacesContext facesCont = FacesContext.getCurrentInstance();
loginTipo = facesCont.getApplication().evaluateExpressionGet(facesCont,
"#{loginTipoController.tipo}", LoginTipo.class);
login = service.login(login.getUsername(), login.getPassword(), loginTipo.getTipo());
if(login.getMensaje_id() != 0)
{
FacesContext.getCurrentInstance().addMessage("usuarioPassword",
new FacesMessage(login.getMensaje()));
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
return "";
}
else
{
return login.getMensaje();
}
}
//logout method
public void logout()
{
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
try
{
ec.redirect(ec.getRequestContextPath() + "/index.jsf");
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
I am not putting the LoginService() class code because it is simply going to the database and checking if the user exists or not. I just want to know what do I need to do so that the session is only stored when the user successfully logs in.
Notice that on the login() method of the controller class, when the user fails to log in, I display an error message. After that, I have the following piece of code:
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.invalidateSession();
I am destroying the session. I want to avoid to have to do that!
I understand that you basically want to clear out the login form on login fail. You could just clear out the bean properties representing form data.
login = new Login();
loginTipo = null;
The bean itself does not necessarily need to be session scoped. It can be request scoped and you could manually put the user object in session. I understand Login represents the user. In that case, once the login is successful, do as follows:
ec.invalidateSession();
ec.getSessionMap().put("login", login);
return "/userhome?faces-redirect=true";
The logged-in user will be available in the session scope as #{login}.
Explicitly invalidating the session right before login is a good security practice to avoid session fixation attacks. Invalidating it on every login fail is indeed unnecessary.
I need to send some parameters from one xhtml to another, but I don't want these parameters to appear in the URL. How to do that? I could use p:commandLink, but then I don't know how to open the destination page from the bean method. The destination page should be accessible by friendly URL, not by xhtml name.
This code will open the page /users/view. How can I send parameters without them appearing in the URL?
<h:outputLink value="/users/view">
<h:outputText value="#{entry.employee}" />
</h:outputLink>
Ignoring the strange design, you could use put the data in the flash scope and send a redirect in a <h:commandLink> action method:
public void view() throws IOException {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.getFlash().put("employee", employee);
ec.redirect(ec.getRequestContextPath() + "/users/view");
}
And then in the backing bean associated with the target page:
#PostConstruct
public void init() {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
employee = (Employee) ec.getFlash().get("employee");
}
See also:
Pass an object between #ViewScoped beans without using GET params
What is the difference between redirect and navigation/forward and when to use what?
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)
I'm performing url redirects between primefaces mobile pages (pm:page). For instance from login.jsf to /secure/myPage.jsf, both pm:pages. After successful authentication the user should be redirect to myPage.jsf. The login is triggered like this:
<pm:commandButton value="login" update="messages"
actionListener="#{loginbean.doLogin}" >
<f:param name="targetUrlParam" value="defaultTarget" />
</pm:commandButton>
and the redirect within the method:
public void doLogin(ActionEvent e) throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext ec = context.getExternalContext();
try {
HttpServletRequest req = (HttpServletRequest) ec.getRequest();
Authentication authentication = SecurityContextHolder.
getContext().getAuthentication();
... // Authentication stuff with Spring Security
try {
HttpSession session = req.getSession(false);
String cp = ec.getRequestContextPath();
String redirectUrl = cp;
... //performing some filtering depending on Roles and target-urls
}
String encodedURL = ec.encodeResourceURL(redirectUrl);
((HttpServletResponse) ec.getResponse()).sendRedirect(encodedURL);
} catch (AuthenticationException ae) {
UtilBean.addErrorMessage("bad_credential");
}
Unfortunately the redirect doesn't occur! It might have to do with the lifecycle of primefaces mobile 3.0M3 because everything works fine with normal JSF pages.
Any suggestions? Thanks
This is not entirely the right way to send a redirect in JSF. I'm not sure why it works in "normal" JSF (that should fail over there as well!). You basically need to call FacesContext#responseComplete() after the redirect to instruct JSF that it should not navigate to the default outcome. However, much better is to perform the redirect using ExternalContext#redirect() as it will do that implicitly.
So in your case, replace
((HttpServletResponse) ec.getResponse()).sendRedirect(encodedURL);
by
ec.redirect(encodedURL);