Spring Security

Integrating Primefaces and Spring Security

Posted on

Following all previous steps we always configured JSF and Spring. In case we use third party library like Primefaces you will have to configure resources required by Primefaces manually in Spring Security xml.

Extending example at if we had Primefaces lib in our project we will have to add below configuration to jsfspring-sec-security-config.xml

<sec:intercept-url pattern="/javax.faces.resource/**" access="permitAll"/>

The resultant code will look like below:

jsfspring-sec-security-config.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	  http://www.springframework.org/schema/beans
	  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	  http://www.springframework.org/schema/security
	  http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	  
	<sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/pages/common/**" access="permitAll"/>
        <sec:intercept-url pattern="/javax.faces.resource/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
		<sec:form-login login-page="/pages/common/login.jsf"/>
		<sec:logout 
			invalidate-session="true" 
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE" 
			logout-success-url="/pages/common/login.jsf"></sec:logout>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider ref="ldapAuthProvider"></sec:authentication-provider>
	</sec:authentication-manager>
	
	<beans:bean id="ldapContextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
  		<beans:constructor-arg value="ldap://localhost:12389/o=mycompany"/>
  		<beans:property name="userDn" value="uid=admin,ou=system"/>
  		<beans:property name="password" value="secret"/>
	</beans:bean>
	
	<beans:bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
 		<beans:constructor-arg>
   			<beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
     			<beans:constructor-arg ref="ldapContextSource"/>
     			<beans:property name="userDnPatterns">
	       			<beans:list>
	       				<beans:value>uid={0},ou=Users</beans:value>
	       			</beans:list>
     			</beans:property>
   			</beans:bean>
 		</beans:constructor-arg>
 		<beans:constructor-arg>
   			<beans:bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
     			<beans:constructor-arg ref="ldapContextSource"/>
     			<beans:constructor-arg value="ou=Groups"/>
     			<beans:property name="groupRoleAttribute" value="cn"/>
   			</beans:bean>
 		</beans:constructor-arg>
	</beans:bean>

	<sec:global-method-security pre-post-annotations="enabled"/>
		
</beans:beans>

No other configuration change is required to make Primefaces work with Spring Security

Integrating Spring Security 3.1 and JSF 2.0

Posted on

Today in this tutorial we will integrate our JSF 2.0 application with Spring Security 3.1.

We will be using the basic security feature of Spring Security and later on we will expand on this.

What we need

  1. JDK
  2. Eclipse
  3. Spring Core
  4. Spring Security
  5. Some additional jars

Project Creation

  1. Create a new “Dynamic Web Project” In Eclipse.
  2. Add all the required libraries as shown below.
  3. Required Libraries

  4. Under “WebContent” create a folder called as “pages
  5. Create four folders under pages namely home, index, secure, unsecure
  6. Folder Structure For Pages

First under home create a new xhtml file named as home.xhtml. It is a simple page with one text field and one command button for navigating.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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">
<h:head>
</h:head>
<h:body>
	<h:form id="homePageFormId">
		<h:outputLabel
			value="Select your resource, depending on selection you may be asked to login"></h:outputLabel>
		<br></br>
		<h:inputText name="option" value="#{navigator.pageToNavigate}" id="optionId" required="True" requiredMessage="Please Enter page to navigate, valid values are ToSecure and ToUnSecure"></h:inputText>
		<h:commandButton value="Navigate" action="#{navigator.navigateTo}">
		</h:commandButton>
	</h:form>
</h:body>
</html>
Second under secure folder create a new xhtml file named as secured.xhtml. We will secure this page using spring security.
secured.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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">
<h:head>
</h:head>
<h:body>
	<h:outputLabel value="I am secured, if you are seeing me it means you have logged in correctly, great!"></h:outputLabel>  
</h:body>
</html>
Third under unsecure folder create a new xhtml file named as unsecured.xhtml. This page will be public and doesn’t need any security.
unsecured.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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">
<h:head>
</h:head>
<h:body>
	<h:outputLabel value="I have no problem with anyone, I am publicly visible!"></h:outputLabel>  
</h:body>
</html>
Fourth under index folder create a jsp file named as index.jsp. This file is the default welcome-file, it will redirect it to home.jsf.
index.jsp

<% response.sendRedirect("pages/home/home.jsf"); %>
Ok, so now we are done with all the different pages that our project needs.
Now is the time to write a managed bean which will help us in navigating to secured and unsecured pages.
Create a new class named as Navigator.java as shown below:

Navigator.java

package com.mumz.jsfspringsec.beans;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean(name="navigator")
@SessionScoped
public class Navigator {
	private String pageToNavigate = "";
	
	public String navigateTo(){
		if("ToSecure".equalsIgnoreCase(pageToNavigate)){
			return "Secured";
		} else if("ToUnSecure".equalsIgnoreCase(pageToNavigate)){
			return "UnSecured";
		} else {
			//This will never happen but we will use this to extend this application
			return "none";
		}
	}
	
	public String getPageToNavigate() {
		return pageToNavigate;
	}
	public void setPageToNavigate(String option) {
		this.pageToNavigate = option;
	}
}
Now, is the time to write our various configuration files.


First we will write our web.xml

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	xsi:schemaLocation=
		"http://java.sun.com/xml/ns/javaee 
     	http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	version="3.0">
	<!-- CONFIGURATION FILES both Bean definition and security -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
				/WEB-INF/classes/CONFIGURATION/SPRING/BEANDEFINITION/jsfspring-sec-bean-config.xml
				/WEB-INF/classes/CONFIGURATION/SPRING/SECURITY/jsfspring-sec-security-config.xml
		</param-value>
	</context-param>
	<!-- CONFIGURATION FILES END HERE -->
	<!-- PROJECT STAGE START FOR DEVELOPEMENT MARK IT AS DEVELOPMENT, FOR TESTING, UAT, PRODUCTION REMOVE THIS -->
	<context-param>
		<param-name>javax.faces.PROJECT_STAGE</param-name>
		<param-value>Development</param-value>
	</context-param>
	<!-- PROJECT STAGE END -->
	<!-- Enable JSF Servlet -->
	<servlet>
		<servlet-name>Faces Servlet</servlet-name>
		<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>Faces Servlet</servlet-name>
		<url-pattern>*.jsf</url-pattern>
	</servlet-mapping>
  	<!-- Enable JSF Server End-->
  	<!-- Integrate JSF and Spring -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!-- Integrate JSF and Spring End-->
	<!-- Enable Spring Filter, Spring Security works on the concept of Filters -->
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>
			org.springframework.web.filter.DelegatingFilterProxy
		</filter-class>
	</filter>
	
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<!-- Enable Spring Filter End -->
	<!-- Welcome File  -->
	<welcome-file-list>
		<welcome-file>/pages/index/index.jsp</welcome-file>
	</welcome-file-list>
	<!-- Welcome File End-->
</web-app>
Second lets write our navigation mechanism, remember JSF needs a valid faces-config.xml.


faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
    <!-- Enable Spring -->
    <application>
    	<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
    </application>
    <!-- Simple Navigation Rule -->
    <!-- If  user keys in ToSecure, move to /pages/secure/secured.xhtml-->
    <!-- Else If user keys in ToUnSecure, move to /pages/unsecure/unsecured.xhtml-->
    <navigation-rule>
    	<display-name>pages/home/home.xhtml</display-name>
    	<from-view-id>/pages/home/home.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{navigator.navigateTo}</from-action>
    		<from-outcome>Secured</from-outcome>
    		<to-view-id>/pages/secure/secured.xhtml</to-view-id>
    		<redirect></redirect>
    	</navigation-case>
    </navigation-rule>
    <navigation-rule>
    	<display-name>pages/home/home.xhtml</display-name>
    	<from-view-id>/pages/home/home.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{navigator.navigateTo}</from-action>
    		<from-outcome>UnSecured</from-outcome>
    		<to-view-id>/pages/unsecure/unsecured.xhtml</to-view-id>
    		<redirect></redirect>
    	</navigation-case>
    </navigation-rule>
</faces-config>
Third now we start with Spring and Spring Security.

Create a folder called as Resource under your project and few other child folder as shown in the below image. This way we can breakup our configuration files across multiple directories for better maintainability.

Resources Folder Structure

With our configuration folder structure in place, lets write our Spring related configuration files.


First we will write our bean configuration file. Create a new xml file under BEANDEFINITION folder and name it jsfspring-sec-bean-config.xml. This contains only one bean definition which is our Navigator bean.

jsfspring-sec-bean-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
       http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
       
	<beans:bean id="navigator" name="navigator" class="com.mumz.jsfspringsec.beans.Navigator" scope="session">
	</beans:bean>
</beans:beans>
Second lets write our main Spring Security configuration. Create a new xml file under SECURITY folder and name it as jsfspring-sec-security-config.xml

jsfspring-sec-security-config.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans:beans 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	   http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	   
	 <sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider>
			<sec:user-service>
				<sec:user authorities="ROLE_USER" name="admin" password="admin" />
			</sec:user-service>
		</sec:authentication-provider>
	</sec:authentication-manager>	
</beans:beans>
In the above configuration we have restricted user for /pages/secure/**, which means to access these resources you have to login with username = admin and password = admin, for non-secure location /pages/unsecure/** there is no need for logging into the system.

If you followed all the steps your final project structure should resemble like:
That’s it, we are now ready to see Spring Security 3.1 and JSF 2.0 in action.

In the next post we will see how can we move away from hardcoded userid and password to a database based authentication.

Integrate LDAP and Spring Security 3.1 for Authentication and Authorization in a JSF 2.0 Application.

Posted on Updated on

Backtracking all our previous tutorial you will notice we always had our user definition with password placed in our database. Not every time this will be the case and frequently we will have to integrate our application with LDAP. In this tutorial we will integrate our earlier application with Apache Directory Services. If you don’t know how to configure Apache DS then take look at Configuring Apache DS.

In this post we are not implementing anything for remember-me service which we did earlier. Let’s get started with code changes to cater to LDAP Spring Security Integration for both Authentication and Authorization

First We will clean up jsfspring-sec-bean-config.xml as we don’t need anything related to database.

jsfspring-sec-bean-config.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
       http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
       
	<beans:bean id="navigator" name="navigator" class="com.mumz.jsfspringsec.beans.Navigator" scope="session">
		<beans:property name="businessModel" ref="businessModel"></beans:property>
	</beans:bean>
	
	<beans:bean id="loginBean" name="loginBean" class="com.mumz.jsfspringsec.beans.LoginBean" scope="prototype">
		<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
	</beans:bean>
	
	<beans:bean id="securityBean" class="com.mumz.jsfspringsec.beans.SecurityHolderBean" scope="session">
	</beans:bean>
	
	<beans:bean id="businessModel" class="com.mumz.jsfspringsec.business.model.BusinessModel" scope="prototype"></beans:bean>
	
	<beans:bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	 	<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<beans:property name="url" value="jdbc:mysql://localhost:3306/jsf-spring-security" />
		<beans:property name="username" value="root" />
		<beans:property name="password" value="root" />
   </beans:bean>
</beans:beans>

Notice that configuration is very simple and all information related to custom database and user details service have been removed.

Second we will update jsfspring-sec-security-config.xml. We will define LdapAuthenticationProvider which uses BindAuthenticator. This won’t work where LDAP is secured and logging to LDAP is disabled. In those cases use PasswordComparisonAuthenticator.

jsfspring-sec-security-config.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	  http://www.springframework.org/schema/beans
	  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	  http://www.springframework.org/schema/security
	  http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	  
	<sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/pages/common/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
		<sec:form-login login-page="/pages/common/login.jsf"/>
		<sec:logout 
			invalidate-session="true" 
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE" 
			logout-success-url="/pages/common/login.jsf"></sec:logout>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider ref="ldapAuthProvider"></sec:authentication-provider>
	</sec:authentication-manager>
	
	<beans:bean id="ldapContextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
  		<beans:constructor-arg value="ldap://localhost:12389/o=mycompany"/>
  		<beans:property name="userDn" value="uid=admin,ou=system"/>
  		<beans:property name="password" value="secret"/>
	</beans:bean>
	
	<beans:bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
 		<beans:constructor-arg>
   			<beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
     			<beans:constructor-arg ref="ldapContextSource"/>
     			<beans:property name="userDnPatterns">
	       			<beans:list>
	       				<beans:value>uid={0},ou=Users</beans:value>
	       			</beans:list>
     			</beans:property>
   			</beans:bean>
 		</beans:constructor-arg>
 		<beans:constructor-arg>
   			<beans:bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
     			<beans:constructor-arg ref="ldapContextSource"/>
     			<beans:constructor-arg value="ou=Groups"/>
     			<beans:property name="groupRoleAttribute" value="cn"/>
   			</beans:bean>
 		</beans:constructor-arg>
	</beans:bean>

	<sec:global-method-security pre-post-annotations="enabled"/>
		
</beans:beans>

Finally our sleek LoginBean.java which just does the login, remember me has been removed.

LoginBean.java

package com.mumz.jsfspringsec.beans;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * The Class LoginBean.
 */
