Fast forward to the last day of FY2010, and here I am writing a post about how to integrate Oracle Entitlements Server with Spring using AOP. What is interesting is that just because the technology has improved (both on the Java side and on the Identity Management side) and the solution is possible, there are still some fundamental questions about the viability of such a model for enterprises. In the enterprise model, security is a coordinated effort between application developers and security administrators to make sure that applications are secure. The security model needs to be sufficiently flexible to meet the security requirements of the business and enable meaningful policy changes made with out having to engage expensive development resources to do so. Its with these goals in mind that I set off to build a "real" solution.
Annotations - A Brave New World, at least for me
I knew that I wanted to use aspects to automatically make the calls to OES. This is more of an AOP thing than per se a Spring thing, but I wanted a solution that was fairly generic and didn't rely on other frameworks. I also in a way wanted to see if there was a way to just magically get authorization called - security along with logging and exception handling has always been held out there as a good use case for AOP. The first challenge was to figure out how to get my code - Aspect - invoked. Obviously, getting called before every Java method made no sense. The good news is that you can define Pointcuts as part of spring-configuration. The bad news is that the syntax for this stuff is pretty messy. Example:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
I think this means whenever you're executing a method of any protection level in the package com.xyx.myapp.dao. This is pretty tricky stuff and if you wanted to secure a different method, you need to go change this. Also, the person doing this is obviously the developer, not a security person since all of the definitions are using Java packages. So, people would probably do when confronted with something really hard...either not use it or set the pointcut like the example above...very broad...all methods result in a call to the authorization aspect. So, what is the simpler way for a developer to indicate that a method should be secured? Annotations to the rescue. In SpringAOP, you can also define point cuts that look for annotations on methods or classes to indicate a pointcut. Here's the poincut I'm using for this example:
@Around("@annotation(com.oracle.oes.springaop.annotation.Protected) ||
(@target(com.oracle.oes.springaop.annotation.Protected)&&
!@annotation(com.oracle.oes.springaop.annotation.UnProtected) ) ")
This means call my aspect, if the method has an @Protected annotation or the class does and the method doesn't have an @UnProtected annotation. Basically, if you mark a class as @Protected all of the methods are protected by default. You can override that with an @UnProtected. You can also have a class which is unprotected, but mark individual methods as @Protected. My feeling is with this model, you'll probably never have to go change the pointcut. The annotations do all of the work.
In addition to @Protected and @UnProtected, I added three more tags:
- @Resource - this is used at the class level to indicate where in the OES resource hierarchy this class sits or at the method level to indicate that when making an authorization call on a method of this object that this method is used to represent the OES resource. In a class has both, then the resource will be the concatenation of the class and method level @Resource values. If no @Resource annotations are found then the name of the class is used.
- @Privilege - this is used at the class level to indicate the default privilege in OES for all of the @Protected methods on the class. It can also be used to override the default with a specific value for the method. If no @Privilege annotation is found, then the method signature is used (yuck)
- @AppContext - this is used at the method or parameter level to indicate that this value should be passed to OES as an attribute it the AppContext. All methods in a class that have the @AppContext annotation will be called and have their values passed to OES. All of the parameters in a method call will be passed, even if they don't have an @AppContext - they get names param1, param2,..,paramN. @AppContext also has some optional parameters - passObject and isProtected. The passObject parameter is set to false by default which means that the authorization aspect will convert the object to a set of primitives. If this is set to true, then the object itself is passed. This can have advantages when working with the Java SM and custom extensions - attribute retrievers and eval functions. The other parameter, isProtected determines if when calling the method to retrieve the appContext and the method has an @Protected annotation should the authorization aspect make a call to OES. This attribute defaults to false, but there are cases where the authorization check should still occur
Here are some sample classes that illustrate more of the annotations that I'm using for this solution:
AccountImpl.java
package test;
import com.oracle.oes.springaop.annotation.AppContext;
import com.oracle.oes.springaop.annotation.Privilege;
import com.oracle.oes.springaop.annotation.Protected;
import com.oracle.oes.springaop.annotation.Resource;
import com.oracle.oes.springaop.annotation.UnProtected;
@Protected()
@Privilege("read")
@Resource("/MyOrg/Account")
public class AccountImpl implements IAccount {
private String accountId;
private double balance;
private String accountType;
private IPerson accountOwner;
public AccountImpl() {
super();
// TODO Auto-generated constructor stub
}
public AccountImpl(String accountId, double balance, String accountType,
IPerson owner) {
super();
this.accountId = accountId;
this.balance = balance;
this.accountType = accountType;
this.accountOwner = owner;
}
@Privilege("readBalance")
@AppContext(value="balance")
public double getBalance() {
return this.balance;
}
@UnProtected
public String toString() {
return super.toString();
}
@UnProtected
@Resource
public String getAccountId() {
return this.accountId;
}
@AppContext("owner")
public IPerson getAccountOwner() {
// TODO Auto-generated method stub
return this.accountOwner;
}
@UnProtected
public String getAccountType() {
// TODO Auto-generated method stub
return this.accountType;
}
@UnProtected
public void setAccountOwner(IPerson owner) {
this.accountOwner = owner;
}
@UnProtected
public void setAccountId(String accountId) {
this.accountId = accountId;
}
@UnProtected
public void setBalance(double balance) {
this.balance = balance;
}
@UnProtected
public void setAccountType(String accountType) {
this.accountType = accountType;
}
}
PersonImpl.java
package test;
import java.util.List;
import com.oracle.oes.springaop.annotation.AppContext;
import com.oracle.oes.springaop.annotation.Privilege;
import com.oracle.oes.springaop.annotation.Protected;
import com.oracle.oes.springaop.annotation.Resource;
public class PersonImpl implements IPerson {
private String name;
private String SSN;
private List<IAccount> accounts;
@Protected()
@Privilege("readConfidential")
@AppContext(value="accounts")
public List<IAccount> getAccounts() {
return accounts;
}
public void setAccounts(List<IAccount> accounts) {
this.accounts = accounts;
}
public PersonImpl() {
super();
// TODO Auto-generated constructor stub
}
public PersonImpl(String name, String sSN) {
super();
this.name = name;
SSN = sSN;
}
@AppContext("name")
@Privilege("read")
public String getName() {
return name;
}
@Protected
@Privilege("write")
public void setName(String name) {
this.name = name;
}
@AppContext(value="ssn")
@Protected()
@Privilege("readConfidential")
public String getSSN() {
return SSN;
}
@Protected
@Privilege("writeConfidential")
public void setSSN(String sSN) {
SSN = sSN;
}
@Protected
@Privilege("txfer")
public void transfer(
@AppContext(value="from") IAccount acct1,
@AppContext(value="to") IAccount acct2,
@AppContext(value="amount", passObject=true) double amount) {
}
}
Some Design Considerations with Annotations
I made a conscious decision to limit the Annotations to the basic OES runtime API, which is very aligned with the XACML concept of Subjects, Actions, Resources and Environment with attributes. I did not expose the ability for developers to author policy or roles (@AllowableRoles) in the annotations. Other models support this, but you're creating a brittleness here, and if things change then developers will have to go back in and change the roles/policy annotations. OES has facilities for authoring these policies so all I'm trying to do here is get the integration between the OES runtime API and Spring simple, and then make the authoring of those policies sensible.
Before we move on its worth noting that I didn't do anything explicitly here about identifying the subject. This is basically a solved problem, where I'm assuming that the user is already authenticated and there is a simple way inside of the aspect to know who the user is. OES has the ability through the identity asserter to take a token (username or JAASSubject) and use that as the subject in the authorization decision.
Now, back to the thinking around these particular annotations. One of the other really hard problems in writing custom PEPs is getting the admin artifacts aligned with the runtime calls. OES has the ability to address this through discovery mode - which basically just captures the calls, and the resources, attributes and privileges in a file format that can be imported into OES admin console. This works fine, but with annotations we can actually do much better. For people who are steady readers of this blog, they'll remember that I used the Java Annotations Compiler capabilities in JDK 1.6 in the OWSMAC (OWSM assertions compiler project) to simplify the creation of the XML deployment descriptors for OWSM custom assertions. As architects, we have a tendency to fall in love with solutions, but I do think in this regard, the use of the annotations is the best choice. I created a simple annotation processor, and now when you compile the classes above, you get this output:
Buildfile: C:\Documents and Settings\jbregman\workspace\oes_spring_aop_test\build.xml
clean:
[delete] Deleting directory C:\Documents and Settings\jbregman\workspace\oes_spring_aop_test\bin
init:
[mkdir] Created dir: C:\Documents and Settings\jbregman\workspace\oes_spring_aop_test\bin
[copy] Copying 1 file to C:\Documents and Settings\jbregman\workspace\oes_spring_aop_test\bin
build-project:
[echo] oes_spring_aop_test: C:\Documents and Settings\jbregman\workspace\oes_spring_aop_test\build.xml
[javac] Compiling 5 source files to C:\Documents and Settings\jbregman\workspace\oes_spring_aop_test\bin
[javac] Processing Annotations: [com.oracle.oes.springaop.annotation.Protected, com.oracle.oes.springaop.annotation.AppContext, com.oracle.oes.springaop.annotation.UnProtected, com.oracle.oes.springaop.annotation.Privilege, com.oracle.oes.springaop.annotation.Resource]
[javac] Protected Classes to Process: [test.PersonImpl, test.AccountImpl]
[javac] PRIV FILE=file:/C:/Documents%20and%20Settings/jbregman/workspace/oes_spring_aop_test/bin/oes/priv
[javac] OBJECT FILE=file:/C:/Documents%20and%20Settings/jbregman/workspace/oes_spring_aop_test/bin/oes/object
[javac] ATTR FILE=file:/C:/Documents%20and%20Settings/jbregman/workspace/oes_spring_aop_test/bin/oes/attr
That's right - based on the annotations in the code, it creates OES import files. You can pull these directly into the OES Admin console. This saves a lot of time and effort in getting the coordination between runtime and admin, since they are using the same tooling and model. The most interesting of the files is the attr - this is list of dynamic attributes that are available at runtime.
to
from.balance
from.resource
accounts.count
param1
resource
ssn
owner.ssn
from
owner.accounts.count
to.resource
to.owner
amount
balance
owner.from
to.balance
from.owner
owner.to
name
owner
owner.name
These are some really nice attributes to write policies on...there is one param1 that I left there from testing, but basically you have a seamless binding between the code and the policy artifacts, for little more than adding some basic annotations.
Pulling it all together - the runtime
Now that we've build this model, let's see how it works at runtime. I have a very basic spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean id="josh" class="test.PersonImpl">
<property name="name" value="Josh"/>
<property name="SSN" value="123-45-6789"/>
<property name="accounts">
<list>
<ref bean="acct2"/>
</list>
</property>
</bean>
<bean id="chris" class="test.PersonImpl">
<constructor-arg type="java.lang.String" value="Chris"/>
<constructor-arg type="java.lang.String" value="987-65-4321"/>
</bean>
<bean id="acct1" class="test.AccountImpl">
<property name="accountId" value="11111111"/>
<property name="balance" value="6000"/>
<property name="accountType" value="CHK"/>
<property name="accountOwner" ref="chris"/>
</bean>
<bean id="acct2" class="test.AccountImpl">
<property name="accountId" value="22222222"/>
<property name="balance" value="7000"/>
<property name="accountType" value="SAV"/>
<property name="accountOwner" ref="josh"/>
</bean>
<context:load-time-weaver/>
</beans>
Notice I'm using the Load Time Weaver (LTW) to pull in the aspects. I did this in the META-INF/aop.xml file
<!DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-showWeaveInfo">
<!-- only weave classes in our application-specific packages -->
<include within="test.*" />
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="com.oracle.oes.springaop.aspect.AuthorizationAspect"/>
</aspects>
</aspectj>
The aop.xml and the aspects are all of the "overhead" that is placed on the developer. Here's the test program:
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.*;
public class Test {
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext(args[0],Test.class);
IPerson josh = (IPerson)ctx.getBean("josh");
IPerson chris = (IPerson)ctx.getBean("chris");
IAccount acct1 = (IAccount)ctx.getBean("acct1");
IAccount acct2 = (IAccount)ctx.getBean("acct2");
System.out.println("Chris SSN="+chris.getSSN());
josh.transfer(acct1, acct2, 63.45);
}
}
And this is the output:
>method-execution PersonImpl.setName(..)
>IsAccessAllowed action=write resource=test/PersonImpl appContext={
param1=Josh
}
>method-execution PersonImpl.setSSN(..)
>IsAccessAllowed action=writeConfidential resource=test/PersonImpl appContext={
{
param1=123-45-6789,
name=Josh
}
>method-execution PersonImpl.getSSN()
>IsAccessAllowed action=readConfidential resource=test/PersonImpl appContext={
name=Chris,
ssn=987-65-4321
}
Chris SSN=987-65-4321
>method-execution PersonImpl.transfer(..)
>IsAccessAllowed action=txfer resource=test/PersonImpl appContext=
{
to.owner.ssn=123-45-6789,
from.balance=6000.0,
from.resource=11111111,
accounts.count=1,
ssn=123-45-6789,
from.owner.name=Chris,
accounts.1.resource=22222222,
to.owner.accounts.count=1,
to.resource=22222222,
amount=63.45,
to.balance=7000.0,
to.owner.name=Josh,
name=Josh,
accounts.1.balance=7000.0,
from.owner.ssn=987-65-4321}
There are 4 authorization calls made in the sample. The first two are made when setting the SSN and Name of the josh bean. There are no corresponding calls on the chris bean because in the spring-config.xml the chris bean is created using the constructor, while josh is done using the setters. I made constructors unprotected by rule, because it seemed like too many issues with having constructors protected. The next authorization call is made when getting chris SSN. The final authorization is the really interesting one where you see all of the context of the transfer being passed. You see that the List of IAccount are passed as items. I could have set the @AppContext to passObject, and then it would have simply passed the list. Either way, there is all of the information needed to make a meaningful authorization decision in OES.
Summary
Java/Spring/AOP have all come a long way since that meeting seven years ago. This sample demonstrates some of the "magic", the ability to seamlessly apply authorization policies with very little coding. Furthermore, it does it in such a way where I think there is a good balance between what the developer has to do to get this model enabled, and what security policy administrators/authors can then do with the artifacts the model generates. Finally, what really makes this solution possible is the performance and scalability of the OES engine. The fact that, unlike SiteMinder or centralized models, the Java SM can run in process, and make the decisions very very fast. With the passObject=true option, all of the information can be available in its native form to OES. This is another way that expensive serializing and de-serializing can be avoided. This authorization aspect discussed is functionally pretty basic - it does inbound checks and then calls proceed. You could image doing some interesting this with responses from OES - like adding additional where clauses to queries or data masking. Now that would be something.