Jaas in Action - Chapter06 02
Jaas in Action - Chapter06 02
Jaas in Action - Chapter06 02
com
6 A Custom Policy
This chapter describes an example of implementing a custom java.security.Policy . The
Policy we’ll develop is actually composed of several parts: a CompositePolicy that
delegates to any number of “sub-Policy” implementations; a database-backed Policy that
retrieves permission grants from a database; and the default file-based Policy. Aggregating a
Policy like this allows for more flexibility and code reuse. As with previous chapters, we’ll
use a small class with a main() method to demonstrate using the custom Policy .
Our custom Policy takes one large short cut to simplify the example: Permission
checks are effectively only applied when a Subject is logged in, allowing most code to
execute with all permissions enabled. With that disclaimer aside, let’s jump into the code.
package chp06;
import java.io.FilePermission;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import javax.security.auth.Subject;
import util.id.Id;
try {
Policy defaultPolicy = Policy.getPolicy();
DbPolicy dbPolicy = new DbPolicy();
List policies = new ArrayList(2);
policies.add(defaultPolicy);
policies.add(dbPolicy);
CompositePolicy p = new CompositePolicy(policies);
Policy.setPolicy(p);
System.setSecurityManager(new SecurityManager());
authHelper.createTestUser("testuser", "testpassword");
authHelper.loginTestUser();
Subject subject = authHelper.getSubject();
}, null);
} catch (SecurityException e) {
allowed = false;
}
if (allowed) {
System.out.println("Subject can read file /tmp/test");
} else {
System.out.println("Subject cannot read file /tmp/test");
}
Id principalId = authHelper.getUserGrp().getId();
PermissionService.addPermission(principalId, Id.create(),
filePerm);
System.out.println("Added " + filePerm + " to Subject.");
allowed = true;
try {
Subject.doAsPrivileged(subject, new PrivilegedAction() {
}, null);
} catch (SecurityException e) {
allowed = false;
}
if (allowed) {
System.out.println("Subject can read file /tmp/test");
} else {
System.out.println("Subject cannot read file /tmp/test");
}
} finally {
if (authHelper != null) {
authHelper.cleanUp();
}
}
}
To run the above, change directories to the root of this book’s project and execute the
command ant run-chp06 . The output will include:
1
Implemented by the class sun.security.providers.PolicyFile, and, by default,
backed by the file <JAVA_HOME/>lib/security/java.policy.
which is returned. The implies() method returns if at least one of the aggregated Policys
returns true from their implies() method; that is, for a Permission to be granted to a
ProtectionDomain , at least one of it’s aggregate Policys must return true from it’s
implies() method.
The code for CompositePolicy is below:
package chp06;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
}
}
return false;
}
6.3 DbPolicy
The chp06.DbPolicy relies on the UserGroupPrincipal class introduced in chapter 4,
although it supports a wide variety of Permission implementations, including it’s own
DbPermission. DbPolicy implements the three Policy methods,
getPermissions(CodeSource) , getPermissions(ProtectionDomain) , and
implies(ProtectionDomain, Permission). As mentioned in the overview, DbPolicy
takes a shortcut by only providing authorization services for Subjects: if the security
context does not include a Subject , the DbPolicy grants all permissions. Though this helps
illustrate the inter-workings of JAAS, it can be extremely dangerous depending on your
security requirements.
First, we’ll look at the code. The rest of this section will then go over different methods
in DbPolicy .
package chp06;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import chp04.UserGroupPrincipal;
// Look up permissions
final Set principalIds = new HashSet();
Principal[] principals = domain.getPrincipals();
if (principals != null && principals.length > 0) {
for (int i = 0; i < principals.length; i++) {
Principal p = principals[i];
if (p instanceof UserGroupPrincipal) {
UserGroupPrincipal userGroup = (UserGroupPrincipal) p;
principalIds.add(userGroup.getId());
}
}
if (!principalIds.isEmpty()) {
try {
List perms = (List) AccessController
.doPrivileged(new PrivilegedExceptionAction() {
}
});
for (Iterator itr = perms.iterator(); itr.hasNext();) {
Permission perm = (Permission) itr.next();
permissions.add(perm);
}
} catch (PrivilegedActionException e) {
// Log
}
}
} else if (domain.getCodeSource() != null) {
return permissions;
}
return implies;
}
6.3.1 getPermissions(ProtectionDomain)
Most of the behavior of DbPolicy is done by getPermssions(ProtectionDomain) . The
other getPermissions(CodeSource) method, as noted above, grants all permissions to
any CodeSource , while the implies method simply uses the PermissionCollection
returned by getPermissions(ProtectionDomain) to perform authorization.
getPermissions(ProtectionDomain) first checks if a Subject is logged in, by
getting the Principals associated with the passed in ProtectionDomain . If there are no
2
Principals, we assume that there is no Subject logged in . In such cases,
getPermissions(ProtectionDomain) delegates to getPermissions(CodeSource) ,
which grants all permissions.
If a Subject with Principals is logged in, getPermissions(ProtectionDomain)
first gathers all of the Subject ’s DbUserGroupPrincipal Id ’s, and then uses
PermissionService to retrieve the associated Permissions. The union of all the
Permissions is returned in a java.security.Permissions instance, which allows us to
collect different types of Permissions together.
Privileged Code
Also, notice that the call to PermissionService.findPermissions() is done in a
privileged code block. The database code used by PermissionService.findPermission()
requires that certain java.io.SocketPermissions be granted to the current security
context. The only Permission we’ve granted to the Subject , however, is the ability to
read the temporary file /tmp/test . The Subject does not have the required
SocketPermissions to connect to the database.
To get around this, enabling the DbPolicy to access the database regardless of which
Subject is logged in two conditions must be satisfied:
Because of the shortcut we’ve taken, any code that executes without a Subject is granted
java.security.AllPermission , granting all Permissions, including the
SocketPermissions we need. So, by creating a Privileged code block around the call the
PermissionService.findPermission() , we exclude considering the Subject in the
authorization checks, thus, allowing the needed SocketPermission . See section 5.5.2 in
chapter 5 for more discussion on using privileged blocks.
2
This does leave a loophole in which a Subject with no Principals is granted all
Permissions.
of the SDK’s default Policy allows our application to support Principals other than
DbUserGroupPrincipal , albeit through the standard flat-file.
Database Schema
Each of PermissionService ’s static methods relies, of course, on the permission schema
in the database. The schema includes the tables from chapter XXX [authentication chapter],
and the tables below:
The permission table is responsible for storing each Permission assigned to a Principal .
Each entry must have an Id and a fully qualified class name. The name and actions columns
are optional. The principal_permission table is a tie table used to associate
Permissions to Principals.
PermissionService Implementation
The code for PermissionService is listed below. Most of the code is simple JDBC code
that does the grunt work of creating, reading, and updating the Permissions stored in the
database. The primary point of interest are the attempts to reflectively create the loaded
Permissions, marked by code annotations.
package chp06;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.Permission;
import java.security.Principal;
import java.security.UnresolvedPermission;
import java.security.cert.Certificate;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import util.db.DbService;
import util.id.Id;
tiePstmt.setString(1, id.getId());
permPstmt.setString(1, id.getId());
tiePstmt.executeUpdate();
permPstmt.executeUpdate();
}
} finally {
if (conn != null) {
conn.close();
}
}
if (perm != null) {
perms.add(perm);
} else {
continue;
}
}
} finally {
if (conn != null) {
conn.close();
}
}
return perms;
}
if (clazz == null) {
perm = new UnresolvedPermission(clazzStr, name, actions,
EMPTY_CERTS);
} else if (clazz.equals(DbPermission.class)) {
perm = new DbPermission(id, name, actions);
} else if (Permission.class.isAssignableFrom(clazz)) {
try {
if (name == null && actions == null) {
Constructor con = clazz.getConstructor(ZERO_ARGS); #1
perm = (Permission) con.newInstance(ZERO_OBJS);
} else if (actions == null) {
Constructor con = clazz.getConstructor(ONE_STRING_ARG); #1
perm = (Permission) con
.newInstance(new String[] { name });
}
// BasicPermission types
else if (name != null && actions != null) {
Constructor con = clazz.getConstructor(TWO_STRING_ARGS); #1
perm = (Permission) con.newInstance(new String[] { name,
actions });
}
} catch (Exception e) {
// Log
}
}
return perm;
(annotation) <#1 the findPermissions() method uses reflection extensively to create the Permission
instances retrieved from the database. Because Permission instances are not required to have a default, no argument
constructors, each instance must be reflectively created by attempting to acquire the
java.lang.reflect.Constructor needed, as dictated by the presence of absence of the name and
actions attributes. If an appropriate Constructor cannot be found, or and er ro r occurs using it, the Permission
is skipped, avoiding the “throwing out the baby with the bathwater” effect where one bad apple ruins the whole bar rel.
For a much more detailed discussion of reflection, see the Forman’s Java Reflection in Action.>
Summary
This chapter demonstrated integrating JAAS authorization functionality with a database. To
accomplish this goal, first we created a CompositePolicy class that allowed us to use
multiple Policys at the same time. Next, we created a custom Policy implementation that
was backed by a database rather than a flat file. Using a database instead of the flat files allows
your application to more easily specify Permissions at runtime and provides an easier way
to maintain all of the Permission grants in your system than flat files.