@ManagedBean(name = "loginMgmtBean")
@RequestScoped
public class LoginBean {

	/** The user name. */
	private String					userName				= null;

	/** The password. */
	private String					password				= null;

	/** The remember me. */
	private String					rememberMe				= null;

	/** The authentication manager. */
	@ManagedProperty(value = "#{authenticationManager}")
	private AuthenticationManager	authenticationManager	= null;
	
	/**
	 * Login.
	 * 
	 * @return the string
	 */
	public String login() {
		try {
			Authentication result = null;
			Authentication request = new UsernamePasswordAuthenticationToken(this.getUserName(), this.getPassword());
			result = authenticationManager.authenticate(request);
			SecurityContextHolder.getContext().setAuthentication(result);
		} catch (AuthenticationException e) {
			e.printStackTrace();
		}
		return "Secured";
	}

	/**
	 * Cancel.
	 * 
	 * @return the string
	 */
	public String cancel() {
		return null;
	}

	/**
	 * Logout.
	 * 
	 * @return the string
	 */
	public String logout() {
		SecurityContextHolder.clearContext();
		/**
		 * Delete Cookies
		 */
		HttpServletRequest httpServletRequest = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
		HttpServletResponse httpServletResponse = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext()
				.getResponse();
		Cookie cookie = new Cookie("SPRING_SECURITY_REMEMBER_ME_COOKIE", null);
		cookie.setMaxAge(0);
		cookie.setPath(httpServletRequest.getContextPath().length() > 0 ? httpServletRequest.getContextPath() : "/");
		httpServletResponse.addCookie(cookie);
		return "loggedout";
	}

