ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


O'Reilly Book Excerpts: WebLogic: The Definitive Guide

Java and Security, Part 2

by Avinash Chugh, Jon Mountjoy

Related Reading

WebLogic: The Definitive Guide
By Jon Mountjoy, Avinash Chugh

Editor's note: Last week's excerpt from Chapter 17 of WebLogic: The Definitive Guide examined WebLogic's security mechanisms, including the Java Security Manager. In this week's second and final excerpt from the book, authors Avinash Chugh and Jon Mountjoy cover WebLogic's various security providers and their default implementations, along with a look at how to authenticate using JAAS, and examples of Authentication and Identity Assertion Providers.

The Providers

Now we'll take a closer look at the different SSPIs that constitute a security realm. We'll learn about WebLogic's default implementation of these security providers and how to configure them. The default implementation provides the authentication architecture (and much more) that we have just seen. You can replace one or more of the providers with your own code if you want to change its behavior. Once again, the Administration Console lets you view and modify the configuration of these security providers. All of the security providers available to your realm can be found under the Security/Realms/myrealm/Providers node in the left pane of the Administration Console, where "myrealm" refers to the name of the security realm. Finally, we'll learn about the embedded LDAP server that holds all of the security data for the domain on behalf of the default security providers.

Authentication Providers

Authentication refers to the server's ability to reliably verify the identity of a user or system. We generally refer to a user or system being authenticated as simply a user. A user requires some proof of identity before it can establish trust with the server. WebLogic supports Authentication Providers that can validate user credentials based on a username-password combination or a digital certificate. The security provider repository, which stores the user and group information, can be implemented in the following ways:

WebLogic's authentication provider architecture closely follows the authentication part of the standard JAAS. Following the JAAS terminology, a subject represents the source of a security request—it represents a user or system that is trying to be authenticated. The point of authentication is to assign principals to a subject. Principals are identities that represent the result of a successful authorization. As we have seen, when the system user authenticates successfully, the subject representing this user is assigned a principal recording the fact that he is in the Administrators group, and another recording that he is a WebLogic user with the name system. Therefore, a subject is a standard container for authentication information, including principals. A client can use a subject to query its identity and other attributes. Figure 17-2 illustrates this client authentication process.

Figure 17-2
Figure 17-2. The client authentication process

The Authentication Provider adheres to the standard JAAS framework by structuring the authentication sequence on top of a number of configurable JAAS LoginModules. LoginModules are critical components of an Authentication Provider, as they are responsible for authenticating users within a security realm and populating the subject with the required principals (existing users and groups).

WebLogic demands that you have at least one authentication provider configured in your security realm.

Principal Validation Providers

Once a (possibly remote) client has established trust with WebLogic, the authenticated subject is retained on the client between server invocations. The Principal Validation Provider ensures that no hanky-panky has taken place with the subject's principals at any time between these invocations. It does this by signing and verifying the authenticity of the principals held by the subject. Once the principals have been validated, they can be used by an Authorization Provider for access control checks or by the Role Mapping Provider for role-mapping decisions. A security realm must define a Principal Validation Provider for each Authentication Provider.

Identity Assertion Providers

Identity Assertion Providers help secure access to the entry points of a WebLogic deployment. Instead of using usernames and passwords, an external client may use tokens to establish trust with a WebLogic Server. The Identity Assertion Provider verifies a token and, if successful, maps it to a valid WebLogic user. Once the token is mapped to a valid user, an Authentication Provider can then generate the principals for the user. This mechanism is called perimeter authentication, so you can consider an Identity Assertion Provider a special type of Authentication Provider. The key point here is that an external agent is responsible for authenticating the user, and then for conveying the user data to WebLogic.

As a side effect, perimeter authentication also enables single sign-on. For instance, an Identity Assertion Provider could supply an X.509 digital certificate as an identity token, and these credentials then could be used across multiple systems. WebLogic supports Identity Assertion Providers that can handle different token types (X.509, IIOP-CSIv2). Alternatively, you can create an Identity Assertion Provider that supports custom token types (e.g. Kerberos tickets). An Identity Assertion Provider can have multiple active token types. However, a token type can be active only in a single provider. Of course, a security realm may support multiple Identity Assertion Providers, though none is required. Figure 17-3 illustrates perimeter authentication.

Figure 17-3
Figure 17-3. Identity assertion during perimeter authentication

The Default Authentication Providers

WebLogic supports authentication based on username-password combinations, authentication using digital certificates transmitted either directly to WebLogic or via an HTTP server, and perimeter-based authentication based on security tokens. WebLogic lets you use the Authentication Providers described next.

Default Authenticator
This is WebLogic's default Authentication Provider, which relies on the embedded LDAP server to persist all security information.
Realm Adapter Authentication Provider
This provides backward compatibility with user and group information held in old WebLogic 6.x realms.
Default Identity Asserter
This is WebLogic's default Identity Assertion Provider, which verifies the authenticity of X.509 and IIOP-CSIv2 tokens and maps them to valid WebLogic users.
LDAP Authentication Providers
These providers use external LDAP stores to persist all security information about users and groups. WebLogic lets you configure Authenticators that can access various LDAP stores, including Open LDAP, Sun's iPlanet, Microsoft's Active Directory, and Novell NDS.

In addition, you may assign custom-built Authenticators and Identity Asserters to the security realm.

In order to access the Authentication Providers assigned to a realm, select the Authentication node from the left pane of the Administration Console. The right pane then lets you select one of the configured Authenticators, or configure a new Authenticator.

A security realm can have a sequence of Authentication Providers (and hence JAAS LoginModules) working in unison. Each Authentication Provider has a JAAS control flag setting that determines how the overall login sequence behaves with respect to that particular Authentication Provider. This is no different from how you configure multiple LoginModules within a J2SE application. To order the Authentication Providers within a security realm, select the Authentication node from the left pane and choose the Re-order the Configured Authentication Providers option. The control flag of each authentication provider can take the following values:

REQUIRED
This option is the default setting for any Authentication Provider. A required Authentication Provider is always invoked, irrespective of the control flag settings on other providers. The overall authentication cannot succeed if any REQUIRED provider fails. Thus, REQUIRED providers are always invoked, and overall authentication fails if any one of them fails.
REQUISITE
This option also requires the Authentication Provider to succeed during the login sequence. However, all of the REQUISITE providers need not be invoked for the overall authentication to succeed. If a REQUISITE provider succeeds, the authentication proceeds as normal to other providers in the sequence. However, if it fails, the overall authentication cannot succeed, and control is immediately passed back to the application once all REQUIRED providers in the login sequence have been invoked.
SUFFICIENT
This option does not require the Authentication Provider to succeed during the login sequence. If a SUFFICIENT provider does succeed, the overall authentication proceeds to ensure that only the remaining REQUIRED providers in the login sequence are executed. However, if it fails, the overall authentication proceeds as normal to the other providers in the login sequence.
OPTIONAL
This option does not require the Authentication Provider to succeed during the login sequence. Regardless of whether an OPTIONAL provider succeeds, the authentication proceeds to other providers that have been configured as part of the login sequence.

When multiple LoginModules are used to authenticate a user, the authentication occurs in two phases. During the first phase, the modules are asked to attempt to authenticate the user. Only if the modules pass this phase is the second phase invoked. During the second phase, each module commits the login and assigns the relevant principals to the subject. The control flags influence how this two-phase commit occurs. Thus, for the overall authentication to succeed, the following rules must be met:

Though you can write your own Authenticators, WebLogic's default implementation of the SSPIs comes with a number of built-in authenticators that you can use. Let's look at these authenticators in more detail.

The Default Authenticator

