How can I integrate SSO authentication with JSF? - jsf

I'm currently using a Filter to check for SSO authentication. (SSO is considered authenticated if the request header contains the variable "Proxy-Remote-User").
if (!(isSsoLoggedIn(request)) {
response.sendRedirect(ERROR_PAGE);
return;
} else {
chain.doFilter(req, res);
}
private boolean isSsoLoggedIn(HttpServletRequest request) {
return request != null && request.getHeader("Proxy-Remote-User") != null
&& !request.getHeader("Proxy-Remote-User").equals("");
}
Now, once the user is authenticated, I want to pass that variable (which is an email address) to JSF. I do that with a session-scoped bean:
#PostConstruct
public void init {
Map<String, String> requestHeaderMap = FacesContext.getCurrentInstance().getExternalContext().getRequestHeaderMap();
String email = requestHeaderMap.get("Proxy-Remote-User");
user = getPersonFromDB(email);
}
This seems simple enough, but I'm not sure if its the "right" way to do this. It doesn't seem correct to rely on a bean's instantiation to verify authentication.
One idea I just had: Use a CDI session-scoped bean and #Inject it into the Filter. Then, you could have the filter itself check for a valid user and, if valid, set it in the session-scoped bean, otherwise forward it to an error page.
Does that sound like a valid solution?
Another approach could be to have every page check for authentication, before the view is rendered, with a view param as mentioned here:
JSF calls methods when managed bean constructor sends 404 ERROR CODE
<f:metadata>
<f:viewAction action="#{bean.checkForValidUser}" />
</f:metadata>
The only problem I have for this is...this would require copying/pasting the same code to every page which seems redundant (or at least a template for them all to use).

Here is the answer I came up with (thanks to some tips from #BalusC).
I check to see if developer login is enabled or SSO has been authenticated. Once I have the email address, I see if its a valid user, verify the JSF session contains the right user, and, if so, forward them on their way.
/**
* This filter handles SSO login (or developer login) privileges to the web
* application.
*/
#WebFilter(servletNames = "Faces Servlet")
public class SecurityFilter implements Filter {
#Inject
private SessionManager sessionManager;
#EJB
private PersonWriteFacadeRemote personFacade;
private HttpServletRequest currentRequest;
private HttpServletResponse currentResponse;
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
currentRequest = (HttpServletRequest) req;
currentResponse = (HttpServletResponse) res;
HttpSession session = currentRequest.getSession();
String requestedPage = currentRequest.getRequestURI();
// check if the session is initialized
// if not, initialize it
if (!isSessionInitialized()) {
Person user = getCurrentUser();
// if we can't figure out who the user is, then send 401 error
if (user != null) {
initializeSession(user);
} else {
currentResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// if it is initialized, check if it actually matches the current
// user
// if not, invalidate the session and redirect them back to the page
// to reinitialize it
} else if (!isSessionCurrentUsers()) {
session.invalidate();
currentResponse.sendRedirect(requestedPage);
return;
}
chain.doFilter(req, res); // If all looks good, continue the request
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
private Person getCurrentUser() {
try {
return personFacade.createFromEmail(getUserEmail());
} catch (InvalidAttributesException ex) {
Logger.getLogger(SecurityFilter.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
private String getUserEmail() {
return isDevLoginEnabled() ? getUserEmailFromJndi() : getUserEmailFromSso();
}
private String getUserEmailFromJndi() {
return JNDI.lookup("devLoginEmail");
}
private String getUserEmailFromSso() {
return currentRequest != null && currentRequest.getHeader("Proxy-Remote-User") != null
&& !currentRequest.getHeader("Proxy-Remote-User").equals("")
? currentRequest.getHeader("Proxy-Remote-User") : null;
}
private boolean isDevLoginEnabled() {
Boolean devLoginEnabled = JNDI.lookup("devLoginEnabled");
return (devLoginEnabled != null ? devLoginEnabled : false);
}
private boolean isSessionInitialized() {
return sessionManager.getUser() != null;
}
private void initializeSession(Person user) {
sessionManager.initializeSession(user);
}
private boolean isSessionCurrentUsers() {
return sessionManager.getUser() != null && sessionManager.getUser().getEmail() != null
&& sessionManager.getUser().getEmail().equals(getUserEmail());
}
}

Related

JSF ExceptionHandler - Response already committed in some cases

We have implemented a custom ExceptionHandler that attempts to intercept any NonexistentConversationException that are thrown if a user requests a URL with an old cid in it (eg. bookmarked link etc.).
The handler simply removed the cid from the URL and performs a redirect to the updated URL.
This solution seems to work in most cases, we have a number of different user 'roles' within the App each with their own login areas and for one of these user types it seems that fc.getExternalContext().isResponseCommitted() is always true when the ExceptionHandler fires which means we are unable to perform the redirect. All other user types it works ok.
I am not sure exactly what the difference is with this user type for this to happen, I am guessing some CDI bean we using setup differently
Is there a way to unsure the ExceptionHandler kicks in earlier before the response is committed?
Below is the handler...
public class ConversationExpiredExceptionHandler
extends ExceptionHandlerWrapper {
final static Logger log = LoggerFactory.getLogger(ConversationExpiredExceptionHandler.class);
private ExceptionHandler wrapped;
private static String CONVERSATION_PARAM = "cid";
public ConversationExpiredExceptionHandler(ExceptionHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public ExceptionHandler getWrapped() {
return this.wrapped;
}
#Override
public void handle() throws FacesException {
for (Iterator<ExceptionQueuedEvent> i =
getUnhandledExceptionQueuedEvents().iterator();
i.hasNext();) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
Throwable t = context.getException();
if (t instanceof javax.enterprise.context.NonexistentConversationException
|| t instanceof org.jboss.weld.contexts.NonexistentConversationException
|| t.getCause() instanceof org.jboss.weld.contexts.NonexistentConversationException /* can be wrapped in FacesException */) {
final FacesContext fc = FacesContext.getCurrentInstance();
try {
if (!fc.getExternalContext().isResponseCommitted()) {
if(Faces.getRequestQueryStringMap().containsKey(CONVERSATION_PARAM)) {
String requestedUrl = Faces.getRequestURLWithQueryString();
String updatedUrl = this.removeQueryParameter(requestedUrl,CONVERSATION_PARAM);
log.debug("No conversation active for {}, redirecting to {}",requestedUrl,updatedUrl);
fc.getExternalContext().redirect(updatedUrl);
}
}
fc.renderResponse();
break;
} catch (Exception ex) {
throw new FacesException(ex);
} finally {
i.remove();
}
}
}
getWrapped().handle();
}
private String removeQueryParameter(String url, String parameterName) throws URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(url);
List<NameValuePair> queryParameters = uriBuilder.getQueryParams();
for (Iterator<NameValuePair> queryParameterItr = queryParameters.iterator(); queryParameterItr.hasNext();) {
NameValuePair queryParameter = queryParameterItr.next();
if (queryParameter.getName().equals(parameterName)) {
queryParameterItr.remove();
}
}
uriBuilder.setParameters(queryParameters);
return uriBuilder.build().toString();
}

Security Configuration doesn't let me use antMatchers() on some pages

Security Configuration doesn't let me use antMatchers() on some pages. Below is a configuration code where I'm trying to let not signed in user access "/", "/entries", "/signup". With "/signup" there is no problem it let me visit that page, but it keeps redirecting me to login page if I'm trying to access "/" or "/entries". I've tried to write each uri in separate antMatchers() and switching orders, but no luck so far.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
DetailService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(User.PASSWORD_ENCODER);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/entries","/signup").permitAll()
.antMatchers("/adminpanel/**")
.access("hasRole('ROLE_ADMIN')")
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(loginSuccessHandler())
.failureHandler(loginFailureHandler())
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/clearConnection")
.and()
.csrf();
http.headers().frameOptions().disable();
}
public AuthenticationSuccessHandler loginSuccessHandler() {
return (request, response, authentication) -> response.sendRedirect("/");
}
public AuthenticationFailureHandler loginFailureHandler() {
return (request, response, exception) -> {
response.sendRedirect("/login");
};
}
#Bean
public EvaluationContextExtension securityExtension() {
return new EvaluationContextExtensionSupport() {
#Override
public String getExtensionId() {
return "security";
}
#Override
public Object getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {
};
}
};
}
}
Apparently I had a UserHandler class that has annotation #ControllerAdvice(basePackages = "myproject.web.controller"). That's means that it applies to all classes for provided package. My addUser() is trying to add User as an attribute and if there is no user it throwing one of exceptions defined in the same class which cause redirection. So, I created separate GuestController outside of the package provided for #ControllerAdvice and handle all logic for a guest in it. That solved my problem. Would appreciate any insights on my approach, if its good practice or not.
#ControllerAdvice(basePackages = "myproject.web.controller")
public class UserHandler {
#Autowired
private UserService users;
#ExceptionHandler(AccessDeniedException.class)
public String redirectNonUser(RedirectAttributes attributes) {
attributes.addAttribute("errorMessage", "Please login before accessing website");
return "redirect:/login";
}
#ExceptionHandler(UsernameNotFoundException.class)
public String redirectNotFound(RedirectAttributes attributes) {
attributes.addAttribute("errorMessage", "Username not found");
return "redirect:/login";
}
#ModelAttribute("currentUser")
public User addUser() {
if(SecurityContextHolder.getContext().getAuthentication() != null) {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
User user = users.findByUsername(username);
if(user != null) {
return user;
} else {
throw new UsernameNotFoundException("Username not found");
}
} else {
throw new AccessDeniedException("Not logged in");
}
}
}

Inconsistency while using ThreadLocal with Filter, HttpSession and Session Attributes

I am using a ThreadLocal object which stores a String. I am setting the String value to the ThreadLocal object in a filter, that intercepts all requests subject to certain conditions. Also I am setting the ThreadLocal's string value to the HttpSession as an attribute.
The attribute in the session is used in multiple jsps, and eventually passed on to the business layer.
The problem I am facing is that, multiple requests originating from different Clients, at some point of time get the same string value, though the session is different.
So my understanding is that, multiple sessions access the same thread. I don't see any other explanation for this.
Setting the attribute to the request causes problems when moving between jsps. As there are redirects by spring security, which means that request attribute is lost.
So is there any way I can change the implementation such that multiple sessions don't use the same thread ?
Edit : Adding sample code
public class ContextFilter implements Filter {
//No significant variables other than constants
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// Set the Session Timeout Object
SessionTimeoutObject obj = (SessionTimeoutObject) httpRequest
.getSession().getAttribute(KEY);
if (obj == null) {
httpRequest.getSession().setAttribute(KEY,
new SessionTimeoutObject());
}
if( some conditions ) {
chain.doFilter(request, response);
} else {
//Defaulting identifier
String identifier = "init";
if (ContextHolder.getId() != null
&& !ContextHolder.getId().equals("")) {
identifier = ContextHolder.getId());
}
//Do some thing
//Compare the identifier with the value in session and use if it exists
String existingId = (String) httpRequest.getSession()
.getAttribute(ID_KEY);
if (existingId != null
&& !existingId.trim().equals("")) {
identifier = existingId;
}
//Setting id to context only happens here
ContextHolder.setId(identifier);
//Validate the identifier
//Get Business Obj method using identifier
BusinessObj bo = getBusObj(identifier);
//everything above is successful so set in session
httpRequest.getSession().setAttribute("identifier", identifier);
httpRequest.getSession().setAttribute("BusinessObj",
bo);
//no exceptions yet then good to go
chain.doFilter(request, response);
}
}
}
public class SessionTimeoutObject implements Serializable, HttpSessionBindingListener {
private String backup;
#Override
public void valueBound(HttpSessionBindingEvent event) {
//Mainly for debuggin what happens to the session
backup = (String) event.getSession().getAttribute("identifier");
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
//Mainly for debuggin what happens to the session
if (ContextHolder.getId() != null) {
backup = ContextHolder.getId();
}
}
}
class ContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public ContextHolder() {
}
public static void setId(String identifier) {
if (null == identifier) {
//throw some exception
}
contextHolder.set(identifier);
}
public static String getId() {
return (String) contextHolder.get();
}
public static void clearId() {
contextHolder.remove();
}
public static void setDefaultId() {
ContextHolder.clearId();
contextHolder.set('init');
}
}
You should wrap your code in a try/finally block, in the finally block do a ContextHolder.clearId() that clears the context holder. This is important as request handling threads are reused and which means that the ContextHolders thread local retains the same id as before. – M. Deinum
Doing the above solved the issue.