	/**
	 * Gets the user name.
	 * 
	 * @return the user name
	 */
	public String getUserName() {
		return userName;
	}

	/**
	 * Sets the user name.
	 * 
	 * @param userName
	 *            the new user name
	 */
	public void setUserName(String userName) {
		this.userName = userName;
	}

	/**
	 * Gets the password.
	 * 
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * Sets the password.
	 * 
	 * @param password
	 *            the new password
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Gets the remember me.
	 * 
	 * @return the remember me
	 */
	public String getRememberMe() {
		return rememberMe;
	}

	/**
	 * Sets the remember me.
	 * 
	 * @param rememberMe
	 *            the new remember me
	 */
	public void setRememberMe(String rememberMe) {
		this.rememberMe = rememberMe;
	}

	/**
	 * Gets the authentication manager.
	 * 
	 * @return the authentication manager
	 */
	public AuthenticationManager getAuthenticationManager() {
		return authenticationManager;
	}

	/**
	 * Sets the authentication manager.
	 * 
	 * @param authenticationManager
	 *            the new authentication manager
	 */
	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}
}

That’s all we need for integration Spring Security and LDAP (Apache DS in this case) for both Authorization and Authentication. There is every possibility that LDAP may be used for Authentication and Authorization will be done from database, in another post we will look at one way of implementing such a solution.

References:

  1. Spring Source Documentation

Secure your business layer with Spring Security Annotation

Posted on

In the last post we secured our business layer with JSR-250 annotation. Spring Security provides similar functionality. In this tutorial we will use @PreAuthorize annotation to secure our business model built earlier.

First change is in our BusinessModel.java where we move from @RolesAllowed to @PreAuthorize, one reason why you would need Spring Security annotation is JSR-250 doesn’t support expression language but Spring does, so if you want to add say a username comparison check it cannot be done using @RolesAllowed but with @PreAuthorize and @PostAuthorize you can.

BusinessModel.java

package com.mumz.jsfspringsec.business.model;

import org.springframework.security.access.prepost.PreAuthorize;

/**
 * The Class BusinessModel.
 * @author prabhat.jha
 */
public class BusinessModel {
	
