I'm developing a web application with JSF 2.0. I implemented login through a managed bean (LoginHandler) and check if the user is logged in with a filter.
Now I got the request to be able to login to the application by sending a request with username and password as parameters. Which is the best way to do that?
I tried using f:metadata and a preRenderView event, but it seems a filter is the better solution? I also tried writing an HTTPFilter on url of the login page, but the problem is I need to store user data in my managed bean, and when I first access the application I don't have a session from which to get my manage bean.
Otpion 1: f:metadata and action in managedbean LoginHandler:
on login page:
<f:metadata>
<f:viewParam name="username" value="#{loginManager.username}"/>
<f:viewParam name="password" value="#{loginManager.password}"/>
<f:event type="preRenderView" listener="#{loginManager.singleSignOn}"/>
</f:metadata>
The method singleSignOn in LoginHandler checks if username and password are set. If that's the case, it does the same logic as submitting the login form (it calles the same method), and if the login is successful, it forwards to the welcome page. Otherwise it returns null (and the login page is displayed)
Otpion 2: filter:
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
String username = request.getParameter("username");
String password = request.getParameter("password");
LoginHandler loginHandler = null;
if (session != null) {
loginHandler = (LoginHandler) session.getAttribute("loginHandler");
if (loginHandler != null && username != null && password != null) {
boolean loginOk = false;
//do logic using methods and objects in loginHandler and set loginOk if login is successful
loginOk = loginHandler.login(username, password);
if (loginOk) {
// login OK
response.sendRedirect(request.getContextPath() + WELCOME_PAGE_URL);
}
}
}
chain.doFilter(request, response);
}
As said, option 2 has the problem that session isn't set the first time I access the application (i.e., i need to try to login twice - the second time everything works)
Thanks
EDIT1: updated comment and description to better reflect the role of the class LoginHandler
On when to use f:param & f:metadata , i will advice you read
this answer as a great one is provided as to such. never use f:viewParam to handle user inputs as data is sent over HTTP GET request.
It is also advised against using servlets with JSF application as
session scoped beans are attributes of HTTPSessions,you will find
an explanations as to why here.
finally there are four ways to register a system even listner in a
JSF application.
With a f:event tag as you used in your question
provided, by annotating a class with the
#ListnerFor(systemEventClass = typeOfEvent.class), by calling a
subscribeToEvent method and finally by registering a system event
listner in faces-config.xml file as so
<application>
<system-event-listener>
<system-event-listener-class>listenerClass</system-event-listener-class>
<system-event-class>eventClass</system-event-class>
</system-event-listener>
</application>
In the situation where you use the f:event tag(as you did in your question) you should make sure you enclose it in a f:view tag
<f:view>
<f:event type="preRenderView" listener="#{loginManager.singleSignOn}"/>
......
<f:viewParam name="username" value="#{loginManager.username}"/>
<f:viewParam name="password" value="#{loginManager.password}"/>
......
</f:view>
and your bean method accepting a ComponentSystemEvent as so
public void singleSignOn(ComponentSystemEvent event) {
if (!login) {
//do JSF stuff here when validating credentials fails or passes
//eg. perform navigation
}
}
NB: there is a an answer on this question that address how to implements filters with jsf,use it as head start
for additional material, I will advice you continue with Basic steps for starting session in jsf.
I went with the prerenderview event:
singleSignOn.xhtml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<f:metadata>
<f:viewParam name="userName" value="#{loginCredentials.userName}" />
<f:viewParam name="password" value="#{loginCredentials.password}" />
<f:event type="preRenderView" listener="#{loginHandler.singleSignOn}" />
</f:metadata>
<h:head>
<title>singleSignOn</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</h:head>
<h:body />
</html>
LoginCredentials is a request scoped POJO with fields userName and password.
singleSignOn() in LoginHandler (part):
public void singleSignOn(ComponentSystemEvent event) {
if (loginCredentials.getUserName() != null
&& loginCredentials.getPassword() != null) {
login();
} else {
// error handling (i did output on syserr)
}
}
LoginCredentials and LoginHandler.login are the same bean/method that are bound to the h:form on the login page
Related
I have a project that uses JSF . I have written a filter that intercepts every request for 2 purposes :
1) change session id with each request , for security reasons
2) check whether a user is logged in , else redirect to login page, again for security reason so that unless properly authenticated, no one sees "internal" pages
A Simplistic code of my LoginFilter (ofcourse I have omitted the session id change and redirection parts to keep things simple)
public class LoginFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String reqUrl = ((HttpServletRequest) request).getRequestURL().toString();
System.out.println("reqUrl : " + reqUrl);
HttpSession session = ((HttpServletRequest)request).getSession();
System.out.println("session obj : " + session);
chain.doFilter(request, response);
System.out.println("reqUrl AFTER :: " + reqUrl);
}
}
When I log in , the doFilter method of Filter is called once for the url of the page that comes next after login screen, intercepting request.
Since I am tracing (System.out.println) urls that pass through the filter,
this is what I get in console
reqUrl : http://localhost:8082/meqms/module/voting/mainframe.jsf
session obj : org.apache.catalina.session.StandardSessionFacade#396df0c7
reqUrl AFTER :: http://localhost:8082/meqms/module/voting/mainframe.jsf
However if I refresh the page now ,
the request gets intercepted twice.
and console gets new entries like
reqUrl : http://localhost:8082/meqms/module/voting/mainframe.jsf
session obj : org.apache.catalina.session.StandardSessionFacade#396df0c7
reqUrl AFTER :: http://localhost:8082/meqms/module/voting/mainframe.jsf
reqUrl : http://localhost:8082/meqms/module/voting/mainframe.jsf
session obj : org.apache.catalina.session.StandardSessionFacade#396df0c7
BROKER_MSTR_BEAN
reqUrl AFTER :: http://localhost:8082/meqms/module/voting/mainframe.jsf
I am not understanding whats happening here.
However one thing I did notice is the resource being loaded , i.e , mainframe.xhtml calls a JSF bean method on page load
which prints BROKER_MSTR_BEAN
This printing comes in second interception , not the first one . which leads me to believe the page mainframe.xhtml gets loaded only on second "interception" (of filter).
If thats the case, why is the first call at all ??
whats happening here . why a resource would be requested twice (and hence intercepted twice by filter ?)
=============== EDIT ==========================
we have 2 mainframe.xhtml here . one includes the other
main one :
<ui:composition template="/WEB-INF/include/template/page-template.xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:define name="page-content">
<ui:param name="currentContentDescriptor"
value="#{applicationController.currentContextDescriptor}" />
<ui:decorate
template="/WEB-INF/include/template/plain_container.xhtml">
example template
<ui:define name="example">
<ui:include src="/WEB-INF/include/ksecurity_vote/mainframe.xhtml" />
</ui:define>
</ui:decorate>
</ui:define>
</ui:composition>
the one being included :
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<h:form xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:mobi="http://www.icesoft.com/icefaces/mobile/component">
<div style="visibility: hidden;">
<h:outputText value="#{brokerMstr.onpageload}" />
</div>
</h:form>
BrokerMstrBean.java
public class BrokerMstrBean extends GenericDaoImpl<BrokerMstr> implements java.io.Serializable {
private String onpageload;
public void setOnpageload(String onpageload) {
this.onpageload = onpageload;
}
public String getOnpageload() {
System.out.println("Brokermstrbean");
HttpServletRequest req = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
Object s = req.getParameter("status");
if (s == null) {
s = req.getAttribute("status1");
System.out.println(" status " + s);
}
if (s != null) {
if ((s.equals("0")) || (s.equals("1"))) {
setValidation_popup(false);
}
}
return onpageload;
}
I am not very good in jsf . This project was made by someone else. Can you please help
We are streaming a binary file to our users, following the procedure elaborated in the SO question How to provide a file download from a JSF backing bean?
In general the workflow works as intended, but during the generation of the export file recoverable errors may occur and we want to display these as a warning to the user. The file itself shall still be generated in that case. So we want that export to continue and display faces messages.
Just to put emphasis on this: Yes, there is something not OK with the data, but our users want the export to continue and receive that flawed file anyway. Then they want to have a look at the file, contact their vendor and send him a message about the flaw.
So I need the export to finish in any case.
But it does not work out as we want it to. I have created a simplified example to illustrate our approach.
As alternative we are considering a Bean that will be hold the messages and display them after the export. But probably there is a way with JSF built-in mechanisms to achieve this.
Controller
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import org.apache.tomcat.util.http.fileupload.util.Streams;
#ManagedBean
#RequestScoped
public class ExportController {
public void export() {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
byte[] exportContent = "Hy Buddys, thanks for the help!".getBytes();
// here something bad happens that the user should know about
// but this message does not go out to the user
fc.addMessage(null, new FacesMessage("record 2 was flawed"));
ec.responseReset();
ec.setResponseContentType("text/plain");
ec.setResponseContentLength(exportContent.length);
String attachmentName = "attachment; filename=\"export.txt\"";
ec.setResponseHeader("Content-Disposition", attachmentName);
try {
OutputStream output = ec.getResponseOutputStream();
Streams.copy(new ByteArrayInputStream(exportContent), output, false);
} catch (IOException ex) {
ex.printStackTrace();
}
fc.responseComplete();
}
}
JSF Page
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<f:view contentType="text/html">
<h:body>
<h:form prependId="false">
<h:messages id="messages" />
<h:commandButton id="download" value="Download"
actionListener="#{exportController.export()}" />
</h:form>
</h:body>
</f:view>
</html>
Since you're actually performing a file download response and not a JSF one, it's not possible for your message to be added while the same request happens. The most clean solution for me, avoiding hacky asynchronous requests is to use a #ViewScoped bean and do your task in two steps. So, to have a button for preparing your file, notifying the user later on and allowing him to download it when it's ready:
#ManagedBean
#ViewScoped
public class ExportController implements Serializable {
private byte[] exportContent;
public boolean isReady() {
return exportContent != null;
}
public void export() {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset();
ec.setResponseContentType("text/plain");
ec.setResponseContentLength(exportContent.length);
String attachmentName = "attachment; filename=\"export.txt\"";
ec.setResponseHeader("Content-Disposition", attachmentName);
try {
OutputStream output = ec.getResponseOutputStream();
Streams.copy(new ByteArrayInputStream(exportContent), output, false);
} catch (IOException ex) {
ex.printStackTrace();
}
fc.responseComplete();
}
public void prepareFile() {
exportContent = "Hy Buddys, thanks for the help!".getBytes();
// here something bad happens that the user should know about
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage("record 2 was flawed"));
}
}
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<f:view contentType="text/html">
<h:body>
<h:form>
<h:messages id="messages" />
<h:commandButton value="Prepare"
action="#{exportController.prepareFile}" />
<h:commandButton id="download" value="Download"
disabled="#{not exportController.ready}"
action="#{exportController.export()}" />
</h:form>
</h:body>
</f:view>
</html>
Note this solution could be valid for small files (their entire content is stored in memory while user keeps in the same view). However, if you're going to use it with large files (or large number of users) your best is to store its content in a temporary file and display a link to it instead of a download button. That's what #BalusC suggests in the reference below.
See also:
File download from JSF with a rendered response
You may try this :
For primefaces, you can use remote command instead of command link, and call it with its name onsuccess. Otherwise give a widget var to the commandlink and call its click method.
I'm using JSF 2 and PrimeFaces 2.1 on GlassFish.
I have a page that is intended to allow people to perform an action after following a callback URL (e.g. as link embedded in email or as callback URL parameter of some external authentication or payment service). In my case I need to reset the password. The callback URL has a token GET parameter like so:
http://example.com/app/resetPasswordForm.jsf?token=abc123
On page load of resetPasswordForm.jsf, I need to check if the token is valid and redirect to the main app screen if it's not valid.
My thinking is to have a bean method like:
public String resetPasswordHandler.showResetForm(String token) {
if /* token is valid */ {
return "resetPasswordForm.jsf";
} else {
return "main.jsf";
}
}
But how would I cause that method to get hit on page load?
Not sure how to proceed -- suggestions are welcome.
Use <f:viewAction> to trigger a bean method before rendering of the view and simply return a navigation outcome (which will implicitly be treated as a redirect).
E.g.
<f:metadata>
<f:viewParam name="token" value="#{authenticator.token}" />
<f:viewAction action="#{authenticator.check}" />
</f:metadata>
with
#ManagedBean
#RequestScoped
public class Authenticator {
private String token;
public String check() {
return isValid(token) ? null : "main.jsf";
}
// Getter/setter.
}
If you're not on JSF 2.2 yet, then you can use the <f:event type="preRenderView"> workaround in combination with ExternalContext#redirect().
<f:metadata>
<f:viewParam name="token" value="#{authenticator.token}" />
<f:event type="preRenderView" listener="#{authenticator.check}" />
</f:metadata>
with
#ManagedBean
#RequestScoped
public class Authenticator {
private String token;
public void check() throws IOException {
if (!isValid(token)) {
FacesContext.getCurrentInstance().getExternalContext().redirect("main.jsf");
}
}
// Getter/setter.
}
See also:
Communication in JSF 2.0 - Processing GET request parameters
What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
How do I process GET query string URL parameters in backing bean on page load?
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.
I want user to be redirected if the value I am checking in c:if is evaluated to true. For redirecting, I am using c:redirect url="url". But it is not redirecting me to the page. Here is the code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<f:view>
<c:if test="#{user.loggedIn}">
#{user.loggedIn}
<c:redirect url="index.xhtml"></c:redirect>
</c:if>
Hello #{user.name}
<h:form>
<h:commandButton value="Logout" action="#{user.logout}" />
</h:form>
</f:view>
Here, h represents JSF Html Taglib, c is JSTL core taglib, f is JSF core taglib.
Do not control the request/response in the view side. Do it in the controller side. Use a filter which you map on URL pattern of the restricted pages, such as /app/*. JSF session scoped managed beans are just available as HttpSession attributes in the filter.
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
User user = (session != null) ? (User) session.getAttribute("user") : null;
if (user == null || !user.isLoggedIn()) {
response.sendRedirect("index.xhtml"); // No logged-in user found, so redirect to index page.
} else {
chain.doFilter(req, res); // Logged-in user found, so just continue request.
}
}
The reason that this fails is that a JSF view is part of the response and that the response may already have been committed at that point. You should have seen an IllegalStateException: response already committed in the server logs at the point <c:redirect> is invoked.