Prevent users to manually access to page

i have implemented jsf phase listener which check if user looged in or not, and if not redirect user to login page.
Now, i want to implement phase listener in case where user manualy input
page name in address bar. In this case phase listener must automatic
redirect user to login page and destroy session.
How do that in JSF ?
Just use a simple servlet Filter which is mapped on a common URL pattern of the restricted pages like /app/*, /pages/*, /secured/*, etc. Here's a kickoff example assuming that you've a #SessionScoped #ManagedBean UserManager.
#WebFilter(urlPatterns={"/app/*"})
public class AuthenticationFilter implements 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);
UserManager userManager = (session != null) ? (UserManager) session.getAttribute("userManager") : null;
if (userManager == null || !userManager.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.
}
}
// ...
}
I am on JSF 1.2 and did that this way:
public void beforePhase(PhaseEvent event)
{
FacesContext fCtx = FacesContext.getCurrentInstance();
String actualView = null;
actualView = event.getFacesContext().getApplication().getViewHandler().getResourceURL(fCtx, fCtx.getViewRoot().getViewId());
//actualView is the page the user wants to see
//you can check, if the user got the permission, is logged in, whatever
}
public PhaseId getPhaseId()
{
return PhaseId.RENDER_RESPONSE;
}

JSF HTTP Session Login

I try to create login form in web application.
in JSP page I can use
<%
String name = request.getParameter( "username" );
session.setAttribute( "theName", name );
%>
but now I am using JSF /Facelets for web application
I don't know how to create session in JSF Backing bean for client and check if user is logged in or not so it will redirect into login page.
who can help me give me link tutorial for these problem ?
thank you before
Now I have little problem with mapping into web.xml
code snipped of class Filter
#Override
public void init(FilterConfig filterConfig) throws ServletException {
this.config = filterConfig;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
LoginController controller = (LoginController) req.getSession()
.getAttribute("loginController");
if (controller == null || !controller.isLoggedIn()) {
res.sendRedirect("../admin/login.xhtml");
} else {
chain.doFilter(request, response);
}
}
and in web.xml I map with <fitler> tag
<filter>
<filter-name>userLoginFilter</filter-name>
<filter-class>com.mcgraw.controller.UserLoginFilter</filter-class>
<init-param>
<param-name>loginPage</param-name>
<param-value>/login.xhtml</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>userLoginFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
I have one folder admin in web project and I check if the user is not logged in with admin permission to not access page (I can do the permission check) but when I use the filter the browser doesn't understand url ??
no StackTrace show when the browser doesn't understand url
Error shown on Firefox
The page isn't redirecting properly
on IE it loading ... loading . .. non-stop
now I change condition which check if req.getPathInfo.startsWith("/login.xhtml") it will do chain
I have 2 idea but it response 500 HTTP STATUS
if (controller == null || !controller.isLoggedIn()) {
res.sendRedirect("../admin/login.xhtml");
if(req.getPathInfo().startsWith("/login.xhtml")){
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response);
}
===============
if (controller == null || !controller.isLoggedIn()) {
if (!req.getPathInfo().startsWith("/login.xhtml")) {
res.sendRedirect("../admin/login.xhtml");
} else {
chain.doFilter(request, response);
}
} else {
chain.doFilter(request, response);
}
======================
update Class loginController
package com.mcgraw.controller;
import com.DAO.UserBean;
import com.entity.IUser;
import java.io.Serializable;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
/**
* #author Kency
*/
#ManagedBean
#SessionScoped
public class LoginController implements Serializable {
#EJB
private UserBean userBean;
private IUser user;
private boolean admin;
private boolean mod;
private PasswordService md5;
/** Creates a new instance of LoginController */
public LoginController() {
user = new IUser();
md5 = new PasswordService();
}
// getter / setter
public boolean isMod() {
return mod;
}
public void setMod(boolean mod) {
this.mod = mod;
}
public IUser getUser() {
return user;
}
public void setUser(IUser user) {
this.user = user;
}
public boolean isAdmin() {
return admin;
}
public void setAdmin(boolean admin) {
this.admin = admin;
}
public String cplogin() {
String md5Password = md5.md5Password(user.getPassword());
if (userBean.userLogin(user.getUsername(), md5Password) != null) {
if (user.getUsername() != null || md5Password != null) {
user = userBean.userLogin(user.getUsername(), md5Password);
if (user.getGroups().getAdmin() != null) {
setAdmin(user.getGroups().getAdmin());
}
if (user.getGroups().getMods() != null) {
setMod(user.getGroups().getMods());
}
if (isAdmin() == true || isMod() == true) {
return "home";
} else {
return "login";
}
} else {
return "login";
}
} else {
return "login";
}
}
public String logout() {
user = null;
return "login";
}
public boolean isLoggedIn() {
return user != null;
}
}
I have new problem if render JSF taglib with method loggedIn, in index page (not in admin folder) user doesn't login can see what I render example, <== this like if user doesn't login user can't see but why can he see it?
You can in JSF get/set HTTP session attributes via ExternalContext#getSessionMap() which is basically a wrapper around HttpSession#get/setAttribute().
#Named
#RequestScoped
public class LoginController {
private String username;
private String password;
#EJB
private UserService userService;
public String login() {
User user = userService.find(username, password);
FacesContext context = FacesContext.getCurrentInstance();
if (user == null) {
context.addMessage(null, new FacesMessage("Unknown login, try again"));
username = null;
password = null;
return null;
} else {
context.getExternalContext().getSessionMap().put("user", user);
return "userhome?faces-redirect=true";
}
}
public String logout() {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
return "index?faces-redirect=true";
}
// ...
}
In the Facelets page, just bind the username and password input fields to this bean and invoke login() action accordingly.
<h:form>
<h:inputText value="#{loginController.username}" />
<h:inputSecret value="#{loginController.password}" />
<h:commandButton value="login" action="#{loginController.login}" />
</h:form>
Session attributes are directly accessible in EL. A session attribute with name user is in EL available as #{user}. When testing if the user is logged in some rendered attribute, just check if it's empty or not.
<h:panelGroup rendered="#{not empty user}">
<p>Welcome, #{user.fullName}</p>
<h:form>
<h:commandButton value="logout" action="#{loginController.logout}" />
</h:form>
</h:panelGroup>
The logout action basically just trashes the session.
As to checking an incoming request if a user is logged in or not, just create a Filter which does roughly the following in doFilter() method:
#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 loginURI = request.getContextPath() + "/login.xhtml";
boolean loggedIn = session != null && session.getAttribute("user") != null;
boolean loginRequest = request.getRequestURI().equals(loginURI);
boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER);
if (loggedIn || loginRequest || resourceRequest) {
chain.doFilter(request, response);
} else {
response.sendRedirect(loginURI);
}
}
Map it on an url-pattern covering the restricted pages, e.g. /secured/*, /app/*, etc.
See also:
How to handle authentication/authorization with users in a database?
Authorization redirect on session expiration does not work on submitting a JSF form, page stays the same
Try this in your backing bean when a request is received (like in an action method):
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
HttpSession session = request.getSession();
Then you can work with the request and session objects just like you used to with JSPs, setting attributes and so on.
You might also want to take a look at my related question about checking the client session in a servlet Filter. You could write a similar Filter to check for the user login in their HttpSession and then do a redirect (or RequestDispatch like I ended up doing) to your login page if needed.

Resources