	/**
	 * Gets the business latest news.
	 *
	 * @return the business latest news
	 */
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	public String getBusinessLatestNews(){
		return "Business is doing great!";
	}
}

Next change is in jsfspring-sec-security-config.xml where instead of

<sec:global-method-security jsr250-annotations="enabled"></sec:global-method-security>

we will use

<sec:global-method-security pre-post-annotations="enabled"/>

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	   http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	   
	 <sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/pages/common/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
		<sec:form-login login-page="/pages/common/login.jsf"/>
		<sec:remember-me key="jsfspring-sec" services-ref="rememberMeServices"/>
		<sec:logout 
			invalidate-session="true" 
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE" 
			logout-success-url="/pages/common/login.jsf"></sec:logout>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider ref="rememberMeAuthenticationProvider"></sec:authentication-provider>
		<sec:authentication-provider user-service-ref="customjdbcUserService">
			<sec:password-encoder ref="passwordEncoder">
			</sec:password-encoder>
		</sec:authentication-provider>
	</sec:authentication-manager>
	
	<sec:global-method-security pre-post-annotations="enabled"/>
</beans:beans>

That’s the only change we have to do in order to move from JSR-250 to Spring Annotation.

Secure your business layer with JSR-250 and Spring Security in a JSF application

Posted on Updated on

In the previouspost we looked at the different option that we have as part of Java EE and Spring security framework. In this tutorial we will see how we can leverage JSR-250 security annotation.

To being with you need the source code from previous post. With some code behind us, let’s see how JSR-250 can be used to secure business methods.

First let’s modify our secured.xhtml, where we will add one extra commandLink to navigate to next page. This commandLink should only be visible to admin user but to showcase the usage of JSR-250 we will randomly make it visible to any logged in user.

secured.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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">
<h:head>
</h:head>
<h:body>
	<h:form>
		<h:outputText value="This message is for admin only" id="adminonlyoutputtext" rendered="#{securityBean.isUserAdmin}"></h:outputText>
		<br/>
		<h:outputText value="This message is for user only" id="guestonlyoutputtext" rendered="#{securityBean.isUser}"></h:outputText>
		<br/>
		<h:commandLink action="#{navigator.showAdminPersonalPage}" rendered="#{securityBean.showAdminPersonalPage}" value="MyPersonalPage"></h:commandLink>
		<br/>
		<h:outputLabel value="I am secured, if you are seeing me it means you have logged in correctly, great!"></h:outputLabel>
		<br/>
		<h:commandLink id="logout" action="#{loginMgmtBean.logout}" value="Logout"></h:commandLink>
		<h:messages></h:messages>
	</h:form>
</h:body>
</html>

Second we will modify our Navigator.java and add implementation for showAdminPersonalPage, which will invoke the business layer which is secured and it requires admin to be logged in.

Navigator.java


package com.mumz.jsfspringsec.beans;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;

import com.mumz.jsfspringsec.business.model.BusinessModel;

/**
 * The Class Navigator.
 * @author prabhat.jha
 */
@ManagedBean(name="navigator")
@SessionScoped
public class Navigator {
	
	/** The page to navigate. */
	private String pageToNavigate = "";
	
	/** The business model. */
	@ManagedProperty(value="businessModel")
	private BusinessModel businessModel = null;
	
	/** The latest news. */
	private String latestNews = null;
	
	/**
	 * Navigate to.
	 *
	 * @return the string
	 */
	public String navigateTo(){
		if("ToSecure".equalsIgnoreCase(pageToNavigate)){
			return "Secured";
		} else if("ToUnSecure".equalsIgnoreCase(pageToNavigate)){
			return "UnSecured";
		} else {
			//This will never happen but we will use this to extend this application
			return "none";
		}
	}
	
	/**
	 * Show admin personal page.
	 *
	 * @return the string
	 */
	public String showAdminPersonalPage(){
		this.setLatestNews(this.getBusinessModel().getBusinessLatestNews());
		return "adminPersonalDetails.xhtml";
	}
	
	/**
	 * Gets the page to navigate.
	 *
	 * @return the page to navigate
	 */
	public String getPageToNavigate() {
		return pageToNavigate;
	}
	
	/**
	 * Sets the page to navigate.
	 *
	 * @param option the new page to navigate
	 */
	public void setPageToNavigate(String option) {
		this.pageToNavigate = option;
	}

	/**
	 * Sets the business model.
	 *
	 * @param businessModel the new business model
	 */
	public void setBusinessModel(BusinessModel businessModel) {
		this.businessModel = businessModel;
	}

	/**
	 * Gets the business model.
	 *
	 * @return the business model
	 */
	public BusinessModel getBusinessModel() {
		return businessModel;
	}

	/**
	 * Sets the latest news.
	 *
	 * @param latestNews the new latest news
	 */
	public void setLatestNews(String latestNews) {
		this.latestNews = latestNews;
	}

	/**
	 * Gets the latest news.
	 *
	 * @return the latest news
	 */
	public String getLatestNews() {
		return latestNews;
	}
}

Third our business layer which happens to be a very simple class but will suffice for our use case.

BusinessModel.java


package com.mumz.jsfspringsec.business.model;

import javax.annotation.security.RolesAllowed;

/**
 * The Class BusinessModel.
 * @author prabhat.jha
 */
public class BusinessModel {
	
	/**
	 * Gets the business latest news.
	 *
	 * @return the business latest news
	 */
	@RolesAllowed(value="ROLE_ADMIN")
	public String getBusinessLatestNews(){
		return "Business is doing great!";
	}
}

Fourth we will update our SecurityHolderBean and add security information for ROLE_USER all this time we were playing with ROLE_ADMIN and ROLE_GUEST.