The Default Authenticator authenticates against the embedded LDAP repository. It provides the notion of a user and group and stores user and group information in its own repository, allowing you to manipulate this information. The only configurable option provided by this Authenticator is the Minimum Password Length setting. By default, all WebLogic users must specify a password that is at least eight characters in length; the password length is validated when the user is created. When you log on to the Administration Server using the console or through a JNDI context, it is generally this authenticator that validates the username and passwords you supply. Of course, the control flag will determine exactly which providers are called.

Configuring an LDAP authenticator

WebLogic also lets you configure an Authentication Provider that can use existing external LDAP directories such as iPlanet LDAP, Active Directory, Open LDAP, and Novell NDS. In fact, WebLogic's LDAP Authenticators can interface with any LDAP v3-compliant directory servers. In this section, we look at how you can set up an iPlanet Authenticator for your security realm. Other LDAP Authenticators can be configured along the same lines. By using one of the LDAP Authenticators, you can configure WebLogic to recognize users and groups defined in the LDAP repositories and authenticate against this information.

First, select the Authentication node under the realm from the left frame of the Administration Console. If you're using the default security realm myrealm, then the node will be under Security/Realms/myrealm/Providers. Then, select the "Configure a new iPlanet Authenticator" option, either by right-clicking the node or from the right frame itself. Now under the General tab on the right, you'll see the overall details of the new Authenticator. Choose a name for the Authenticator and make sure that the value of the JAAS Control Flag is Required, and then hit the Create button.

Note: When starting out, it may be useful to set the Control Flag to Optional. In this way, even if your LDAP authentication fails, you still can authenticate using WebLogic's default authenticator and gain access to the Administration Console.

Now select the iPlanet LDAP tab, and enter the values for the host, port, and principal as they apply to your LDAP server. Here, the principal refers to the distinguished name (DN) of the LDAP user that WebLogic will use to connect to the LDAP Server. Typically, you'll use the DN associated with some administrative user account on the LDAP server. For iPlanet LDAP, this is usually uid=admin, ou=Administrators, ou=TopologyManagement, o=NetscapeRoot. If the LDAP server is listening on an SSL port, tick the SSL Enabled option and ensure that you've specified the SSL port. Now hit the Apply button to save changes to the form. Next, change the Credential attribute for the LDAP Principal. In the new screen, you must enter the password that will be used to authenticate the LDAP user defined in the Principal attribute.

Now, select the Users tab and make sure the fields in this form match the configuration of your LDAP repository. In most cases, you will need to modify only the value of the User Base DN attribute to ou=people, o=mydomain.com. This attribute defines the base DN of the branch within the LDAP tree that holds the actual users. Table 17-3 lists the other configuration settings under the Users tab.

Table 17-3. Configuring the Users tab for an LDAP Authenticator

SettingDescriptionDefault
User Object Class This attribute indicates the LDAP object class that holds user information person
User Name Attribute This setting specifies the name of the attribute within the LDAP user object that holds the username. uid
User Search Scope This setting determines how the users are organized in a multilevel hierarchy or a flat, single-level tree. It affects how deep in the hierarchy to search for users. You can choose from the following values: subtree/onelevel. subtree
User From Name Filter This attribute specifies a search filter for finding a user given the username. "(&(uid=%u)(objectclass=person))"

WebLogic populates these fields with sensible default values, so in most cases you won't have to alter them. Hit the Apply button and move on to the Groups tab. Again, you need to ensure the settings accurately reflect the structure of your LDAP repository. In most cases, you will need to modify only the Group Base DN setting to (say) ou=groups, o=mydomain.com. Table 17-4 lists the other configuration settings available under the Groups tab.

Table 17-4. Configuring the Groups tab for an LDAP Authenticator

SettingDefault
Static Group Object Class groupofuniquenames
Static Group Name Attribute cn
cn Group Search Scope subtree
Group From Name Filter "(|(&(cn=%g)(objectclass=groupofUniqueNames))(&(cn=%g)(objectclass=groupOfURLs)))"

These settings are pretty straightforward, and they have the same semantics as the attributes under the Users tab, just applied to groups.

The Membership tab determines how group members are stored and located in the LDAP directory. WebLogic specifies default values for all fields in the form. Table 17-5 lists two important attributes under the Membership tab.

Table 17-5. Configuring the Membership tab for an LDAP Authenticator

SettingDescriptionDefault
Static Member DN Attribute This setting specifies the name of the attribute within the LDAP group object that holds the DNs of members of the group. member
Static Group DNs from Member DN Filter This attribute specifies a search filter for finding all groups that contain the member, given the name of the group member. (&(uniquemember=%M)(objectclass=groupofuniquenames))

Unlike the other authentication providers, the iPlanet provider also supports dynamic groups for which there are additional options.

Now that you have configured the LDAP Authenticator, you are almost ready to reboot the Administration Server. However, you need to ensure that the server's boot identity (i.e., the WebLogic user account used to start the server) corresponds to an LDAP user with the necessary Admin privileges. This means you need to use the iPlanet Console (or any management tool specific to the LDAP server) and complete the following steps:

  1. Create an Administrators group in the LDAP repository and place the LDAP user, which is associated with WebLogic's boot identity, in this group.

  2. If you are unable to create an Administrators group, create a new group in the LDAP Repository—say, MyAdministrators. Make the LDAP user, which is associated with WebLogic's boot identity, a member of the MyAdministrators group. Then using WebLogic's Administration Console, assign the MyAdministrators group to the default global role Admin.

By doing this, you guarantee that WebLogic's boot identity has the required Admin privileges. The next time you restart the server, go into the Administration Console and remove the Default Authenticator from the list of Authentication Providers. Once you've applied these changes, restart the server—hopefully, you should be able to boot without any authentication errors and set up your WebLogic domain to use the LDAP repository for its user and group information base.

The Default Identity Asserter

The Default Identity Asserter supports perimeter authentication using either X.509 certificates or IIOP CORBA Common Secure Interoperability Version 2 (CSIv2) tokens. A good example of perimeter authentication is when you configure a web application to use CLIENT-CERT authentication. In this case, WebLogic can perform identity assertion based on values from request headers and cookies. If the header name or cookie name matches the active token type for the provider, the value is passed to the provider.

This provider requires you to configure the following attributes:

User Name Mapper Class Name
This attribute specifies the name of a Java class that maps the X.509 certificates or X.501 DNS to WebLogic users, according to some scheme. The User Name Mapping class must implement the weblogic.security.providers.authentication. UserNameMapper interface, and also must be available in WebLogic's CLASSPATH during startup.
Trusted Client Principals
This attribute specifies a list of client principals that may rely on CSIv2 identity assertion. You can use the wildcard character (*) to indicate that all client principals are trusted. If a client principal isn't included in this list, the CSIv2 identity assertion fails and access is denied.

Note that Identity Assertion Providers do not verify proof material. A user can forge a CSIv2 token, for instance, and assume a false identity. Because WebLogic cannot trust such tokens, the Trusted Client Principals setting offers a way to restrict the set of client principals that can use identity assertion. We saw a good example of using X.509 certificates as perimeter authentication in the previous chapter, where we also supplied a custom username mapper class for mapping digital certificates to WebLogic users. When using two-way SSL and X.509 certificates for identity assertion, the SSL protocol ensures that the certificate is not forged. So, the Trusted Client Principals setting applies only to CSIv2 identity tokens.

WebLogic 8.1 comes with a default username mapper that you can enable from the Details tab. This is a general username mapper that can be configured to extract a username from a given attribute of the subject DN field in a certificate. So, for example, if the client's certificate has an email attribute (E), then you can set the Default User Name Mapper Attribute Type to E. You also can specify a delimiter, in which case WebLogic will use that part of the attribute up to but not including the delimiter. For instance, if you need to extract a username from the email attribute, you will want to use the delimiter @. Other attribute types that can be used are the C, CN, L, O, OU, S, and ST types. If you need anything more complex than this, you will have to create your own username mapper class. See Example 17-2 later in this chapter to learn how you can write a custom username mapper.

