- Differences Between JAAS and Service Model
- Migration from JAAS to Service Model
- Overcoming Technical Challenges
- Other Considerations
Integration of JAAS into the Java EE platform, although quickly announced after its introduction, never really took place, as was illustrated in an earlier article. Despite these difficulties, JAAS enjoyed wide adoption by the industry and many enterprise systems today rely on this technology for their security needs.
Migration to SOA, however, means new obstacles, since JAAS-based systems represent homogeneous, API-based silos in the larger heterogeneous SOA environments, and yet they contain heavy technological investment that cannot (and should not!) be discarded because of the transition to SOA.
This article provides a high-level overview of steps to bring JAAS-based security into the service-oriented world and make it available for other participants outside of the Java universe. It is based on experiences gathered while working on the SOA-enabling of an existing security system, and builds upon the presentation given by myself and my colleague Josh Bregman at the 2006 RSA Conference. I am also grateful to Neil Smithline, WebLogic security architect, for his valuable comments and help with reviewing this article.
In order to understand what needs to be done, it is first necessary to review the current state of affairs.
The system being considered represents a security framework that is used for user authentication and, through its
Principal-based Java policies, for authorization. Many large institutions, especially related to the financial industry, with its complex regulations and audit requirements, have built very extensive (and expensive!) in-house security systems around JAAS. It is also worth noting that in many of such systems only the authentication part of JAAS is used, while authorization is mainly enforced by the applications (as opposed to the JVM) by checking with external policy engines.
The typical architecture of such systems is shown in Figure 1--it is the integration between the "JAAS" component inside the container and the box labeled "Enterprise Identity Assets" that is frequently the most valuable (and complicated) part of the security framework.
There are several traits of such security systems that are worth noting:
- They are in-process; i.e., can serve only applications within the same JVM.
- Only Java applications are supported.
JAAS authorization finds limited use because of its reliance on a Java policy-based security model. This model relies on policy entries stored in a plain text file and has the following shortcomings when considered for enterprise-level authorization:
- Java policies are not easily integrated with back-end Enterprise Identity Management systems, because the default JDK policy implementation is not suitable for retrieving policy records from places other than the Java policy files.
- They are permission-based and well-designed for authorizing access to resources, as they do not support conducting access control checks by parameters other than location, signer, or
Principal--for example, allowing access only during certain hours or weekdays.
As a result, many of today's real world enterprise systems avoid JAAS authorization altogether and embed security logic right into the application code. This logic controls access to application-specific resources in a way that is not available via permission-based Java policies. Unfortunately, hardcoded security logic has many inherent pitfalls, such as lacking deployment and runtime flexibility, presenting a maintenance nightmare, being inconsistent across multiple applications, and so on. More advanced applications, while still using JAAS for authentication, delegate all authorization decisions to external entitlements systems.
When considering an interconnecting web of services in a typical large enterprise, shown in Figure 2, it becomes clear why the Java-and-JVM-centric approach of JAAS is insufficient. This limitation is especially true when it comes to authorization, as
Principals can not work across multiple JVMs. It is difficult to share policy permission files and even harder to maintain and update them, just to name a few issues.
There are multiple factors that make JAAS-based security inadequate for protecting such enterprise networks:
- Heterogeneous nature of the participating systems: A Java-centric approach will not work in many cases.
- Multiple containers: The JAAS world is confined to a single JVM, and provides a rather limited support for working across multiple containers, as was explained above.
The place of JAAS within such systems is typically limited to working as an authentication service within one or more of the participating components to establish a local representation of user identity for the duration of a single interaction. As explained previously, authorization is typically delegated to an external service, or, in some cases, embedded directly into the logic of the participating applications.
When creating a new security model that is applicable for a SOA environment, the following main goals must be constantly kept in mind:
- It has to reuse pre-existing investment into the integration between JAAS and back-end Identity Management Systems as much as is realistically possible.
- Such a solution must be applicable across a variety of participating services, whether Java-based or not.
- A security service must be flexible when it comes to location; i.e., applications must have a choice or either remote or local invocation.
- Addition of new authentication schemes and token types must be supported via an extensibility mechanism.
In order to be useful outside of the immediate container, the authorization has to rely on an external policy engine, while enforcement has to be done by the application itself.
The main focus of this section is on the actual contents and interface to the Security Services box in the Figure 3, and the best ways to reuse the existing code when building it. To emphasize most important points, the text is accompanied by brief examples in the form of Java pseudocode and fragments of the service's XML schema.
Exposing a web service to serve requests from applications for authentication and authorization decisions will satisfy requirements for location independence and platform neutrality, specified in the section Desired Model. Addressing other requirements requires integration of the existing JAAS infrastructure into the new SOA Security Service.
By reusing the JAAS infrastructure, the SOA Security Service will be able to utilize and build upon its strong points:
- Integration with the back-end Enterprise Identity Management systems.
- Pluggable and stackable login modules, which support different authentication methods.
- Flexible callback mechanism, which includes support for different authentication schemes, including ones with challenge-response exchanges.
The SOAP authentication interface of the SOA security service will expect user login credential(s) to be passed in the SOAP body of the message, and upon successful authentication, will return a security token of the requested (or default) type, which will be used for representing user identity in subsequent SOAP calls. Its Java pseudocode will look like this:
AuthnResult authenticate(Hashmap credentials, Hashmap context, String tokenType)
The web service facade will initialize the
credentials map using the request parameters to handle the JAAS authentication callbacks wrapped behind it. For the cases when the passed information is not sufficient for authentication, or for challenge-response schemes, service response must support a challenge (through its XML schema):
<xsd:complexType name="AuthnResult"> <xsd:choice> <xsd:element name="IdentityAssertion" type="ns:IdentityAssertionType"/> <xsd:element name="Challenge" type="ns:ChallengeType"/> </xsd:choice> </xsd:complexType>
Authorization interface will accept a security token that represents the user, and create an authorization context from the request parameters. It will return policy decisions in the response message. Its Java pseudocode will look like this:
AuthzResult authorize(IdentityAssertionType token, Resource resource, Action action, Hashmap context)
The authorization engine may require some additional parameters for making a decision, so there should be a mechanism foreseen in the interface (expressed via an XML schema) to communicate this back to the caller:
<xsd:complexType name="AuthzResult"> <xsd:choice> <xsd:element name="AtzResult" type="xsd:boolean"/> <xsd:element name="ContextRequests" type="ns:ContextRequestsType"/> </xsd:choice> </xsd:complexType>
A SOAP interface designed for a security service should be generic and ready to accept new types of incoming objects without updates. This interface represents a thin layer, intended for conversion between SOAP and Java types. Java uses an
Object type to allow for passing arbitrary objects, and supports runtime type discovery. The SOAP interface has to use the special type declaration
any in the service's XML schema:
<xsd:complexType name="AssertionType"> <xsd:sequence> <xsd:any namespace="##any" minOccurs="0" processContents="strict" /> </xsd:sequence> </xsd:complexType>
This declaration tells the XML processor that an element of XML type
AssertionType may contain any subelement belonging to any namespace, including empty and global ones.
In order to support new security token types, the SOA security service must be designed around the concept of providers or some other pluggable mechanism; i.e., adding new token types should require changes neither to the service API nor to its framework code. These providers have to be able to perform two primary functions for their specific token types:
- Verify security tokens presented in SOAP requests without the overhead of credential verification performed during authentication (see details).
- Issue new tokens to return back to the user in SOAP responses.
Figure 4 represents the high-level breakdown of different components inside the service.
Unfortunately, SOAP structures do not map directly to Java ones, so converting Java interfaces to XML is not straightforward. Besides, there are many technical considerations that must be taken into account for successful development of such a service. A couple of design issues will be reviewed below to provide guidance on how they can be dealt with.
As was already discussed in the section JAAS View of the World, JAAS presents a callback-based authentication interface, not suitable for SOA solutions, and, through limitation of its API, encourages authorization logic built into the application code. The task at hand, conversion to a SOA service, demands the creation of a flexible SOAP interface and a convenient representation of user identity.
The first issue that needs to be solved with SOA security service is the representation of the user identity. In pure Java applications, it would be an instance of the
Principalobject, but it would not make any sense to try passing it to non-Java services. Instead, the user must be represented by a commonly understandable security token, which can be unambiguously verified at both communicating endpoints. Various token formats are possible--standard-based or custom ones--as long as both sides have necessary code to perform XML serialization/deserialization for conversion between XML and Java representations. Binary tokens (such as X509 certificates) need to be Base64-encoded; text-based ones (such as SAML assertions) may be used as is or also encoded.
In any case, security tokens, which represent user identity, must be signed and timestamped to be useful in SOA environments. Expiration time of the token is either included in the token itself (like validity period is in X.509 certificates), or should be determined by timeout setting in the security service itself. Otherwise, it is impossible to protect against identity spoofing and replay attacks when using such tokens.
When an SOA security service receives a security token of a certain XML type via its SOAP interface, it has to be able to construct an appropriate Java object from it. Unfortunately, XML processors vary greatly in their abilities to deal with the
any XML type, which is the preferred way of implementing such generic interfaces in XML (see the section Support for New Token Types). Some of the XML compilers do not support this keyword at all and will not compile such schemas; others (including .NET) will generate a class named
AssertionType with an internal member variable of the type
XmlElement (or similar to it, depending on the language), which contains raw XML, leaving all the parsing burden to developers.
The second problem is making the SOAP layer map the actual XML type of the XML element, received in the
anydeclaration, to the appropriate Java type. In other words, the service has to provide an automatic mapping between XML and Java types. Otherwise, the service's interface and code would have to be updated every time support for a new token type is added or changed in any way.
To address this requirement, one should pick an advanced XML processor with mapping capabilities, and pair it with a SOAP engine. The following combination provides reasonably good results in Java: using Axis for handling SOAP interfaces, and letting Castor handle conversion of all XML types (the newer releases of Axis already include Castor serializers). Shown below is an example of Axis type declaration (from its WSDD deployment descriptor file), which invokes Castor serializers for processing XML elements of the XML type
<typeMapping xmlns:ns="http://security.bea.com/soap-types.xsd" qname="ns:AuthenticationResultType" type="java:com.bea.security.ssmws.soap.AuthenticationResultType" serializer="org.apache.axis.encoding.ser.castor.CastorSerializerFactory" deserializer="org.apache.axis.encoding.ser.castor.CastorDeserializerFactory" encodingStyle="" />
Castor supports external files with type-mapping declarations, so adding support for a new XML token type can be accomplished simply by including a new element declaration to Castor's type mapping file:
<class name="com.bea.security.ssmws.credentials.SamlCredentialHolderImpl"> <map-to cst:xml="SAML" cst:ns-uri="http://security.bea.com/soap-types.xsd" /> <field name="saml" type="java.lang.String" handler="com.bea.security.Base64String"> <bind-xml node="text"/> </field> </class>
This declaration tells Castor that an XML element named SAML, belonging to the XML namespace
http://security.bea.com/soap-types.xsd, should be mapped, after applying Base64 conversion, to the text field
saml in a Java object of the type
SamlCredentialHolderImpl. The Java implementation class, specified in the above declaration, should be made available on the service's classpath for this mapping to work. Additionally, the service's configuration should be updated to include new provider(s), which can process instances of the new Java token type, and the service should be restarted for the changes to take effect (unless it uses dynamic classloading schemes to pick up new classes and configuration changes at runtime).
It is important to distinguish between login credentials, typically in the form of username/password, and security tokens representing user identity. The former ones will be used for the login operation, which is potentially quite expensive. Security tokens, on the other hand, already encapsulate user identity and do not require going through the full authentication process. An internal service API (not exposed via SOAP interface) will include a method for verification of the received tokens:
boolean verifySecurityToken(IdentityAssertionType token)
It is also necessary for token providers to implement conversion classes for conversion between the Java
Principal type and security token instances:
Principal getJavaPrincipal(IdentityAssertionType token) IdentityAssertionType getSecurityToken(Principal user, IdentityAssertionType oldToken, String tokenType, Hashmap context)
The first method is used to create a
Principalclass, which can be used in subsequent calls to other Java components or applications. The second function does the opposite: given a user representation in the form of a
Principalinterface, creates a security token (convertible to XML) suitable for passing back through a SOAP interface. The
oldToken object may be useful for some provider implementations, but should be treated as an optional parameter.
Once a Java representation of a security token has been obtained, it must be converted into a
Principalobject, suitable for using in Java applications. As was explained above, the SOA security service will include a special method,
verifySecurityToken(), which is going to be called for verification of the received token as the very first step before attempting to use it.
After successful verification of the security token during processing of a SOAP request, it would be possible to call the
getJavaPrincipal() method to obtain a Java
Principal object. However, doing this on every invocation would be terribly inefficient and would likely lead to poor scalability and performance problems. Instead, the constructed
Principal object should be cached for short
time-to-live (TTL) periods, using the provider's hints and security token cryptographic hash (MD5 or SHA1) of the security token to produce a key. This way, when the next SOAP request with this token is received by SOA security service, it will be able to bypass the step of creating a
Principal by retrieving the cached value instead. Again, it is very important to stress that the entries in the identity cache must be short-lived--otherwise, this might open a door for replay attacks against the service.
There are, of course, many other considerations that have to be weighed when designing a SOA security service. While it is impossible to cover them all within a single article, they are worth at least mentioning:
- The authorization interface may benefit from supporting XACMLrequest/response protocol for conveying authorization decisions, as it solves at least syntactical compatibility issues among communicating services.
- Establishing trust for remote invocations.
- Avoiding interoperability issues.
- Performance optimizations.
AquaLogic Enterprise Security (ALES), a fine-grained entitlements system from BEA, will be used as an example of the described approach. Its Security Service Modules (SSM) provide security services to applications via configured Authentication, Authorization, Credential Mapping, Role Mapping, and Auditing services.
Initially, SSMs were available only via a Java API, based on a JAAS-based authentication service. As a result, it could be used only within Java applications, as shown in Figure 5.
However, with the rise of SOA, ALES in its old incarnation faced many of the challenges, explained in the section Real World. All of the discussed techniques were used to add a new Web Service API to it, wrapping the existing Java API (with its underlying JAAS structures) and exposing it as a generic security web service. Figure 6 shows SSM integration with .NET web applications using a web service interface.
As a result of this effort, the old JVM-centric service has been catapulted right into usage within complex enterprise networks, with their multi-vendor and multi-language systems communicating to each other using SOAP, while at the same time preserving the existing JAAS code base under the covers. Thanks to the new Web Service security API, ALES is now capable of orchestrating access control decisions in a uniform way across entire enterprises, using security policies centrally configured at the ALES Administration Server.