SecurityHolderBean.java


package com.mumz.jsfspringsec.beans;

import java.util.Random;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * The Class SecurityHolderBean.
 * @author prabhat.jha
 */
@ManagedBean(name = "securityBean")
@SessionScoped
public class SecurityHolderBean {
	
	/** The is user admin. */
	private boolean isUserAdmin = false;
	
	/** The is user guest. */
	private boolean isUserGuest = false;
	
	/** The is user. */
	private boolean isUser = false;
	
	/** The show admin personal page. */
	private boolean showAdminPersonalPage = false;
	
	/**
	 * Gets the checks if is user admin.
	 * 
	 * @return the checks if is user admin
	 */
	public boolean getIsUserAdmin() {
		isUserAdmin = false;
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		for (GrantedAuthority grantedAuthority : authentication
				.getAuthorities()) {
			if ("ROLE_ADMIN".equals(grantedAuthority.getAuthority())) {
				isUserAdmin = true;
				break;
			}
		}
		return isUserAdmin;
	}

	/**
	 * Gets the checks if is user guest.
	 * 
	 * @return the checks if is user guest
	 */
	public boolean getIsUserGuest() {
		isUserGuest = false;
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		for (GrantedAuthority grantedAuthority : authentication
				.getAuthorities()) {
			if ("ROLE_GUEST".equals(grantedAuthority.getAuthority())) {
				isUserGuest = true;
				break;
			}
		}
		return isUserGuest;
	}

	/**
	 * Sets the user admin.
	 * 
	 * @param isUserAdmin
	 *            the new user admin
	 */
	public void setUserAdmin(boolean isUserAdmin) {
		this.isUserAdmin = isUserAdmin;
	}

	/**
	 * Sets the user guest.
	 * 
	 * @param isUserGuest
	 *            the new user guest
	 */
	public void setUserGuest(boolean isUserGuest) {
		this.isUserGuest = isUserGuest;
	}

	/**
	 * Sets the user.
	 * 
	 * @param isUser
	 *            the new user
	 */
	public void setIsUser(boolean isUser) {
		this.isUser = isUser;
	}

	/**
	 * Checks if is user.
	 * 
	 * @return true, if is user
	 */
	public boolean getIsUser() {
		isUser = false;
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		for (GrantedAuthority grantedAuthority : authentication
				.getAuthorities()) {
			if ("ROLE_USER".equals(grantedAuthority.getAuthority())) {
				isUser = true;
				break;
			}
		}
		return isUser;
	}

	/**
	 * Sets the show admin personal page.
	 * 
	 * @param showAdminPersonalPage
	 *            the new show admin personal page
	 */
	public void setShowAdminPersonalPage(boolean showAdminPersonalPage) {
		this.showAdminPersonalPage = showAdminPersonalPage;
	}

	/**
	 * Gets the show admin personal page.
	 * 
	 * @return the show admin personal page
	 */
	public boolean getShowAdminPersonalPage() {
		showAdminPersonalPage = false;
		if(getIsUserAdmin()){
			showAdminPersonalPage = true;
		} else {
			/**
			 * Randomly decide whether to show admin Personal Page or not.
			 */
			Random random = new Random();
			if(random.nextInt(2) % 2 == 0){
				showAdminPersonalPage = true;
			}
		}
		return showAdminPersonalPage;
	}
}

Fifth since all this while we have been using JSF explicit navigation we will follow the same and update our faces-config.xml to add navigation to move to AdminPersonal page.

faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
    <!-- Enable Spring -->
    <application>
    	<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
    </application>
    <!-- Simple Navigation Rule -->
    <!-- If  user keys in ToSecure, move to /pages/secure/secured.xhtml-->
    <!-- Else If user keys in ToUnSecure, move to /pages/unsecure/unsecured.xhtml-->
    <navigation-rule>
    	<display-name>pages/home/home.xhtml</display-name>
    	<from-view-id>/pages/home/home.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{navigator.navigateTo}</from-action>
    		<from-outcome>Secured</from-outcome>
    		<to-view-id>/pages/secure/secured.xhtml</to-view-id>
    		<redirect></redirect>
    	</navigation-case>
    </navigation-rule>
    <navigation-rule>
    	<display-name>pages/home/home.xhtml</display-name>
    	<from-view-id>/pages/home/home.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{navigator.navigateTo}</from-action>
    		<from-outcome>UnSecured</from-outcome>
    		<to-view-id>/pages/unsecure/unsecured.xhtml</to-view-id>
    		<redirect></redirect>
    	</navigation-case>
    </navigation-rule>
    <navigation-rule>
    	<display-name>pages/secure/secured.xhtml</display-name>
    	<from-view-id>/pages/secure/secured.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{loginMgmtBean.logout}</from-action>
    		<from-outcome>loggedout</from-outcome>
    		<to-view-id>/pages/home/home.xhtml</to-view-id>
    		<redirect></redirect>
    	</navigation-case>
    </navigation-rule>
    <navigation-rule>
    	<display-name>pages/common/login.xhtml</display-name>
    	<from-view-id>/pages/common/login.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{loginMgmtBean.login}</from-action>
    		<from-outcome>Secured</from-outcome>
    		<to-view-id>/pages/secure/secured.xhtml</to-view-id>
    	</navigation-case>
    </navigation-rule>
    <navigation-rule>
    	<display-name>pages/secure/secured.xhtml</display-name>
    	<from-view-id>/pages/secure/secured.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{navigator.showAdminPersonalPage}</from-action>
    		<from-outcome>adminPersonalDetails</from-outcome>
    		<to-view-id>/pages/secure/adminPersonalDetails.xhtml</to-view-id>
    		<redirect />
    	</navigation-case>
    </navigation-rule>