Authorization Providers

Authorization is synonymous with access control—it determines whether a subject has access to a resource. Whenever an application requests an operation on a protected resource, the resource container that receives the request calls the WebLogic Security framework to determine whether the user is authorized to access the resource. In making this call, any relevant request parameters, such as the subject making the request, are passed to the framework. A few things need to happen before an access decision can be made, as illustrated in Figure 17-4.

First, the configured Role Mapping Providers are invoked. These providers use the request parameters to determine a set of roles that are valid for the subject. After this, the Authorization Providers are asked to decide whether access should be allowed. The Adjudication Provider has the final say in the matter. It looks at the different access decisions returned by the Authorization Providers and reconciles any potential conflicts. The Adjudication Provider generates a final verdict based on these individual access decisions.

A realm may include one or more Authorization Providers. For instance, you could define Authorization Providers that separately control access to JNDI branches, web applications, JMS connection factories, and more.

Figure 17-4
Figure 17-4. The authorization process

The Default Authorization Provider

WebLogic's Authorization Provider performs the aforementioned tasks by using a policy-based authorization engine. This means that WebLogic's resources are protected via security policies assigned at deployment time. Earlier, in the section "The Security Provider Architecture," we looked at how your security policy statements can help protect server-side resources.

Security policies may be assigned manually through the Administration Console, or automatically via the role settings specified in deployment descriptors. The Default Authorizer lets you specify whether the authorizer will store policies that are created when EJBs and web applications are deployed. The Policy Deployment Enabled option indicates whether the provider will evaluate security policies while EJBs and web applications are being deployed. By default, this setting is enabled. This option is quite similar to the Role Deployment Enabled flag in the Role Mapping Provider.

Role Mapping Providers

A Role Mapping Provider determines dynamically at call time the set of roles that are valid for a particular subject when it tries to access a protected resource. An Authorization Provider can then use this information to determine whether a user is allowed access to the resource by evaluating the policy and role information.

The role information is generally based on the role settings defined in J2EE and WebLogic-specific deployment descriptors, the requested operation on the resource, and any custom business logic. You can configure these roles at deployment time, either using the deployment descriptors or via the Administration Console. Because the Authorization Provider makes calls to the Role Mapping Provider just before calculating an authorization decision, and because the role binding occurs dynamically, this gives you the ideal opportunity to embed your own logic for role allocation into a custom provider.

A security realm can define one or more Role Mapping Providers. You could configure a Role Mapping Provider that handles role associations for resources bound to the JNDI tree, or another that handles role associations for web applications.

The Default Role Mapper

Given a protected resource and a user, WebLogic's default Role Mapping Provider will determine all the roles that are valid for that user for the resource using the role information stored in its repository. In order to configure the Default Role Mapper, select the Default Role Mapper node from under the Role Mapping node in the left pane of the Administration Console.

The Default Role Mapper has one configuration setting only: the Role Deployment Enabled option. This indicates whether the provider stores roles that are created while deploying a web application or EJB. This setting is similar to the Ignore Security Data in Deployment Descriptors setting that we encountered earlier, except that it is specific to role information. If the Role Deployment Enabled option is set, WebLogic automatically creates roles based on security data in the deployment descriptors and stores them in the embedded LDAP server. This means that if you subsequently ignore the security data in the deployment descriptors, the deployment descriptors won't be processed the next time the EJB or web application is deployed. By default, the Role Deployment Enabled setting is enabled, and you need at least one role mapper with this setting enabled in order to deploy web applications and EJBs.

Adjudication Providers

A subject may access a protected resource in a way that requires multiple Authorization Providers to decide whether the subject has access to the protected resource. Each Authorization Provider may pass one of the following access decisions: PERMIT, ABSTAIN, or DENY. The Adjudication Provider needs to arbitrate between potentially conflicting decisions made by the different Authorization Providers involved. Typically, the Adjudication Provider resolves potential authorization conflicts among the Authorization Providers by carefully weighing the access decisions of each provider.

Thus, you must define an Adjudication Provider if the security realm supports multiple Authorization Providers. Clearly, a security realm may have only a single Adjudication Provider!

The Default Adjudicator

In order to configure the Default Adjudicator, select the Default Adjudicator node from under the Adjudication node in the left frame of the Administration Console.

Here you can adjust the Requires Unanimous Permit setting, which determines the basis on which the Adjudicator grants access to a resource when multiple Authorization Providers are concerned.

If this setting is enabled, the Adjudicator grants access to the resource only if all participating Authorization Providers return a result of PERMIT. That is, the decision must be a unanimous PERMIT decision. If any provider returns a DENY or ABSTAIN, the overall decision must be to refuse access.

If the setting is disabled, the Adjudicator grants access to the resource only if no Authorization Provider returns a DENY. This means that the Adjudicator will grant access to the resource even though a unanimous decision could not be reached. So long as all Authorization Providers vote to PERMIT or ABSTAIN, the subject will be allowed to access the resource. Of course, at least one Authorization Provider must vote to PERMIT access to the resource.

Credential Mapping Providers

When a WebLogic user needs to access some external systems, the credentials of the user need to be mapped to valid credentials on the external system. Only then can a subject already authenticated by WebLogic log into the external system. A Credential Mapping Provider is responsible for associating users authenticated by WebLogic to appropriate credentials in an external system. Typically, the Credential Mapping Provider is invoked by WebLogic on behalf of another component—in particular, the container that hosts a resource adapter that needs to connect to a remote resource.

A Credential Mapping Provider can handle different kinds of user credentials—e.g., a username-password combination, a digital certificate, and more. For instance, you could implement a credential map between WebLogic users and credentials of valid users on a remote legacy DBMS. This could be the username-password combination of a valid user on the remote system who is authorized to perform the necessary operations. You can define these credential mappings either in the deployment descriptors or via the Administration Console. A security realm must define at least one Credential Mapping Provider. If you've defined multiple providers, WebLogic queries all providers and returns a list of credentials that may be associated with the WebLogic user.

The Default Credential Mapper

WebLogic's Credential Mapper associates WebLogic users to appropriate credentials in an external system when a user makes use of a resource adapter. So, the Credential Mapping Provider holds a map of WebLogic users and groups to external identities that can be used to authenticate to a remote system. A good example of this is the use of J2EE connectors. Here you typically need to map a WebLogic user to a remote user that has access to the target EIS. Chapter 7 shows how to configure credential maps for deployed J2EE connectors.

In order to configure the provider itself select the Default Credential Mapper node from under Providers/Credential Mapping in your security realm. The Default Credential Mapper provides only one configuration setting, the Credential Mapping Deployment Enabled option, which indicates whether the provider processes the credential maps from the descriptor files of a resource adapter when it is deployed. By default, the Credential Mapping Deployment Enabled flag is set to true. Like the other Deployment Enabled settings, if you disable this setting, the credential maps specified in the deployment descriptor for the resource adapter won't overwrite the credential maps you may have created via the Administration Console.

Auditing Providers

Auditing is another important feature of WebLogic's security framework. Nonrepudiation requires that you maintain a log of security events that provides an electronic trace of how the data has been accessed. WebLogic's Auditing Provider can be configured to log information about security requests and their outcomes. Usually, the Auditing Provider is invoked by WebLogic's Auditor on behalf of other security providers, both before and after a security operation has been executed. In this way, the Auditor is able to capture detailed information about any security requests and responses.

