Saturday, March 15, 2014

Using Apache Shiro for JSF 2.0 Web Application Session Management


  • Configure Shiro filter as the first filter in web.xml:
        
  
  webapp.CdiEnvironmentLoaderListener
 
 
  ShiroFilter
  org.apache.shiro.web.servlet.ShiroFilter
 

 
  ShiroFilter
  /*
  REQUEST
  FORWARD
  INCLUDE
  ERROR
 
public class CustomRealm extends AuthenticatingRealm {

  private CredentialsMatcher credentialsMatcher;

  public String getName() {
    return "myRealm";
  }

  public boolean supports(AuthenticationToken token) {
    return true;
  }

  public CredentialsMatcher getCredentialsMatcher() {
    return credentialsMatcher;
  }

  public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
    this.credentialsMatcher = credentialsMatcher;
  }

  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    // we can safely cast to a UsernamePasswordToken here, because this class
    // 'supports' UsernamePasswordToken
    // objects. See the Realm.supports() method if your application will use a
    // different type of token.
    UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    return new SimpleAuthenticationInfo(upToken.getUsername(), upToken.getPassword(), getName());
  }
}
public class CustomAuthenticator extends AbstractAuthenticator {
  @Override
  protected AuthenticationInfo doAuthenticate(AuthenticationToken token) throws AuthenticationException {
    // perform custom authentication - lookup DB or invoke session EJB for auth
  }
}
import java.io.IOException;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.web.filter.authc.UserFilter;

public class FacesAjaxAwareUserFilter extends UserFilter {

    private static final String FACES_REDIRECT_XML = ""
            + "";

    @Override
    protected void redirectToLogin(ServletRequest req, ServletResponse res) throws IOException {
        HttpServletRequest request = (HttpServletRequest) req;

        if ("partial/ajax".equals(request.getHeader("Faces-Request"))) {
            res.setContentType("text/xml");
            res.setCharacterEncoding("UTF-8");
            res.getWriter().printf(FACES_REDIRECT_XML, request.getContextPath() + getLoginUrl());
        }
        else {
            super.redirectToLogin(req, res);
        }
    }

}
public class CdiEnvironmentLoaderListener extends EnvironmentLoaderListener {

  CustomRealm customRealm = null;

  @Override
  protected WebEnvironment createEnvironment(ServletContext context) {
    WebEnvironment environment = super.createEnvironment(context);
    customRealm = new CustomRealm();

    RealmSecurityManager rsm = (RealmSecurityManager) environment.getSecurityManager();

    /*-HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
    matcher.setHashAlgorithmName(Sha512Hash.ALGORITHM_NAME);*/
    AllowAllCredentialsMatcher matcher = new AllowAllCredentialsMatcher();
    
    customRealm.setCredentialsMatcher(matcher);

    rsm.setRealm(customRealm);

    ((DefaultWebEnvironment) environment).setSecurityManager(rsm);

    return environment;
  }
}
@Named
@SessionScoped
public class LoginBean implements Serializable {
 private String username;
 private String password;
private static Factory factory = null;

  @PostConstruct
  public void init() {
    if (factory == null) {
      factory = new IniSecurityManagerFactory("classpath:shiro.ini");
      SecurityManager securityManager = factory.getInstance();
      SecurityUtils.setSecurityManager(securityManager);
    }
  }

 public String login() {
  UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    token.setRememberMe(true);

    // Submit the principals and credentials
    Subject currentUser = SecurityUtils.getSubject();

   try {
     FacesContext fc = FacesContext.getCurrentInstance();
      ExternalContext externalContext = fc.getExternalContext();
      HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
      /*
       * Enable the session creation only during login.
       */
      request.setAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED, Boolean.TRUE);

      currentUser.login(token);
      return "SUCCESS";
    } catch (AuthenticationException | IOException e) {
      return "FAILED";
    }
   }

  public void logout() {
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
    try {

      // Invalidate HTTP session.
      Subject currentUser = SecurityUtils.getSubject();
      if (currentUser != null) {
          try {
          currentUser.logout();
        } catch (Exception e) {
          
        }
        externalContext.invalidateSession();
      }
    } catch (Exception ex) {
      
    } finally {
      try {
        externalContext.redirect("/login.xhtml");
      } catch (IOException e) {
        
      }
    }
  }
 }
}

Login

Lastly, the shiro.ini:
[main]
user = webapp.FacesAjaxAwareUserFilter
user.loginUrl = /login.xhtml

# Configure The EhCacheManager
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile = classpath:ehcache.xml

# Configure the above CacheManager on Shiro's SecurityManager
# to use it for all of Shiro's caching needs:
securityManager.cacheManager = $cacheManager

# Auth
myRealm = webapp.CustomRealm
myRealmCredentialsMatcher = org.apache.shiro.authc.credential.AllowAllCredentialsMatcher
myRealm.credentialsMatcher = $myRealmCredentialsMatcher
securityManager.realms = $myRealm

authenticator = webapp.CustomAuthenticator
securityManager.authenticator = $authenticator

#Remember Me
rememberMe = org.apache.shiro.web.mgt.CookieRememberMeManager
securityManager.rememberMeManager = $rememberMe

[urls]
/ = user
/login.xhtml = user

/javax.faces.resource/** = noSessionCreation, anon
/images/** = noSessionCreation, anon
/js/**= noSessionCreation, anon
/css/** = noSessionCreation, anon
/** =  user

No comments:

Book notes: Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems, by Martin Kleppmann

My notes from the excellent book on how software has evolved to handle data from hierarchical databases to the NoSQL -  https://www.goodrea...