</faces-config>

Sixth we will update our bean configuration as we are not using auto wiring capability of Spring.

jsf-spring-sec-bean-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
       http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
       
	<beans:bean id="navigator" name="navigator" class="com.mumz.jsfspringsec.beans.Navigator" scope="session">
		<beans:property name="businessModel" ref="businessModel"></beans:property>
	</beans:bean>
	
	<beans:bean id="loginBean" name="loginBean" class="com.mumz.jsfspringsec.beans.LoginBean" scope="prototype">
		<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
		<beans:property name="rememberMeServices" ref="rememberMeServices"></beans:property>
		<beans:property name="userDetailsService" ref="customjdbcUserService"></beans:property>
	</beans:bean>
	
	<beans:bean id="securityBean" class="com.mumz.jsfspringsec.beans.SecurityHolderBean" scope="session">
	</beans:bean>
	
	<beans:bean id="businessModel" class="com.mumz.jsfspringsec.business.model.BusinessModel" scope="prototype"></beans:bean>
	
	<beans:bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	 	<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<beans:property name="url" value="jdbc:mysql://localhost:3306/jsf-spring-security" />
		<beans:property name="username" value="root" />
		<beans:property name="password" value="root" />
   </beans:bean>
   
   <beans:bean id="customjdbcUserService" class="com.mumz.jsfspringsec.dao.CustomJDBCDaoImpl">
   		<beans:property name="dataSource" ref="dataSource"/>
		<beans:property name="enableAuthorities" value="false"/>
		<beans:property name="enableGroups" value="true"></beans:property>
		<beans:property name="usersByUsernameQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENABLED FROM JSF_SPRING_SEC_USERS WHERE JSF_SPRING_SEC_USERS_USERNAME = ?</beans:value>
		</beans:property>
		<beans:property name="authoritiesByUsernameQuery">
			<beans:value>
				 SELECT JSF_SPRING_SEC_ROLES_USERNAME,JSF_SPRING_SEC_ROLES_ROLE_NAME from JSF_SPRING_SEC_ROLES where JSF_SPRING_SEC_ROLES_USERNAME = ?
			</beans:value>
		</beans:property>
		<beans:property name="groupAuthoritiesByUsernameQuery">
			<beans:value>
				SELECT 
						GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID, 
						GROUPDTLS.JSF_SPRING_GROUPS_GROUP_NAME, 
						GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY 
				FROM 
						JSF_SPRING_GROUPS GROUPDTLS, 
						JSF_SPRING_SEC_GROUP_AUTHORITIES GROUPPERMISSION, 
						JSF_SPRING_SEC_GROUP_MEMBERS GROUPMEMBERS, 
						JSF_SPRING_SEC_USERS USERS 
				WHERE 
						USERS.JSF_SPRING_SEC_USERS_USERNAME = ? AND 
						GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID = USERS.PK_JSF_SPRING_SEC_USERS AND 
						GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID = GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID AND 
						GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID = GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID 
			</beans:value>
		</beans:property>
   </beans:bean>
   
	<beans:bean id="rememberMeServices"
		class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
		<beans:property name="key" value="jsfspring-sec" /> 
		<beans:property	name="userDetailsService" ref="customjdbcUserService" /> 
		<beans:property	name="alwaysRemember" value="true" /> 
		<beans:property	name="tokenValiditySeconds" value="60" /> 
	</beans:bean>
	
	<beans:bean id="rememberMeAuthenticationProvider" 
		class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
  		<beans:property name="key" value="jsfspring-sec"/>
	</beans:bean>
	
	<beans:bean id="rememberMeFilter" 
		class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
  		<beans:property name="rememberMeServices" ref="rememberMeServices"/>
  		<beans:property name="authenticationManager" ref="authenticationManager" />
	</beans:bean>
	
	<beans:bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="passwordEncoder">
	</beans:bean>
	
	<beans:bean id="databasePasswordEncrypter" class="com.mumz.jsfspringsec.dao.security.DBPasswordEncrypterBean" init-method="encryptDBPassword" depends-on="dataSource">
		<beans:property name="passwordEncoder" ref="passwordEncoder"></beans:property>
		<beans:property name="dataSource" ref="dataSource"></beans:property>
		<beans:property name="selectQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENCRYPTED FROM JSF_SPRING_SEC_USERS WHERE (JSF_SPRING_SEC_USERS_ENCRYPTED='' || JSF_SPRING_SEC_USERS_ENCRYPTED IS NULL)</beans:value>
		</beans:property>
		<beans:property name="updateQuery">
			<beans:value>UPDATE JSF_SPRING_SEC_USERS SET JSF_SPRING_SEC_USERS_PASSWORD = ?, JSF_SPRING_SEC_USERS_ENCRYPTED='YES' WHERE JSF_SPRING_SEC_USERS_USERNAME = ? </beans:value>
		</beans:property>
	</beans:bean>
</beans:beans>

Final step is to enable JSF-250 so we will update our jsf-spring-sec-security-config.xml.

jsf-spring-sec-security-config.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	   http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	   
	 <sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/pages/common/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
		<sec:form-login login-page="/pages/common/login.jsf"/>
		<sec:remember-me key="jsfspring-sec" services-ref="rememberMeServices"/>
		<sec:logout 
			invalidate-session="true" 
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE" 
			logout-success-url="/pages/common/login.jsf"></sec:logout>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider ref="rememberMeAuthenticationProvider"></sec:authentication-provider>
		<sec:authentication-provider user-service-ref="customjdbcUserService">
			<sec:password-encoder ref="passwordEncoder">
			</sec:password-encoder>
		</sec:authentication-provider>
	</sec:authentication-manager>
	
	<sec:global-method-security jsr250-annotations="enabled"></sec:global-method-security>