Typically, the Auditing Provider will decide whether to audit the security request based on several criteria. For instance, you could configure a security level for the Auditing Provider that automatically filters out audit requests that do not reach the desired security level. An Auditing Provider also supports channels that can record the information to a variety of sinks, such as an LDAP store, database table, or plain file. WebLogic's Auditor can interact with multiple Auditing Providers. However, you may choose not to define any Auditing Provider for a security realm.

The Default Auditor

By default, there are no Auditing Providers configured for a security realm. In order to configure a new Auditing Provider, expand the Auditing node from the left frame of the Administration Console and then choose the "Configure a new Default Auditor" option. In the new screen, assign a severity level for the audit log and hit the Create button.

The severity level determines the kind of events that are logged by WebLogic's Default Auditor. The lower the severity level, the more verbose the auditing. The severity level can be chosen from the following (in increasing order of verbosity): FAILURE, SUCCESS, ERROR, WARNING, and INFORMATION. For example, a severity level of FAILURE ensures that the Auditor logs only security requests that have failed (e.g., failed authentication), while a severity level of INFORMATION ensures that the Auditor logs all authentication activity. The default severity level is ERROR.

Note: The lower the severity level, the more verbose the output. This can impact server performance because the Auditor is potentially a lot busier. You should set a sensible severity level and carefully monitor your setup to ensure it doesn't yield unacceptable performance levels.

The audit information is written to the mydomain\myserver\DefaultAuditRecorder.log file, where mydomain is the name of the WebLogic domain. For example, if an authenticated user attempts to dip into a connection pool and is denied access, the Default Auditor logs the following information:

#### Audit Record Begin <05-Sep-02 22:24:46> <Severity =FAILURE>
<<<Event Type = Authorization Audit Event ><Subject: 1
   Principal = class weblogic.security.principal.WLSUserImpl("A")>
<ONCE><<jdbc>><type=<jdbc>, application=, module=, resourceType=ConnectionPool,
resource=MyPool, action=reserve>>> Audit Record End ####

The Embedded LDAP Server

Any implementation of the SSPIs requires some kind of security provider database that can act as a repository for the domain's security data. WebLogic relies on an embedded LDAP server to persist all of its information about users, groups, policies, roles, and user credentials. The embedded LDAP server is accessible to all of WebLogic's security providers that need to store and manipulate such data: the Authentication, Authorization, Role Mapping, and Credential Mapping Providers. In the default setup, the Administration Server holds a master LDAP repository, and this repository is then replicated to all Managed Servers. Any changes made by WebLogic's providers are sent to the master LDAP server, which then sends the appropriate changes to each replicated server. Managed Servers in the domain are synchronized as soon as the data is changed on the Administration Server's LDAP repository. There is, however, a small window between the write to the Administration Server and the replication due to network traffic.

To configure the embedded LDAP server, select your domain from the left frame of the Administration Console. Choose the View Domain-Wide Security Settings* option and select the Configuration/Embedded LDAP tab. Table 17-6 lists the various settings that can be configured from the Embedded LDAP tab.

* WebLogic 7.0 users should just choose the Security/Embedded LDAP tab after selecting the domain node.

Table 17-6. Configuring the embedded LDAP server

SettingDescriptionDefault
Credential This setting specifies a password that allows you to connect to the LDAP server. none
Backup Hour/Minute This setting determines the hour/minute when the backups are supposed to occur. 23;5
Backup Copies This setting determines the number of backup copies of the LDAP data that should be made. 7
Cache Enabled This option indicates whether caching is enabled for the LDAP server. true
Cache Size This setting determines the size of the cache used by the LDAP server. 32KB
Cache TTL This setting determines the duration for which items are held in the cache. 60s
Replica Refresh By default, changes are sent periodically to the Managed Servers. If you've made a number of changes while a Managed Server has been down, then sending a large number of changes may be expensive. To optimize this, enabling this parameter ensures all of the replicated data will be refreshed as a whole at boot time. false
Master First In extreme cases, you can enable this flag so that any Managed Server must contact the master LDAP server instead of its local LDAP server. false

Note how the LDAP server can be configured to back itself up. The backup files are written to a directory called mydomain\servername\ldap\backup and can be used to replace those files in the ldapfiles directory.

External access to the LDAP server

You can use your favorite LDAP browser to gain access to WebLogic's embedded LDAP server. This may be useful for integration purposes, or perhaps more usefully as providing a way of importing and exporting user data. Before accessing the server, you need to set up its security credential. Select the Embedded LDAP tab as described in the previous section. Then select the Credential option and enter a credential (password) for the LDAP server. WebLogic will use this password to authenticate any LDAP clients.

Now start up your favorite LDAP browser and point it to the following URL:

ldap://hostname:port/dc=mydomain

Here, mydomain refers to the name of the WebLogic domain. The LDAP server isn't set up for anonymous access, so you must specify the username cn=Admin. For the password, you need to supply the same credentials you configured earlier for WebLogic's embedded LDAP server. You then can browse the LDAP data, which includes information about the users, groups, security roles, policy statements, and more.

Some LDAP browsers also let you import and export the LDAP data in the LDIF format. This provides you with an easy way to migrate some data, such as the information on users and groups located under the DNs ou=people,ou=myrealm,dc=mydomain and ou=groups,ou=myrealm,dc=mydomain, respectively. Once again, myrealm refers to the name of the security realm, while mydomain refers to the name of the WebLogic domain. Experienced administrators can further restrict access to the embedded LDAP server. Because WebLogic's LDAP server also supports the IETF LDAP Access Control Model, you can implement fine-grained access to the LDAP server if the need arises.

In WebLogic 8.1, many security providers also permit the export and import of their data. You can find this facility on the Migration tab of the Authentication, Authorization, Credential Mapper, and Role Mapper Providers.

Configuring Trust Between Two Domains

Two or more WebLogic domains can be configured so that they trust each other. When you've set up trust between two different WebLogic domains, you enable authenticated WebLogic users in one domain to access protected resources in another domain. For instance, an authenticated user in one domain may invoke a protected web service or an EJB in another domain without requiring any additional authentication. Let's say that we have two WebLogic domains, A and B. Trust between the domains means that the subject's principals in one domain—say, A— are accepted by the other domain, B (and vice versa) and are treated as if they were local principals of domain B. The Authorization Providers within domain B remain unaware of the fact that the subject's principals belong to domain A.

Two WebLogic domains will trust each other only if they both have the same Credential attribute. The Credential attribute represents a string value that is assigned to a domain and then is used to sign principals that belong to subjects created in that domain. Ordinarily, if you haven't explicitly set the domain credential, it is assigned a random value when the Administration Server is first booted. All Managed Servers in the domain subsequently import this credential when they are booted as well. In order to set the Credential attribute for a WebLogic domain, select your domain from the left pane of the Administration Console, choose the View Domain-Wide Security Settings* option, and select the Configuration/Advanced tab. If two domains share the same Credential attribute value, the signatures of the principals will match and be recognized by both domains. Thus, in order to establish trust between several domains, you need to simply ensure they are assigned the same value for the Credential attribute.

JAAS Authentication in a Client

We already have seen many examples of how a Java client authenticates itself to WebLogic Server. In most cases, the client submits a username-password combination as its credentials when setting up the JNDI context:

Hashtable env = new Hashtable( );
env.put(Context.INITIAL_CONTEXT_FACTORY,
             "weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL, "t3://10.0.10.10:7001");
env.put(Context.SECURITY_PRINCIPAL, "system");
env.put(Context.SECURITY_CREDENTIALS, "12341234");
Context ctx = new InitialContext(env);
// use the JNDI context as "system" user ...

WebLogic also lets you build Java clients that can use the more standard approach to authentication using JAAS. Even though JAAS authentication is somewhat more long-winded than traditional JNDI-based authentication, your clients will be more portable. Because of the pluggable nature of the JAAS framework, it should enable you to benefit from future changes to the authentication technology without changes to the client code.

Anatomy of a JAAS Client

A JAAS client involves the interplay among a number of classes and interfaces, as shown in Figure 17-5. Let's examine how these different objects interact during JAAS-style authentication:

Subject
This represents the goal of the authentication sequence. Once a client has been authenticated, it obtains a Subject instance that is populated with all of the principals that map to the client.
LoginContext
This is responsible for populating the Subject with its principals. Its allimportant login( ) method delivers an authenticated Subject back to the client. To construct a LoginContext instance, you need to supply objects of two subsidiary classes: a CallBackHandler and LoginModule instance.
CallBackHandler
This is responsible for retrieving the username and password of the client being authenticated. In the case of a Swing-based application, the CallBackHandler instance could conceivably pop up a dialog box requesting the data from the end user. In fact, the CallBackHandler instance is invoked by the LoginModule.
LoginModule

This is any entity capable of authenticating the user's credentials. A separate JAAS configuration file settles how the LoginModule is implemented. In general, you can implement your own login modules. However, WebLogic also provides you with a convenient LoginModule that can authenticate the client via the supplied username and password against a WebLogic instance whose URL is also supplied. On successful authentication, the LoginModule populates the subject with its principals. Using this authenticated subject, the JAAS client can now perform one or more privileged actions.

Figure 17-5
Figure 17-5. Typical interaction when authenticating a JAAS client

PrivilegedAction
Any class that implements the PrivilegedAction interface encapsulates the code that a Java client can run within the security context defined by the populated Subject. The weblogic.security.Security.runAs( ) method allows the client to associate a Subject with the current thread, before invoking the privileged action under this security context.

A Sample JAAS Client

Let's look at how to build a JAAS client that can authenticate itself to WebLogic. We'll cover this example using a top-down approach, starting with what the JAAS client needs to accomplish and then breaking it down into the individual components of its implementation. Let's begin with the main class, SimpleJAASClient, which takes the following steps:

  1. It reads the username, password, and URL as input arguments from the command line.
  2. It attempts to connect to the specified URL and then authenticates the client using the supplied username and password.
  3. It executes a privileged action under the newly acquired authenticated subject. Example 17-1 lists the source code for our JAAS client.

Example 17-1 lists the source code for our JAAS client.

Example 17-1. A sample JAAS client

package com.oreilly.wlguide.security.jaas;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
public class SimpleJAASClient {
   public static void main(String[] args) {
      String username = args[0];
      String password = args[1];
      String url = args[2];
      LoginContext loginContext = null;
      // Create a LoginContext using our own CallBackHander
      try {
         loginContext = new LoginContext("Simple",
            new SimpleCallbackHandler(username, password, url));
      } catch (Exception e) {
         // Can get a SecurityException or a LoginException
         e.printStackTrace( );
         System.exit(-1);
      }
      // Now authenticate. If we don't get an exception, we succeeded
      try {
         loginContext.login( );
      } catch (Exception e) {
         // Can get FailedLoginException, AccountExpiredException,
         // or CredentialExpiredException
         e.printStackTrace( );
         System.exit(-1);
      }
      // Retrieve authenticated subject and perform action using it
      Subject subject = loginContext.getSubject( );
      SimpleAction simpleAction = new SimpleAction(url);
      weblogic.security.Security.runAs(subject, simpleAction);
   }
}

Notice how we've highlighted the important bits of the JAAS client. Our first critical step is to establish a LoginContext object:

loginContext = new LoginContext("Simple",
   new SimpleCallbackHandler(username, password, url));

The LoginContext object initializes the client with the CallBackHandler and LoginModule instances that will be used during JAAS authentication. The second argument to the constructor is our own CallBackHandler instance that will be used by the LoginModule to retrieve the user's credentials, and the URL of the WebLogic instance that will authenticate our client.

The first argument to the constructor, Simple, is used to look up the appropriate LoginModule for the client. JAAS clients rely on a configuration file that maps the names of JAAS login modules to their implementation, and also may specify additional parameters. Example 17-2 lists the JAAS configuration file that we used.

Example 17-2. Login configuration file, jaas.config

Simple {
   weblogic.security.auth.login.UsernamePasswordLoginModule
   required
};

Our configuration file contains a single entry for Simple that specifies WebLogic's LoginModule for authentication on the basis of the given username and password: weblogic.security.auth.login.UsernamePasswordLoginModule. When you run the JAAS client, you must specify the location of this configuration file using a system property. Here's how you would run our sample JAAS client:

java -Djava.security.auth.login.config=jaas.config \
   com.oreilly.wlguide.security.jaas.SimpleJAASClient system pssst t3://10.0.10.10:
8001/

In this way, we can configure the LoginContext to use WebLogic's LoginModule, which supports authentication using a username-password combination. Later, we'll see how you can use the JAAS configuration file to transparently substitute this with your own LoginModule implementation.

After establishing the login context, we've invoked the loginContext.login( ) method to execute the actual login. Our LoginContext will utilize the configured login module and callback handler objects and attempt to authenticate the client with the server. If this client is authenticated successfully, you can retrieve the authenticated subject from the LoginContext:

Subject subject = loginContext.getSubject( );

The getPrincipals( ) method on this authenticated Subject retrieves all of the principals associated with the user. For instance, if our JAAS client authenticated using the credentials of the system administrator, the authenticated Subject holds two principals: system, which represents the user, and Administrators, which represents the user's group. Now, we can use this subject to execute one or more "privileged" actions. In other words, these actions are performed within the context of this authenticated subject:

weblogic.security.Security.runAs(subject, simpleAction);

There is one caveat here—the client must invoke the runAs( ) method on WebLogic's Security class. The runAs( ) method accepts two parameters: the authenticated Subject, and a PrivilegedAction object, which wraps the applicationspecific interaction with the server. Example 17-3 illustrates the action that our JAAS client wishes to execute.

Example 17-3. A very simple action

package com.oreilly.wlguide.security.jaas;
import java.security.PrivilegedAction;
import java.sql.Connection;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class SimpleAction implements PrivilegedAction {
   private static final String JNDI_NAME = "jdbc.xpetstore";
   private String url;
   public SimpleAction(String url) {
      this.url = url;
   }
   public Object run( ) {
      Object obj = null;
      try {
         Context ctx = null;
         Hashtable ht = new Hashtable( );
         ht.put(Context.INITIAL_CONTEXT_FACTORY,
         "weblogic.jndi.WLInitialContextFactory");
         ht.put(Context.PROVIDER_URL, url);
         // Get a context for the JNDI lookup
         ctx = new InitialContext(ht);
         // do any work here
         DataSource ds =(javax.sql.DataSource) ctx.lookup(JNDI_NAME);
         // ...
      } catch (Exception e) {
         e.printStackTrace( );
      }
      return obj;
   }
}

Here you need to recognize the following significant points:

Example 17-4 lists the source code for our CallBackHandler class. In general, the callback handler would interact with the client in some way that prompts the user for the username and password to be used for authentication. In the case of our simple JAAS client, we supply the necessary credentials and URL to the constructor of our callback handler so that the callbacks can easily return this information.

Example 17-4. A simple callback handler

package com.oreilly.wlguide.security.jaas;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import weblogic.security.auth.callback.URLCallback;

public class SimpleCallbackHandler implements CallbackHandler {
   private String username = null;
   private String password = null;
   private String url = null;
   
   public SimpleCallbackHandler(String pUsername, String pPassword, String pUrl) {
      username = pUsername; password = pPassword; url = pUrl;
   }

   public void handle(Callback[] callbacks)
         throws java.io.IOException, UnsupportedCallbackException {
      for (int i = 0; i < callbacks.length; i++) {
         if (callbacks[i] instanceof NameCallback) {
            NameCallback nc = (NameCallback) callbacks[i];
            nc.setName(username);
         } else if (callbacks[i] instanceof URLCallback) {
            URLCallback uc = (URLCallback) callbacks[i];
            uc.setURL(url);
         } else if (callbacks[i] instanceof PasswordCallback) {
            PasswordCallback pc = (PasswordCallback) callbacks[i];
            pc.setPassword(password.toCharArray( ));
         } else {
            throw new UnsupportedCallbackException(
            callbacks[i], "Unrecognized Callback");
         }
      }
   }
}

The final piece of the puzzle is the JAAS login module. Earlier, we saw how the JAAS configuration file enabled us to set up our client to use WebLogic's login module for username-password authentication, the UsernamePasswordLoginModule. This Login-Module class expects our callback handler to deal with username and password callbacks, and optionally, the URL callback as well. The login( ) method provides the entry point for the JAAS framework into the LoginModule. It uses the user's credentials to authenticate the client with WebLogic and, if successful, returns an authenticated Subject populated with the appropriate principals.

We could have easily constructed our own login module and modified the configuration file to reference this module. The login( ) method is the most important within the LoginModule implementation class because this method is responsible for performing the actual authentication. Typically, it must use the configured callback handler to retrieve the username, password, and URL. It must then create an Environment object populated with this data, and invoke the authenticate( ) method on WebLogic's Authenticate class to execute the login and generate an authenticated Subject populated with required principals. The following code shows how to accomplish this authentication:

weblogic.jndi.Environment env = new weblogic.jndi.Environment( );
env.setProviderUrl(url);
env.setSecurityPrincipal(username);
env.setSecurityCredentials(password);
weblogic.security.auth.Authenticate.authenticate(env, subject);

In general, WebLogic's login module should be sufficient for most purposes; it is unlikely that you will need to provide your own LoginModule implementation.

Creating a Custom Authentication Provider

Developing your own security providers is a relatively specialized task—it is necessary only if WebLogic's default providers are insufficient. The majority of custom providers tend to change the default authentication or identity assertion mechanisms. The following sections provide an example of each of these. We recommend that you read WebLogic's well-documented security provider API to understand the life cycle of each provider if you intend to create your own. This information is supplied on the official web sit. BEA's dev2dev web site also contains a number of example providers.

MBeans

WebLogic's provider architecture is MBean-based (see Chapter 20)—if you are going to write a new provider, it has to have a corresponding MBean implementation. WebLogic provides tools for creating the necessary MBean deployment files and implementations. At runtime, the MBean representing your provider will be used to create an instance of your provider implementation—the MBean is, in a sense, a factory for the provider implementation that you will have to supply. This, in turn, will use the MBean to read its configuration information. Any provider MBean must extend the appropriate base MBean type, supplied with WebLogic. To facilitate in the creation of these peripheral classes, WebLogic provides a few utilities. They are based around an MBean Definition File (MDF), an XML file that describes your MBean implementation. Example 17-5 lists such an XML file.

Example 17-5. A simple MDF (MyAuthentication.xml) for an authentication provider

<?xml version="1.0" ?>
<!DOCTYPE MBeanType SYSTEM "commo.dtd">
<MbeanType Name = "MyAuthenticator" DisplayName = "MyAuthenticator"
  Package = "com.oreilly.wlguide.security.iap"
  Extends = "weblogic.management.security.authentication.Authenticator"
  PersistPolicy = "OnUpdate">
<MbeanAttribute Name = "ProviderClassName" Type = "java.lang.String"
  Writeable = "false"
  Default =
  "&quot;com.oreilly.wlguide.security.iap.MyAuthenticationProviderImpl&quot;"
  />
<MbeanAttribute Name = "Description" Type = "java.lang.String"
  Writeable = "false"
  Default = "&quot;O'Reilly Authentication Provider&quot;"
  />
<MBeanAttribute Name = "Version" Type = "java.lang.String"
  Writeable = "false" Default = "&quot;1.0&quot;"
  />
</MBeanType>

If you are to implement your own authentication provider, copy Example 17-5 verbatim, changing only the lines that have been highlighted (unless you want to add additional attributes). The ProviderClassName MBean attribute is the most important. This indicates the class name that represents the custom provider that we will have to implement, in this case, com.oreilly.wlguide.security.iap.MyAuthentication-ProviderImpl.

Once you have this file, you can use the MbeanMaker utility to take the MDF file and generate the MBean and stubs:

java -DcreateStubs="true" weblogic.management.commo.WebLogicMBeanMaker -MDF ..\
MyAuthentication.xml -files outdirectory

This generates a number of files, all of which can be ignored unless you implement custom operations and attributes. These are all placed in the outdirectory. All you need to do now is supply the actual implementation of the provider, recompile and repackage the whole lot, and deploy it.

Note that to use these utilities, you not only need to execute the setEnv script to prepare your environment, but you also have to add an extra JAR file to your classpath. This JAR file is located in WL_HOME/server/lib/mbeantypes/wlManagement.jar.

Authentication Provider

Now we turn to the authentication provider implementation itself. Recall that in the MDF, we specified the class name of a class that is to supply the authentication provider (see Example 17-6). This class must implement the weblogic.security.spi.AuthenticationProvider interface.

Example 17-6. An authentication provider implementation

package com.oreilly.wlguide.security.iap;

import java.util.HashMap;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
import weblogic.management.security.ProviderMBean;
import weblogic.security.provider.PrincipalValidatorImpl;
import weblogic.security.spi.*;

public final class MyAuthenticationProviderImpl implements AuthenticationProvider {
   private String description;
   private LoginModuleControlFlag controlFlag;

   // Our mapping of users to passwords/groups, instead of being in LDAP or in a
   // database, is represented by a HashMap of MyUserDetails objects..
   public class MyUserDetails {
      String pw;
      String group;
      // We use this to represent the user's groups and passwords
      public MyUserDetails(String pw, String group) {
         this.pw=pw; this.group = group;
      }  
      public String getPassword( ) {return pw;}
      public String getGroup( ) {return group;}
   }

   // This is our database
   private HashMap userGroupMapping = null;
   
   public void initialize(ProviderMBean mbean, SecurityServices services) {
      MyAuthenticatorMBean myMBean = (MyAuthenticatorMBean)mbean;
      description = myMBean.getDescription( ) + "\n" + myMBean.getVersion( );
      System.err.println("#In realm:" + myMBean.getRealm( ).wls_getDisplayName( ));
      // We would typically use the realm name to find the database
      // we want to use for authentication. Here, we just create one.
      userGroupMapping = new HashMap( );
      userGroupMapping.put("a", new MyUserDetails("passworda", "g1"));
      userGroupMapping.put("b", new MyUserDetails("passwordb", "g2"));
      userGroupMapping.put("system", new MyUserDetails("12341234",
                           "Administrators"));
      
	  String flag = myMBean.getControlFlag( );
      if (flag.equalsIgnoreCase("REQUIRED")) {
         controlFlag = LoginModuleControlFlag.REQUIRED;
      } else if (flag.equalsIgnoreCase("OPTIONAL")) {
         controlFlag = LoginModuleControlFlag.OPTIONAL;
      } else if (flag.equalsIgnoreCase("REQUISITE")) {
         controlFlag = LoginModuleControlFlag.REQUISITE;
      } else if (flag.equalsIgnoreCase("SUFFICIENT")) {
         controlFlag = LoginModuleControlFlag.SUFFICIENT;
      } else {
         throw new IllegalArgumentException("Invalid control flag " + flag);
      }
   }

   public AppConfigurationEntry getLoginModuleConfiguration( ) {
      HashMap options = new HashMap( );
      options.put("usermap", userGroupMapping);
      return new AppConfigurationEntry(
           "com.oreilly.wlguide.security.provider.MyLoginModuleImpl",
           controlFlag, options
      );
   }
   public String getDescription( ) {
      return description;
   }
   public PrincipalValidator getPrincipalValidator( ) {
      return new PrincipalValidatorImpl( );
   }
   public AppConfigurationEntry getAssertionModuleConfiguration( ) {
      return null;
   }
   public IdentityAsserter getIdentityAsserter( ) {
      return null;
   }
   public void shutdown( ) {}
   }

The class simply provides a way of getting to the various modules, most of which are optional. For example, we do not provide an identity asserter, so we just return null. Likewise, we simply return WebLogic's default principal validator implementation as the principal validator. All of the action happens in the initialize( ) and getLoginModuleConfiguration( ) methods.

Typically, the job of the initialize( ) method is to set up the resources necessary to perform any authentication. As you can see from the implementation, it has direct access to the MBean—so, if you added extra attributes or operations to the MBean, they can be used here. In our case, we simply set up a local database in the form of a hashmap. It then stores the control flag that is used in the module configuration. This eventually will get passed to the login module.

The getLoginModuleConfiguration( ) method returns an object that encapsulates the login module—the final piece of functionality that we have to implement. You can think of this method as playing the same role as the JAAS configuration file. It is instructing the provider as to which login module to use, on the server side this time. Note that the login module is wrapped in an object (AppConfigurationEntry) that also captures the control flag and options.

Login Module

We now need to supply a login module, as shown in Example 17-7. This follows the standard JAAS API.

Example 17-7. A login module

package com.oreilly.wlguide.security.provider;

import java.io.IOException;
import java.util.*;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.LoginModule;
import weblogic.security.principal.WLSGroupImpl;
import weblogic.security.principal.WLSUserImpl;

/**
 * This login module will be called by our Authentication Provider.
 * It assumes that the option, usermap, will be passed which contains
 * the map of users to passwords and groups.
 */
public class MyLoginModuleImpl implements LoginModule {
   private Subject subject;
   private CallbackHandler callbackHandler;
   private HashMap userMap;
   // Authentication status
   private boolean loginSucceeded;
   private boolean principalsInSubject;
   private Vector principalsBeforeCommit = new Vector( );
   
   public void initialize(Subject subject, CallbackHandler callbackHandler,
   Map sharedState, Map options) {
      this.subject = subject;
      this.callbackHandler = callbackHandler;
      // Fetch user/password map that should be set by the authenticator
      userMap = (HashMap) options.get("usermap");
   }
   
   /* Called once after initialize to try and log the person in */
   public boolean login( ) throws LoginException {
      // First thing we do is create an array of callbacks so that
      // we can get the data from the user
      Callback[] callbacks;
      callbacks = new Callback[2];
      callbacks[0] = new NameCallback("username: ");
      callbacks[1] = new PasswordCallback("password: ", false);
      try {
         callbackHandler.handle(callbacks);
      } catch (IOException eio) {
         throw new LoginException(eio.toString( ));
      } catch (UnsupportedCallbackException eu) {
         throw new LoginException(eu.toString( ));
      }
   
      String username = ((NameCallback) callbacks[0]).getName( );
      char [] pw = ((PasswordCallback) callbacks[1]).getPassword( );
      String password = new String(pw);
      if (username.length( ) > 0) {
         if (!userMap.containsKey(username))
            throw new FailedLoginException("Authentication Failed: Could not find user:
" + username);
         String realPassword = ((MyAuthenticationProviderImpl.MyUserDetails) userMap.
get(username)).getPassword( );
         if (realPassword == null || !realPassword.equals(password))
            throw new FailedLoginException("Authentication Failed: Password incorrect
for user" + username);
      } else {
         // No Username, so anonymous access is being attempted
      }
      loginSucceeded = true;
      // We collect some principals that we would like to add to the user
      // once this is committed.
      // First, we add his username itself
      principalsBeforeCommit.add(new WLSUserImpl(username));
      // Now we add his group
      principalsBeforeCommit.add(new WLSGroupImpl(((MyAuthenticationProviderImpl.
MyUserDetails)userMap.get(username)).getGroup( )));
      return loginSucceeded;
   }

   public boolean commit( ) throws LoginException {
      if (loginSucceeded) {
         subject.getPrincipals().removeAll(principalsBeforeCommit);
         principalsInSubject = true;
         return true;
      } else {
         return false;
      }
   }

   public boolean abort( ) throws LoginException {
      if (principalsInSubject) {
         subject.getPrincipals( ).removeAll(principalsBeforeCommit);
         principalsInSubject = false;
      }
      return true;
   }

   public boolean logout( ) throws LoginException {
      return true;
   }
}

Although long, this is pretty straightforward. Let's go through it. The initialize( ) method has direct access to the options that were configured in the provider. As we put our database in the options, this method provides the ideal place to extract the database. The rest of the action occurs in a combination of the login( ), commit( ), and abort( ) methods. If a login succeeds, and the control flags of the login modules are such that the entire login is to commit, the commit( ) method will be called— otherwise, the abort( ) method will be called. These methods simply ensure that the principals that should be associated with the subject are placed into the subject. The login( ) method does all of the work. First, it sets up a number of callbacks—we need the username and password. Note that the actual callback implementation is going to be handled by WebLogic. For example, WebLogic prompts you for the system user credentials when you try and boot a WebLogic server or access the Administration Console. It is the data from these callbacks that eventually will be supplied to the login method. After handling the callbacks, we extract the username and password of the user and locate it in our database. If successful, we make a list of principals that we want to associate with the user, storing these in the variable principalsBeforeCommit. The principals are added to the subject only if WebLogic calls the commit( ) method.

Deploying the Provider

Once you have created the authentication provider and login module, you can package these together with the generated stub and MBI files. To do this, execute the following command:

java weblogic.management.commo.WebLogicMBeanMaker -MJF myAuth.jar -files .

You are now ready to deploy your new provider. Copy myAuth.jar to the WL_HOME/server/lib/mbeantypes directory, and then reboot the server. Note that all custom providers have to be located in this directory. Start up the Administration Console and navigate to the Security/myrealm/Providers/Authentication node. In the list of available authenticators and identity asserters, you should find an option for "Configure a new My Authenticator." Selecting this option and clicking Create will configure the authenticator. On the following tab, you will notice that you can change the control flag. If you change this to something such as requisite, make sure that your database has a user in the Administrators group. If not, you won't even be able to boot the server! Use the OPTIONAL flag during development to avoid these problems.

Creating an Identity Assertion Provider

Imagine that you have some external system—say, a Java client or perhaps even an external web server—that authenticates a user, and you now want this user to participate in actions involving WebLogic. Furthermore, you don't want WebLogic to reauthenticate the user. Rather, you want to use some token generated by the external system to be used as an automatic WebLogic login. This is fairly typical of many single sign-on scenarios. The key to implementing this is to use an Identity Assertion Provider. Let's look at how you can implement such a scenario.

We are going to take as an example an external Java client that has presumably performed some user authentication, and who now needs to transfer this identity to WebLogic in order to access a protected web application. First of all, let's configure the web application to use identity assertion. Do this by setting the login-config to use a CLIENT-CERT authorization method. As this is standard J2EE, you will need to create a web.xml file with something such as the following in it:

<security-constraint>
<!-- web resource collection omitted -->
<auth-constraint>
<description>nyse</description>
<role-name>mysecrole</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>myrealm</realm-name>
</login-config>
<security-role>
<role-name>mysecrole</role-name>
</security-role>>

Now let's imagine we have a client (written in whatever language you wish) that has already performed some user authentication and now needs to access one of the protected web pages—say, http://10.0.10.10:8001/index.jsp. The following client is such an example:

URL url = new URL("http://10.0.10.10:8001/index.jsp");
URLConnection connection = url.openConnection( );
BufferedReader in = new BufferedReader(new InputStreamReader(
                                       connection.getInputStream( )));
// Read the input stream
in.close( );

If you simply run this program, you can expect an IOException when you try and access the input stream. This will be the 401 HTTP error code indicating that you are not authorized. We are going to get around this by making the client supply a token, and then configuring an Identity Assertion Provider to accept this token and authorize the user. Identity Assertion Providers can automatically take advantage of request cookies or headers. If WebLogic finds, for example, a header property with the same name as a token (we will see in a moment how to configure the identity provider with token names), it assumes that the content of the header property is the value of the token. The token we will use is a simple string that we are going to send in the HTTP request header when we create the connection to the server. To this end, modify the preceding code to read as follows:

URL url = new URL(urlAddr);
URLConnection connection = url.openConnection( );
connection.setRequestProperty("MyToken",encodedToken);
// Everything as before

The name of the request property, MyToken in our example, is significant. This is interpreted as the type of the token, which we will see later. A small caveat here is that WebLogic always expects incoming tokens to be Base 64-encoded. You can do this by using the utility class weblogic.utils.encoders.BASE64Encoder. So, to create an encoded token, you can write something such as this:

String token = "jon";
BASE64Encoder encoder = new BASE64Encoder( );
String encodedToken = encoder.encodeBuffer(token.getBytes( ));

The text that you place in the token can be anything you please, as long as your Identity Assertion Provider can read it. In our example, we will use a simple string, which we take to represent the authenticated user.

Note: WebLogic 8.1 allows you to configure the Identity Assertion Provider to use tokens that aren't encoded, in which case you won't need to use an encoder.

All that's left now is to create an Identity Assertion Provider. The MBean definition file used in our example is given in Example 17-8 in full.

Example 17-8. MyA.xml, the MDF file for the assertion provider

<?xml version="1.0" ?>
<!DOCTYPE MBeanType SYSTEM "commo.dtd">
<MBeanType Name = "MyA" DisplayName = "MyA"
  Package = "com.oreilly.wlguide.security.iap"
  Extends = "weblogic.management.security.authentication.IdentityAsserter"
  PersistPolicy = "OnUpdate"
>
<MBeanAttribute Name = "ProviderClassName" Type = "java.lang.String"
  Writeable = "false"
  Default = "&quot;com.oreilly.wlguide.security.iap.MyAProviderImpl&quot;"
/>
<MBeanAttribute Name = "Description" Type = "java.lang.String"
  Writeable = "false" Default = "&quot;My Identity Assertion Provider&quot;"
/>
<MBeanAttribute Name = "Version" Type = "java.lang.String"
  Writeable = "false" Default = "&quot;1.0&quot;"
/>
<MBeanAttribute Name = "SupportedTypes" Type = "java.lang.String[]"
  Writeable = "false" Default = "new String[] { &quot;MyToken&quot; }"
/>
<MBeanAttribute Name = "ActiveTypes" Type = "java.lang.String[]"
  Default = "new String[] { &quot;MyToken&quot; }"
/>
</MBeanType>

Note the following things:

You can create the support files as usual. Here we place all the output in the directory out:

java -DcreateStubs="true" weblogic.management.commo.WebLogicMBeanMaker -MDF MyA.xml
-files out

Finally, you need to create the provider class com.oreilly.wlguide.security.iap.MyAProviderImpl, which was referred to in the ProviderClassName attribute.

Example 17-9 lists this class in its entirety.

Example 17-9. The provider implementation

package com.oreilly.wlguide.security.iap;

import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.AppConfigurationEntry;
import weblogic.management.security.ProviderMBean;
import weblogic.security.spi.*;

public final class MyAProviderImpl
             implements AuthenticationProvider, IdentityAsserter {
   private String description; // holds our description which we derive from MBean
        attributes
   
   public void initialize(ProviderMBean mbean, SecurityServices services) {
      MyAMBean myMBean = (MyAMBean)mbean;
      description = myMBean.getDescription( ) + "\n" + myMBean.getVersion( );
   }

   public CallbackHandler assertIdentity(String type, Object token)
            throws IdentityAssertionException {
      if (type.equals("MyToken")) {
         byte[] tokenRaw = (byte[])token;
         String username = new String(tokenRaw);
         return new SimpleSampleCallbackHandlerImpl(username,null,null);
      } else
         throw new IdentityAssertionException("Strange Token!");
      }
      public String getDescription( ) {
         return description;
      }
      public void shutdown( ) {
      }
      public IdentityAsserter getIdentityAsserter( ) {
         return this; // this object is the identity asserter
      }
      public AppConfigurationEntry getLoginModuleConfiguration( ) {
         return null; // we are not an authenticator
      }
      public AppConfigurationEntry getAssertionModuleConfiguration( ) {
         return null; // we are not an authenticator
      }
      public PrincipalValidator getPrincipalValidator( ) {
         return null; // we are not an authenticator
      }
   }
}

The most important methods are initialize( ) and assertIdentity( ). The initialize() method simply extracts some information from the MBean representing the provider and uses it to create the description. The assertIdentity( ) method is given two parameters, the type of the token and the token itself. We simply check that the token type is correct and map the token to the username. You could conceivably do a lot more here, such as validate the authenticity of the token for stronger security. The method must return a standard JAAS callback handler, which eventually will be invoked to extract the username (that is, only the NameCallback will be used). We use the callback handler that we defined in Example 17-4. Note that the identity asserter could have been an authenticator too, in which case it could populate the subject with usernames and groups belonging to the user. Because we are doing pure identity assertion, the corresponding methods simply return null.

Place this file and the callback handler in the out directory, and then issue the following command to create a packaged provider:

java weblogic.management.commo.WebLogicMBeanMaker -MJF myIAP.jar -files out

Copy this to the WL_HOME/server/lib/mbeantypes directory, and then reboot the server. Start up the Administration Console and navigate to the Security/myrealm Providers/Authentication node. In the list of available authenticators and identity asserters, you should find an option for "Configure a new MyA...". Selecting this option and clicking Create will configure the identity asserter. On the following tab you will notice that the support token type is set to MyToken and the active token to MyToken too. You will now have to reboot the server for this change to take effect.

If you rerun the client application, you will find that you will no longer get an unauthorized warning (assuming that jon is in the permission group mysecrole, which was granted access to the web resource). To further illustrate the point, you can try accessing a servlet or JSP page in this way, which has a call to request. getUserPrincipal( ). You will find that this call returns jon as you would expect.

So, here is a summary of what happens, as was illustrated in Figure 17-2:

  1. The client attempts to access a protected web page. The web container notes that the client does not have any security credentials and that the web application implements identity assertion, so it fires up the Identity Assertion Providers, passing in the appropriate request parameters.

  2. The Identity Asserter grabs the username directly from the incoming token and returns it in the form of a callback handler.

  3. Any login modules that you have configured for the security realm then fire, using the callback handler to fetch the username. So, for example, the Default Authenticator will fire and log in the user. However, because it knows that the data comes from the Identity Asserter, it will not require a password. As a result, the user is logged in and can now access the web application.

Avinash Chugh, Jon Mountjoy presently works as Senior Development Manager for a firm that produces software for the regulated industries (finance, energy, pharmaceutics).


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.