How can i retrieve the bookmarkable URL, respecting rewritten rules using ViewHandler.getBookmarkableURL(...) inside a #WebFilter to redirect the user to the rewritten login page URL.
Is there an alternate function to get a bookmarkable URL without FacesContext?
Simplified example
The page /sites/user/login.xhtml is rewritten to just /Login using OCPSoft rewrite library but in following filter i dont know how to do this since i dont have access to FacesContext.
The real example has additional querystring params that also contribute to the rewritten URL
#WebFilter(filterName = "UserFilter", urlPatterns =
{
"/sites/user/account.xhtml"
}, dispatcherTypes =
{
DispatcherType.FORWARD, DispatcherType.REQUEST, DispatcherType.ERROR
})
public class UserFilter extends HttpFilter
{
#Override
public void doFilter(final HttpServletRequest request, final HttpServletResponse response,
final HttpSession session, final FilterChain chain) throws ServletException, IOException
{
if (isLoggedIn())
chain.doFilter(request, response);
else
response.sendRedirect(request.getContextPath() + "/sites/user/login.xhtml");
}
}
Related
I would like to block the access of some page even if the user knows the url of some pages.
For example, /localhost:8080/user/home.xhtml (need to do the login first) if not logged then redirect to /index.xhtml.
How do that in JSF ? I read in the Google that's needed a filter, but I don't know how to do that.
You need to implement the javax.servlet.Filter class, do the desired job in doFilter() method and map it on an URL pattern covering the restricted pages, /user/* maybe? Inside the doFilter() you should check the presence of the logged-in user in the session somehow. Further you also need to take JSF ajax and resource requests into account. JSF ajax requests require a special XML response to let JavaScript perform a redirect. JSF resource requests need to be skipped otherwise your login page won't have any CSS/JS/images anymore.
Assuming that you've a /login.xhtml page which stores the logged-in user in a JSF managed bean via externalContext.getSessionMap().put("user", user), then you could get it via session.getAttribute("user") the usual way like below:
#WebFilter("/user/*")
public class AuthorizationFilter implements Filter {
private static final String AJAX_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<partial-response><redirect url=\"%s\"></redirect></partial-response>";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER + "/");
boolean ajaxRequest = "partial/ajax".equals(request.getHeader("Faces-Request"));
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response); // So, just continue request.
}
else if (ajaxRequest) {
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.getWriter().printf(AJAX_REDIRECT_XML, loginURL); // So, return special XML response instructing JSF ajax to send a redirect.
}
else {
response.sendRedirect(loginURL); // So, just perform standard synchronous redirect.
}
}
// You need to override init() and destroy() as well, but they can be kept empty.
}
Additionally, the filter also disabled browser cache on secured page, so the browser back button won't show up them anymore.
In case you happen to use JSF utility library OmniFaces, above code could be reduced as below:
#WebFilter("/user/*")
public class AuthorizationFilter extends HttpFilter {
#Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain) throws ServletException, IOException {
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = Servlets.isFacesResourceRequest(request);
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
Servlets.setNoCacheHeaders(response);
}
chain.doFilter(request, response); // So, just continue request.
}
else {
Servlets.facesRedirect(request, response, loginURL);
}
}
}
See also:
Our Servlet Filters wiki page
How to handle authentication/authorization with users in a database?
Using JSF 2.0 / Facelets, is there a way to attach a global listener to all AJAX calls?
Avoid back button on JSF web application
JSF: How control access and rights in JSF?
While it's of course legitimate to use a simple Servlet filter, there are alternatives like
Spring Security
Java EE Security
Apache Shiro
I would like to block the access of some page even if the user knows the url of some pages.
For example, /localhost:8080/user/home.xhtml (need to do the login first) if not logged then redirect to /index.xhtml.
How do that in JSF ? I read in the Google that's needed a filter, but I don't know how to do that.
You need to implement the javax.servlet.Filter class, do the desired job in doFilter() method and map it on an URL pattern covering the restricted pages, /user/* maybe? Inside the doFilter() you should check the presence of the logged-in user in the session somehow. Further you also need to take JSF ajax and resource requests into account. JSF ajax requests require a special XML response to let JavaScript perform a redirect. JSF resource requests need to be skipped otherwise your login page won't have any CSS/JS/images anymore.
Assuming that you've a /login.xhtml page which stores the logged-in user in a JSF managed bean via externalContext.getSessionMap().put("user", user), then you could get it via session.getAttribute("user") the usual way like below:
#WebFilter("/user/*")
public class AuthorizationFilter implements Filter {
private static final String AJAX_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<partial-response><redirect url=\"%s\"></redirect></partial-response>";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER + "/");
boolean ajaxRequest = "partial/ajax".equals(request.getHeader("Faces-Request"));
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response); // So, just continue request.
}
else if (ajaxRequest) {
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.getWriter().printf(AJAX_REDIRECT_XML, loginURL); // So, return special XML response instructing JSF ajax to send a redirect.
}
else {
response.sendRedirect(loginURL); // So, just perform standard synchronous redirect.
}
}
// You need to override init() and destroy() as well, but they can be kept empty.
}
Additionally, the filter also disabled browser cache on secured page, so the browser back button won't show up them anymore.
In case you happen to use JSF utility library OmniFaces, above code could be reduced as below:
#WebFilter("/user/*")
public class AuthorizationFilter extends HttpFilter {
#Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain) throws ServletException, IOException {
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = Servlets.isFacesResourceRequest(request);
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
Servlets.setNoCacheHeaders(response);
}
chain.doFilter(request, response); // So, just continue request.
}
else {
Servlets.facesRedirect(request, response, loginURL);
}
}
}
See also:
Our Servlet Filters wiki page
How to handle authentication/authorization with users in a database?
Using JSF 2.0 / Facelets, is there a way to attach a global listener to all AJAX calls?
Avoid back button on JSF web application
JSF: How control access and rights in JSF?
While it's of course legitimate to use a simple Servlet filter, there are alternatives like
Spring Security
Java EE Security
Apache Shiro
In my web.xml file, I configured:
<welcome-file-list>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
It means, when I type a URL www.domain.com, index.xhtml file is used to render. But when I type www.domain.com/index.xhtml, the result is the same.
is it called duplicated content?
This is no problem for my project but a big problem for SEO.
How can I redirect to www.domain.com/index.xhtml page when typing URL www.domain.com instead of letting it perform a forward?
An URL is marked duplicate content when there's another URL on the same domain which returns exactly the same response. And yes, you should definitely worry about this if SEO is important.
Easiest way to fix this is to provide a so-called canonical URL in the head of index.xhtml. This should represent the URL of preference, which is in your particular case apparently the one with the filename:
<link rel="canonical" href="http://www.domain.com/index.xhtml" />
This way the http://www.domain.com will be indexed as http://www.domain.com/index.xhtml. and not cause duplicate content anymore. However, this will not stop endusers being able to bookmark/share different URLs anyway.
Another way is to configure a HTTP 301 redirect to the URL of preference. It's very important to understand that the origin of a 302 redirect is still indexed by searchbots, but the origin of a 301 redirect not, only the target page is indexed. If you would be using a 302 as by default used by HttpServletResponse#sendRedirect(), then you would still end up having duplicate content because the both URLs are still indexed.
Here's a kickoff example of such a filter. Just map it on /index.xhtml and perform a 301 redirect when the URI doesn't equal the desired path.
#WebFilter(urlPatterns = IndexFilter.PATH)
public class IndexFilter implements Filter {
public static final String PATH = "/index.xhtml";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String uri = request.getContextPath() + PATH;
if (!request.getRequestURI().equals(uri)) {
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301
response.setHeader("Location", uri);
response.setHeader("Connection", "close");
} else {
chain.doFilter(req, res);
}
}
// init() and destroy() can be NOOP.
}
To remove duplicate content, design a Filter with URL patter /*. If user on root domain than redirect to index.xhtml URL.
#WebFilter(filterName = "IndexFilter", urlPatterns = {"/*"})
public class IndexFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String requestURL = request.getRequestURI().toString();
if (request.getServletPath().equals("/index.xhtml") &&
!requestURL.contains("index.xhtml")) {
response.sendRedirect("http://" + req.getServerName() + ":"
+ request.getServerPort() + request.getContextPath()
+"/index.xhtml");
} else {
chain.doFilter(req, resp);
}
}
}
As I was told and found on websites, my bean will only be executed if I have a call for it on my .xhtml.
Is it possible to call my bean without any EL expression?
I need this because my HomeController is calling a method that checks for the session status and on my home.xhtml and don't have any need for fall this bean, for now.
You need to look for a solution in a different direction.
If you're homegrowing user authentication instead of using container managed authentication, then you normally use a servlet filter for the particular purpose of checking if an user is logged in or not.
The servlet filter can like as servlets (like FacesServlet) be mapped on a particular URL pattern. It will then be invoked on every request matching that URL pattern. You can explore request/session data in the filter and then handle the request/response accordingly by either continuning the filter chain, or by sending a redirect.
You need to implement javax.servlet.Filter interface accordingly. Here's a kickoff example of how the doFilter() method should be implemented assuming that you've a #SessionScoped managed bean LoginController. JSF stores session scoped managed beans as attributes of HttpSession.
#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);
LoginController loginController = (LoginController) (session != null) ? session.getAttribute("loginController") : null;
if (loginController == null || !loginController.isLoggedIn()) {
response.sendRedirect(request.getContextPath() + "/login.xhtml"); // No logged-in user found, so redirect to login page.
} else {
chain.doFilter(req, res); // Logged-in user found, so just continue request.
}
}
Map this filter on an URL pattern covering the restricted pages, e.g. /app/*.
#WebFilter("/app/*")
public class LoginFilter implements Filter {
// ...
}
Update if the login.xhtml is also covered by this URL pattern and you really can't change it, then change the if block as follows:
if (!request.getRequestURI().endsWith("/login.xhtml") && (loginController == null || !loginController.isLoggedIn())) {
// ...
}
I would like to block the access of some page even if the user knows the url of some pages.
For example, /localhost:8080/user/home.xhtml (need to do the login first) if not logged then redirect to /index.xhtml.
How do that in JSF ? I read in the Google that's needed a filter, but I don't know how to do that.
You need to implement the javax.servlet.Filter class, do the desired job in doFilter() method and map it on an URL pattern covering the restricted pages, /user/* maybe? Inside the doFilter() you should check the presence of the logged-in user in the session somehow. Further you also need to take JSF ajax and resource requests into account. JSF ajax requests require a special XML response to let JavaScript perform a redirect. JSF resource requests need to be skipped otherwise your login page won't have any CSS/JS/images anymore.
Assuming that you've a /login.xhtml page which stores the logged-in user in a JSF managed bean via externalContext.getSessionMap().put("user", user), then you could get it via session.getAttribute("user") the usual way like below:
#WebFilter("/user/*")
public class AuthorizationFilter implements Filter {
private static final String AJAX_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<partial-response><redirect url=\"%s\"></redirect></partial-response>";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER + "/");
boolean ajaxRequest = "partial/ajax".equals(request.getHeader("Faces-Request"));
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response); // So, just continue request.
}
else if (ajaxRequest) {
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.getWriter().printf(AJAX_REDIRECT_XML, loginURL); // So, return special XML response instructing JSF ajax to send a redirect.
}
else {
response.sendRedirect(loginURL); // So, just perform standard synchronous redirect.
}
}
// You need to override init() and destroy() as well, but they can be kept empty.
}
Additionally, the filter also disabled browser cache on secured page, so the browser back button won't show up them anymore.
In case you happen to use JSF utility library OmniFaces, above code could be reduced as below:
#WebFilter("/user/*")
public class AuthorizationFilter extends HttpFilter {
#Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain) throws ServletException, IOException {
String loginURL = request.getContextPath() + "/login.xhtml";
boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = Servlets.isFacesResourceRequest(request);
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
Servlets.setNoCacheHeaders(response);
}
chain.doFilter(request, response); // So, just continue request.
}
else {
Servlets.facesRedirect(request, response, loginURL);
}
}
}
See also:
Our Servlet Filters wiki page
How to handle authentication/authorization with users in a database?
Using JSF 2.0 / Facelets, is there a way to attach a global listener to all AJAX calls?
Avoid back button on JSF web application
JSF: How control access and rights in JSF?
While it's of course legitimate to use a simple Servlet filter, there are alternatives like
Spring Security
Java EE Security
Apache Shiro