</beans:beans>

That’s all, before running this example remember to have a user whose has authority of ROLE_USER. If you login as user randomly you will see MyPeronalPage link, which is meant only for Admin. When you try to access it you will get an Not Authorized Exception. In our current implementation we have intentionally chosen to show that link to user but it is good enough to realize our use case of business layer security with JSR-250.

Data dump is given below:

INSERT INTO `jsf_spring_sec_users` (`PK_JSF_SPRING_SEC_USERS`, `JSF_SPRING_SEC_USERS_USERNAME`, `JSF_SPRING_SEC_USERS_PASSWORD`, `JSF_SPRING_SEC_USERS_ENABLED`, `JSF_SPRING_SEC_USERS_CREATED_DT`, `JSF_SPRING_SEC_USERS_MODIFIED_DT`) VALUES (1,'admin','admin','true','2012-07-17 10:07:41','2012-07-17 10:07:41'),(2,'guest','guest','true','2012-07-17 16:59:13','2012-07-17 16:59:13','YES'),(3,'user','user','true','2012-07-25 16:59:13','2012-07-25 16:59:13');

INSERT INTO `jsf_spring_sec_groups` (`JSF_SPRING_SEC_GROUPS_GROUP_ID`, `JSF_SPRING_SEC_GROUPS_GROUP_NAME`) 
VALUES (1,'ADMIN'),(2,'GUEST'),(3,'USER');

INSERT INTO `jsf_spring_sec_group_authorities` (`JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY_ID`, `JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID`, `JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY`) VALUES (1,1,'ROLE_USER'),(2,2,'ROLE_GUEST'),(3,1,'ROLE_ADMIN'),(4,3,'ROLE_USER');

INSERT INTO `jsf_spring_sec_group_members` (`JSF_SPRING_SEC_GROUP_MEMBERS_ID`, `JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID`, `JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID`) VALUES (1,1,1),(2,2,2),(3,3,3);

Business Layer Security with Java EE and Spring

Posted on

If you have read previous tutorial about Spring Security and JSF you would have noticed that all this time we have been speaking about securing our web layer. What about business layer ? In this post we will go through few ways of securing our business layer, and then in subsequent post we will see practical examples how do we accomplish our goal.

Before we begin, first question is why do we bother about business layer security ? Multiple answers can be given for this question:

  1. Business layer can be exposed via webserive
  2. Any security break on web layer will expose business layer

and like wise. When we speak about security it’s better to be over secured instead of exposing weakness and later trying to secure the application.

With this much of background let’s try to explore what Java EE and Spring has to offer as far as business layer security is concerned.

Java EE provides JSR-250 which provides basic annotation for Java EE application. For security you have :

  1. RunAs – Given a method this annotation allows you to execute a method as a given user, for e.g run a method as ADMIN, definitely the security realm should have ADMIN configured
  2. RolesAllowed – A comma delimited value which specifies which all roles have access to a given method
  3. PermitAll – This allows everyone to run a given method
  4. DenyAll – No one has access to this method, cannot be executed

Now let’s see what Spring has to offer. Spring allows two versions xml or annotation, alongside Spring also allows JSR-250 annotations.

  1. intecept-method (XML configuration)- This is very similar to RolesAllowed only difference is that this is xml configuration other one is annotated
  2. PreAuthorize (Annotation) – This is also similar to RolesAllowed but this also allows us to use Spring’s Expression Language
  3. PostAuthorize (Annotation) – This seems redundant if you have a PreAuthorize but there can be instances where even after processing you would like to know if caller is still authorize, seems okay when security is concerned.
  4. Secure (Annotation)- You won’t be finding this in the new code, but in earlier code you can come across this. This is exactly as RolesAllowed
  5. With use of Spring PostAuthorize you can remove elements from say a Collection( remember this will modify your collection permanently) which may be useful in many cases

This post has just given some ways in which business layer can be secured, in next few weeks we will see how exactly we can accomplish this.

Spring Security 3.1 conditionally securing your web page with JSF 2.0

Posted on

Hello, in all our previous use cases we secured a whole page, but what if we want to secure only a certain element or part ?

Consider a scenario where message displayed on admin login and guest login is different, message is a very simple example, you may want to show different menu to admin. Using Spring Security there are two ways to accomplish this.

  1. Use Spring Security tag lib
  2. Use JSF attributes and use managed bean to manage security

In this tutorial we will enhance our application from Spring Security 3.1 Adding Salt to Password using BCryptPasswordEncoder and JSF 2.0 and add conditional rendering of element.

First we will change our secured.xhtml to add different message for admin and guest.

secured.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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">
<h:head>
</h:head>
<h:body>
	<h:form>
		<h:outputText value="This message is for admin only" id="adminonlyoutputtext" rendered="#{securityBean.isUserAdmin}"></h:outputText>
		<br/>
		<h:outputText value="This message is for guest only" id="guestonlyoutputtext" rendered="#{securityBean.isUserGuest}"></h:outputText>
		<br/>
		<h:outputLabel value="I am secured, if you are seeing me it means you have logged in correctly, great!"></h:outputLabel>
		<h:commandLink id="logout" action="#{loginMgmtBean.logout}" value="Logout"></h:commandLink>
		<h:messages></h:messages>
	</h:form>
</h:body>
</html>

Second we will write one simple managed bean which will tell us if the logged in user is admin or guest.

SecurityHolderBean

package com.mumz.jsfspringsec.beans;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

@ManagedBean(name = "securityBean")
@SessionScoped
public class SecurityHolderBean {
	private boolean isUserAdmin = false;
	private boolean isUserGuest = false;

