Spring Security, formerly known as Acegi Security, is a framework that provides a way to secure your spring application. As you look for online resources on Spring Security, you will find several people using it with Grails as well. I am not familiar with Grails, so we will discuss only spring based applications, specifically web applications.
Spring Security is currently in version 3. Several examples online use version 2, but may not mention it. There are several differences between version 2 and 3. As an example, the <authentication-provider> element in the security.xml file is within the <beans> element in version 2, but in version 3, it is inside another element called <authentication-manager>. Also, the registerPermissionsFor method that is used when extending the BasePermission class in version 2 is deprecated in version 3. So just be aware that you may need to modify your code if you test code from version 2 but intend to use it with version 3.
To add spring security to your application, you need to download spring security jars and add them to your application. The following jars are available:
| Jar | Use |
| spring-security-core | Must be included in |
| spring-security-remoting | If using Servlet API |
| spring-security-web | Use in web applications |
| spring-security-ldap | If using LDAP |
| spring-security-config | If using namespace configuration (web, ldap, openid, protect-pointcut) |
| spring-security-acl | If using ACL security |
| spring-security-cas | If integrating with JA-SIG CAS |
| spring-security-openid | If integrating with OpenID |
| spring-security-taglibs | For using Spring Security JSP tag implementations |
Add the following lines to your web.xml under the <web-app> element:
<!-- - Location of the XML file that defines the root application context - Applied by ContextLoaderListener. --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF\applicationContext-security.xml</param-value> </context-param> <!-- - Loads the root application context of this web app at startup. - The application context is then available via - WebApplicationContextUtils.getWebApplicationContext(servletContext). --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Spring security uses filters to enforce security. The springSecurityFilterChain tells the application context to load the security specific configuration in applicationContext-security.xml. --> <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>
The above will add spring security capabilities to your app. Our application is still not secure yet, because we haven’t decided what it is we want to secure in our application. Spring Security provides three broad categories of handling security:
- URL-based Security
- Method Security
- ACL Domain Object Security
The three categories are discussed in detail below.
URL based Security
Perhaps the simplest type of security for beginners to think of is url based security. Spring Security allows the securing individual pages using role-based authentication. You can define the roles and the pages to secure, the url to go to when login is successful, or fails, or link your application to an authentication source. Spring Security provides configuration for LDAP, OpenID, CAS and JAAS based authentications.
In order to add url based security, we create a new xml called WEB-INF/applicationContext-security.xml which is mentioned in the web.xml under context-param.
This file looks like this:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <http pattern="/login.jsp" security="none"/> <http auto-config="true"> <intercept-url pattern="/viewAccount.htm" access="ROLE_ACCHOLDER,ROLE_MANAGER,ROLE_SUPERVISOR,ROLE_TELLER" /> <intercept-url pattern="/viewAccountAdmin.htm" access="ROLE_MANAGER,ROLE_SUPERVISOR,ROLE_TELLER" /> <intercept-url pattern="/viewEmployees.htm" access="ROLE_MANAGER" /> <form-login login-page="/login.jsp" default-target-url="/viewAccount.htm" always-use-default-target="false" authentication-failure-url="/login.htm?authfailed=true" /> <logout invalidate-session="true" logout-url="/logout.jsp" logout-success-url="/login.htm?loggedout=true" /> </http> <authentication-manager> <authentication-provider> <!-- <password-encoder hash="plaintext" /> --> <user-service> <user name="user1" password="test" authorities="ROLE_MANAGER" /> <user name="user2" password="test" authorities="ROLE_SUPERVISOR" /> <user name="user3" password="test" authorities="ROLE_TELLER" /> </user-service> </authentication-provider> </authentication-manager> </beans:beans>
In the above example, the login.jsp has no security applied to it, because we want unauthenticated users to be able to get to the login page. The http element is where you list the pages that you want to secure and which roles have access. The auto-config attribute automatically configures form login, logout and basic authentication features. The intercept-url uses the pattern property to match the pages against the url request. Since the url’s are matched in order, the more specific patterns should be listed first.
In our example above, we have listed the usernames and passwords in plain text. In a production application, the authentication provider would refer to another bean that provided the user authentication details via a datasource.
The following entries are used to configure a hierarchical based role system:
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter"> <constructor-arg ref="roleHierarchy" /></class> <bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> <property name="hierarchy"> ROLE_ADMIN > ROLE_STAFF ROLE_STAFF > ROLE_USER ROLE_USER > ROLE_GUEST </property> </bean>
Method Security
In Spring Security, it is possible to secure individual methods for authentication. In order to use method security, the following lines need to be added to the files specified:
In security.xml
<global-method-security pre-post-annotations="enabled"/>
The above line activates method security using pre and post annotations throughout your application. The next step to securing your methods is to add annotations like the one below for each of your methods:
@PreAuthorize("hasRole('ROLE_USER')")
The above annotation is added before the method declaration in the interface for the method that you want to secure. The role names should start with “ROLE_” prefix in order for spring security to detect them as being roles. It is possible to configure this prefix if needed.
The other available method security annotations are:
@PreAuthorize("hasPermission(#item, 'admin')")
public void addPermission(Item item,…);
In the above example, the #item refers to the argument “item”, and evaluates whether the user has the permission ‘admin’ to this particular item.
Spring Security has the following five built in permissions:
- Read
- Write
- Create
- Delete
- Admin
It is possible to create custom permissions, which may be numbered up to 32.
The hasPermission evaluator can be configured to use a custom permission evaluator by adding an expression handler to the global-method-security tag. The expression handler has a property named “permissionEvaluator” which refers to our custom permission evaluator. The custom permission evaluator has access to the authentication object, through which we can get the user details of the current user, as well as the target Domain Object. The target domain object is the one for which the permission is being evaluated. This gives us a high degree of flexibility to evaluate the permission using the properties of either of those objects.
There are other annotation options as listed below:
@PreAuthorize("#item.property == authentication.property")
public void myMethod(Item item);
//“authentication” is a built in expression for Authentication in the security context. @PreAuthorize("hasRole('ROLE_USER')") @PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')") public List<Contact> getAll();
PostFilters filters the returned collection and filters the list by removing each ‘filterObject” to which the current user does not have ‘read’ permissions.
Some other security expressions are:
- authentication
- principal
- denyAll
- permitAll
- hasAnyRoles(role1,role2,role3)
- hasRole(role)
- hasIpAddress(ip)
- isAnonymous()
- isAuthenticated()
- isFullyAuthenticated()
- isRememberMe()
Another way to protect the methods is to use protect-pointcut.
<global-method-security> <protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="ROLE_USER" /> </global-method-security>
This protects all the methods that match the expression provided and only users with the role specified in the access property are allowed access to them.
According to the reference documentation, The SecurityContextHolder is the most fundamental object in Spring Security. It holds the user details in the Authentication object. A user is considered authenticated when the SecurityContextHolder contains a fully populated Authentication object.
The following code from the reference docs shows how to query the Authentication object:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails) principal).getUsername();
} else {
String username = principal.toString();
}
The principal object returned above can be cast into a UserDetails object. This UserDetails object can then be cast into the application specific user object. The following method in the UserDetailsService accepts a string based username argument and returns a UserDetails object:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
JdbcDaoImpl and InMemoryDaoImpl are some of the implentations of the UserDetailsService interface provided by Spring Security. UserDetailsService is just that – it provides user details but does not authenticate a user.
The AuthenticationManager inserts GrantedAuthority objects into the Authentication object to be read by the AccessDecisionManager. The AccessDecisionManager makes the final access control decision based on the AccessDecisionVoter responses. Each AccessDecisionManager polls one or more AccessDecisionVoters specified in the security.xml config file. It passes all the arguments to all the voters. For each argument, each voter has only three options (int) to return – ACCESS_GRANTED, ACCESS_DENIED or ACCESS_ABSTAIN. A voter returns ACCESS_ABSTAIN when the config attriute it is asked to vote on does not apply. For example, if a RoleVoter is asked to vote on anything other than the role of a user, it will return ACCESS_ABSTAIN.
The following AccessDecisionManagers are available:
- AffirmativeBased (Any yes)
- ConsensusBased (Simple Majority yes, property to decide what to do if all decisions abstain)
- UnanimousBased (All yes or abstain).
The following voters are provided by Spring Security:
- RoleVoter (Votes only whether the user has a specified Role.)
- AuthenticatedVoter (Checks whether the user is anonymous, fully-authenticated or remember-me authenticated)
It is also possible to create custom voters.
ACL Security
Spring Security also provides domain object level security. In simple terms, ACL provides a way to specify permissions based on a combination of role, business object (referred to as domain object) and permissions. For example, if you want to grant a user read permission based on their role, on their own user data, you would use ACL security.
In order to secure the domain objects, the following tables need to be created:
ACL_SID: Uniquely identifies a principal or authority (user or role or group)
ACL_ENTRY: Individual permissions assigned to each sid for each object_identity
ACL_CLASS: Uniquely identifies a domain object class. The field class contains fully qualified name of the java class.
ACL_OBJECT_IDENTITY: Stores information for each unique domain object instance.
| ACL_SID |
| id |
| principal |
| sid |
| ACL_ENTRY |
| id |
| acl_object_identity |
| ace_order |
| sid |
| mask |
| granting |
| audit_success |
| audit_failure |
| ACL_ENTRY |
| id |
| class |
| ACL_OBJECT_IDENTITY |
| id |
| object_id_class |
| object_id_identity |
| parent_object |
| owner_sid |
| entries_inheriting |
A datasource is created and injected into a JdbcMutableAclService and BasicLookupStrategy instance. BasicLookupStrategy does the lookup of the acl. The domain objects that we want to secure should have a public Serializable getId() method that returns a type long or compatible with long. Next step is to create a AccessDecisionVoter or AfterInvocationProvider that would use AclService’s isGranted() method to retrieve the ACL and check whether the permission is granted or not.
Appendix A: Code to create a custom permission evaluator:
security.xml
<security:global-method-security pre-post-annotations="enabled"> <security:expression-handler ref="expressionHandler" /> </security:global-method-security> <bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"> <property name="permissionEvaluator"> <bean id="permissionEvaluator" class="org.krams.tutorial.infrastructure.SomePermissionsEvaluator" /> </property> </bean>
Service Interface
@PostFilter("hasPermission(filterObject, 'READ')")
public List<Post> getAll();
Custom Permissions Evaluator
@Override
public boolean hasPermission(Authentication authorities,
Object targetDomainObject, Object permission) {
boolean Decision = false;
System.out.println("Initial Decision: " + Decision);
Date cutoffDate = null;
try {
cutoffDate = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH)
.parse("January 1, 2012");
System.out.println("Cutoff Date: " + cutoffDate.toString());
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("Domain Object Date: "
+ Post.class.cast(targetDomainObject).getDate());
if (Post.class.cast(targetDomainObject).getDate().before(cutoffDate)) {
Decision = false;
System.out.println("In before");
} else {
Decision = true;
System.out.println("In after");
}
System.out.println("Final Decision: " + Decision);
System.out.println("--------");
return Decision;
}
Appendix B: SQL For creating ACL Tables
create database acl; use acl; CREATE TABLE IF NOT EXISTS `acl_sid` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `principal` tinyint(1) NOT NULL, `sid` varchar(100) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unique_uk_1` (`sid`,`principal`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ; CREATE TABLE IF NOT EXISTS `acl_class` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `class` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unique_uk_2` (`class`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ; CREATE TABLE IF NOT EXISTS `acl_entry` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `acl_object_identity` bigint(20) NOT NULL, `ace_order` int(11) NOT NULL, `sid` bigint(20) NOT NULL, `mask` int(11) NOT NULL, `granting` tinyint(1) NOT NULL, `audit_success` tinyint(1) NOT NULL, `audit_failure` tinyint(1) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unique_uk_4` (`acl_object_identity`,`ace_order`), KEY `foreign_fk_5` (`sid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=43 ; CREATE TABLE IF NOT EXISTS `acl_object_identity` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `object_id_class` bigint(20) NOT NULL, `object_id_identity` bigint(20) NOT NULL, `parent_object` bigint(20) DEFAULT NULL, `owner_sid` bigint(20) DEFAULT NULL, `entries_inheriting` tinyint(1) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unique_uk_3` (`object_id_class`,`object_id_identity`), KEY `foreign_fk_1` (`parent_object`), KEY `foreign_fk_3` (`owner_sid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=10 ; ALTER TABLE `acl_entry` ADD CONSTRAINT `foreign_fk_4` FOREIGN KEY (`acl_object_identity`) REFERENCES `acl_object_identity` (`id`), ADD CONSTRAINT `foreign_fk_5` FOREIGN KEY (`sid`) REFERENCES `acl_sid` (`id`); ALTER TABLE `acl_object_identity` ADD CONSTRAINT `foreign_fk_1` FOREIGN KEY (`parent_object`) REFERENCES `acl_object_identity` (`id`), ADD CONSTRAINT `foreign_fk_2` FOREIGN KEY (`object_id_class`) REFERENCES `acl_class` (`id`), ADD CONSTRAINT `foreign_fk_3` FOREIGN KEY (`owner_sid`) REFERENCES `acl_sid` (`id`);
Appendix C: How to create custom permission
Security.xml:
<bean id="permissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator"> <constructor-arg ref="aclService" /> <property name="permissionFactory" ref="permissionFactory" /> </bean> <bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy"> ... <property name="permissionFactory" ref="permissionFactory" /> </bean> <bean id="permissionFactory" class=" com.company.security.myPermissionFactory" />
Custom Permission class:
public class myPermission extends BasePermission {
public static final Permission CUSTOMX = new myPermission (1 << 5,
'X');
public static final Permission CUSTOMY = new myPermission (1 << 6, 'Y');
protected myPermission (int mask) {
super(mask);
}
protected myPermission (int mask, char code) {
super(mask, code);
}
}
Custom Factory Class to register the new permissions:
public class myPermissionFactory extends DefaultPermissionFactory {
public myPermissionFactory() {
super();
registerPublicPermissions(myPermission.class);
}
}
The method annotation:
@PreAuthorize("hasPermission(#user,'customx')")
public void myMethod(User user) {
...
}
Appendix D: Paging Implementation
Original interface:
public interface theOriginalServiceInterface {
public void setDataSource(DataSource dataSource);
/**
* Retrieves the next two items.
*/
@PostFilter("hasPermission(filterObject, 'READ')")
public List<Post> getNextTwo(int startRow);
}
Original implementation class:
@Service("origService")
@Transactional
public class theOriginalService implements theOriginalServiceInterface {
public List<Post> getNextTwo(int startRow) {
try {
logger.debug("Retrieving two admin posts");
if ( jdbcTemplate == null )
{
System.out.println("No JDBC Template!");
}
// Prepare SQL statement
String sql = "select id, date, message from db_table Limit "
+ startRow + ",2";
// Map SQL result to a Java object
RowMapper<Post> mapper = new RowMapper<Post>() {
public Post mapRow(ResultSet rs, int rowNum)
throws SQLException {
Post post = new AdminPost();
post.setId(rs.getLong("id"));
post.setDate(rs.getDate("date"));
post.setMessage(rs.getString("message"));
return post;
}
};
// Run query then return result
return jdbcTemplate.query(sql, mapper);
}
catch (NullPointerException ne)
{
System.out.println("NPE");
ne.printStackTrace();
throw ne;
}
catch (Exception e) {
e.printStackTrace();
logger.error(e);
throw new RuntimeException(e);
}
}
}
My extended interface:
public interface myServiceInterface extends theOriginalServiceInterface {
public List<Post> getNextTwoSecured(int startRow);
}
My extended implementation class:
@Service("myService")
@Transactional
public class myService implements myServiceInterface {
@Resource(name="origService")
theOriginalServiceInterface myServiceObj;
public List<Post> getNextTwoSecured(int startRow) {
List<Post> myPosts = myServiceObj.getNextTwo(startRow);
while (myPosts.size() < 2) {
myPosts.addAll(myServiceObj.getNextTwo(startRow + 2));
}
return myPosts.subList(0, 2);
}
}
Leave a comment