	public boolean getIsUserAdmin() {
		isUserAdmin = false;
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		for (GrantedAuthority grantedAuthority : authentication
				.getAuthorities()) {
			if ("ROLE_ADMIN".equals(grantedAuthority.getAuthority())) {
				isUserAdmin = true;
				break;
			}
		}
		return isUserAdmin;
	}

	public boolean getIsUserGuest() {
		isUserGuest = false;
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		for (GrantedAuthority grantedAuthority : authentication
				.getAuthorities()) {
			if ("ROLE_GUEST".equals(grantedAuthority.getAuthority())) {
				isUserGuest = true;
				break;
			}
		}
		return isUserGuest;
	}

	public void setUserAdmin(boolean isUserAdmin) {
		this.isUserAdmin = isUserAdmin;
	}

	public void setUserGuest(boolean isUserGuest) {
		this.isUserGuest = isUserGuest;
	}
}

Last we will update our jsfspring-sec-bean-config.xml to add our managed bean to Spring configuration.

jsfspring-sec-bean-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
       http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd"
	   >
       
	<beans:bean id="navigator" name="navigator" class="com.mumz.jsfspringsec.beans.Navigator" scope="session">
	</beans:bean>
	
	<beans:bean id="loginBean" name="loginBean" class="com.mumz.jsfspringsec.beans.LoginBean" scope="prototype">
		<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
		<beans:property name="rememberMeServices" ref="rememberMeServices"></beans:property>
		<beans:property name="userDetailsService" ref="customjdbcUserService"></beans:property>
	</beans:bean>
	
	<beans:bean id="securityBean" class="com.mumz.jsfspringsec.beans.SecurityHolderBean" scope="session">
	</beans:bean>
	
	<beans:bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	 	<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<beans:property name="url" value="jdbc:mysql://localhost:3306/jsf-spring-security" />
		<beans:property name="username" value="root" />
		<beans:property name="password" value="root" />
   </beans:bean>
   
   <beans:bean id="customjdbcUserService" class="com.mumz.jsfspringsec.dao.CustomJDBCDaoImpl">
   		<beans:property name="dataSource" ref="dataSource"/>
		<beans:property name="enableAuthorities" value="false"/>
		<beans:property name="enableGroups" value="true"></beans:property>
		<beans:property name="usersByUsernameQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENABLED FROM JSF_SPRING_SEC_USERS WHERE JSF_SPRING_SEC_USERS_USERNAME = ?</beans:value>
		</beans:property>
		<beans:property name="authoritiesByUsernameQuery">
			<beans:value>
				 SELECT JSF_SPRING_SEC_ROLES_USERNAME,JSF_SPRING_SEC_ROLES_ROLE_NAME from JSF_SPRING_SEC_ROLES where JSF_SPRING_SEC_ROLES_USERNAME = ?
			</beans:value>
		</beans:property>
		<beans:property name="groupAuthoritiesByUsernameQuery">
			<beans:value>
				SELECT 
						GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID, 
						GROUPDTLS.JSF_SPRING_GROUPS_GROUP_NAME, 
						GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY 
				FROM 
						JSF_SPRING_GROUPS GROUPDTLS, 
						JSF_SPRING_SEC_GROUP_AUTHORITIES GROUPPERMISSION, 
						JSF_SPRING_SEC_GROUP_MEMBERS GROUPMEMBERS, 
						JSF_SPRING_SEC_USERS USERS 
				WHERE 
						USERS.JSF_SPRING_SEC_USERS_USERNAME = ? AND 
						GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID = USERS.PK_JSF_SPRING_SEC_USERS AND 
						GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID = GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID AND 
						GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID = GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID 
			</beans:value>
		</beans:property>
   </beans:bean>
   
	<beans:bean id="rememberMeServices"
		class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
		<beans:property name="key" value="jsfspring-sec" /> 
		<beans:property	name="userDetailsService" ref="customjdbcUserService" /> 
		<beans:property	name="alwaysRemember" value="true" /> 
		<beans:property	name="tokenValiditySeconds" value="60" /> 
	</beans:bean>
	
	<beans:bean id="rememberMeAuthenticationProvider" 
		class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
  		<beans:property name="key" value="jsfspring-sec"/>
	</beans:bean>
	
	<beans:bean id="rememberMeFilter" 
		class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
  		<beans:property name="rememberMeServices" ref="rememberMeServices"/>
  		<beans:property name="authenticationManager" ref="authenticationManager" />
	</beans:bean>
	
	<beans:bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="passwordEncoder">
	</beans:bean>
	
	<beans:bean id="databasePasswordEncrypter" class="com.mumz.jsfspringsec.dao.security.DBPasswordEncrypterBean" init-method="encryptDBPassword" depends-on="dataSource">
		<beans:property name="passwordEncoder" ref="passwordEncoder"></beans:property>
		<beans:property name="dataSource" ref="dataSource"></beans:property>
		<beans:property name="selectQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENCRYPTED FROM JSF_SPRING_SEC_USERS WHERE (JSF_SPRING_SEC_USERS_ENCRYPTED='' || JSF_SPRING_SEC_USERS_ENCRYPTED IS NULL)</beans:value>
		</beans:property>
		<beans:property name="updateQuery">
			<beans:value>UPDATE JSF_SPRING_SEC_USERS SET JSF_SPRING_SEC_USERS_PASSWORD = ?, JSF_SPRING_SEC_USERS_ENCRYPTED='YES' WHERE JSF_SPRING_SEC_USERS_USERNAME = ? </beans:value>
		</beans:property>
	</beans:bean>
</beans:beans>

All done, login as admin and guest and see the result in action.