Oracle® Application Server Containers for J2EE Security Guide
10g Release 2 (10.1.2) Part No. B14013-01 |
|
![]() Previous |
![]() Next |
This chapter discusses how to write and install a LoginModule
to be used with the OracleAS JAAS Provider. This chapter contains the following sections:
Note: Because the JAAS specification does not cover user management, when you configure your application to use a customLoginModule , the use of the UserManager API within your application is effectively disabled. The J2EE API, however, will continue to function within your application.
|
OC4J supplies a JAAS pluggable authentication framework that conforms to the JAAS standard. With this framework, an application server and any underlying authentication services remain independent from each other, and alternative authentication services can be plugged in through JAAS login modules without requiring modifications to the application server or application code.
Possible types of JAAS login modules include the following:
Principal mapping JAAS module
Credential mapping JAAS module
Kerberos JAAS module
A JAAS login module can be developed by the customer or supplied by the provider of the EIS and resource adapter. A login module must implement the standard JAAS LoginModule
interface, which includes methods to initialize the login module, authenticate a given subject (referred to as phase 1), commit or abort an authentication (referred to as phase 2), and sign off a subject.
OC4J passes an initiating-principal subject to a JAAS login module. Specifically, this is a Subject
instance containing a Principal
instance that represents the OC4J user (initiating principal), along with any public certificates. OC4J can pass a null Subject
instance if there is no authenticated user (that is, if the OC4J user is anonymous). The initiating-principal subject is passed to the initialize()
method of the JAAS login module.
The login()
method of the JAAS login module (for phase 1 authentication) must, based on the initiating principal, find the corresponding resource principal and create a new credential (such as a PasswordCredential
instance) for the resource principal. The resource principal and the credential are then added to the initiating-principal Subject
instance through the JAAS login module commit()
method. The resource credential is passed to the createManagedConnection()
method of the ManagedConnectionFactory
implementation that is provided by the resource adapter.
If a null Subject
is passed, the JAAS login module is responsible for creating a new Subject
instance containing the resource principal and the appropriate credential.
Before working with custom LoginModule
s, you must verify that you are using the XML-based provider; the LDAP-based provider does not support custom LoginModules. After you have verified the provider, turn on dynamic role mapping; see Configuring Dynamic Role Mapping for details.
When you turn on dynamic role mapping, the OracleAS JAAS Provider performs authorization checks based on the current Subject
instead of using static configurations. By default, dynamic role mapping is turned off, which means that the OracleAS JAAS Provider uses static configurations as the basis for authorization checks.
To turn on dynamic role mapping:
Openthe bootstrap jazn.xml
file, ORACLE_HOME
/j2ee/
instance
/config/jazn.xml
, in a text editor and go to the <jazn>
element within the file.
Search for a <property name="role.mapping.dynamic">
sub-element within the <jazn>
element.
If the sub-element exists, change the value to true
or false
; if the sub-element does not exist, create one. In either case, you should have a sub-element that looks like:
<jazn provider="XML" location="./jazn-data.xml"> <property name="role.mapping.dynamic" value="true" /> </jazn>
Note: Do not edit any<jazn> properties except as specified in this documentation.
|
Restart OC4J.
A custom JAAS LoginModule
may be desirable when Oracle Identity Management is not available and users and roles are defined in an external repository. You can configure a LoginModule
using the XML-based provider type. When you create a custom LoginModule
, the following preliminary questions need to be considered.
Development. Do you want to take advantage of J2EE security constraints?
Development, packaging, and deployment. Are you using the login modules that come with J2SE 1.4? Or are you deploying custom or third-party login modules?
Note: Custom login modules are supported only with the XML-based Provider. |
You can use an any JAAS-compliant LoginModule
within the OC4J framework. For general information on developing LoginModules, see the Sun JAAS documentation at http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASLMDevGuide.html
When developing a LoginModule
, you must consider several important issues:
Each of these is discussed in detail in its own section.
When you associate a custom LoginModule
with an application, the Subject and the principals it contains are used as the sole basis for all authorization tasks, including evaluating J2EE security constraints. To ensure that all relevant principals are considered during authorization, the LoginModule
must add the relevant principals, including all roles and groups that the authenticated user participates in, to the Subject durring the commit phase of the JAAS authentication process.
The OracleAS JAAS Provider custom LoginModule
framework supports the J2EE declarative security model. This means that Subject-based authorization enforces the J2EE security constraints declared in an application's deployment descriptors (web.xml
and ejb-jar.xml
, for example). We encourage you to take advantage of the J2EE security model whenever possible.
The OracleAS JAAS Provider supports the standard javax.security.auth.callback
name (NameCallback) and password (PasswordCallback) callbacks.
When debugging your secure application, bear the following issues in mind:
If you set the JVM system property jazn.debug.log.enable
to true
, the OracleAS JAAS Provider logs debugging output to the console. Under Oracle Application Server, debugging output is captured in the directory ORACLE_HOME
/opmn/logs
.
You use the JAZN Admintool to add and remove login modules. For basic information on running the JAZN Admintool, see "Admintool Overview" .
java -jar jazn.jar -addloginmodule
application_name
login_module_name control_flag
[optionname=value ...
] java -jar jazn.jar -remloginmodule
application_name
login_module_name
The -addloginmodule
option configures a new LoginModule
for the named application.
The control_flag
must be one of required
, requisite
, sufficient
or optional
, as specified in javax.security.auth.login.Configuration
. See Table 10-1.
Table 10-1 LoginModule Control Flags
Flag | Meaning |
---|---|
Required
|
The LoginModule must succeed. Whether or not it succeeds, authentication proceeds down the LoginModule list.
|
Requisite
|
The LoginModule must succeed. If it succeeds, authentication continues down the LoginModule list. If it fails, control immediately returns to the application (authentication does not continue down the LoginModule list).
|
Sufficient
|
The LoginModule is not required to succeed. If it succeeds, control immediately returns to the application and authentication does not proceed down the LoginModule list. If it fails, authentication continues down the LoginModule list.
|
Optional
|
The LoginModule is not required to succeed. Whether or not it succeeds, authentication proceeds down the LoginModule list.
|
If the LoginModule
accepts its own options, you specify each option and its value as an optionname=value
pair. Each LoginModule
has its own individual set of options.
For instance, to add MyLoginModule
to the application myapp
as a required module with debug
set to true
, type:
java -jar jazn.jar -addloginmodule myapp MyLoginModule required debug=true
To delete MyLoginModule
from myapp
, type:
java -jar jazn.jar -remloginmodule myapp MyLoginModule
Admintool shell:
JAZN:> addloginmodule myapp MyLoginModule required debug=true JAZN: remloginmodule myapp MyLoginModule
You use the JAZN Admintool to list login modules. For basic information on running the JAZN Admintool, see "Admintool Overview" .
java -jar jazn.jar -listloginmodules
[application_name [login_module_class]]
The -listloginmodules
option displays all LoginModule
s either in the specified application_name
, or, if no application_name
is specified, in all applications. Specifying login_module_class
, after application_name
displays information on only the specified class within the application.
For example, to display all LoginModule
s for the application myapp
, type:
java -jar jazn.jar -listloginmodules myapp
Admintool shell:
JAZN:> listloginmodules myapp
If you are using one or more of the default login modules provided with J2SE 1.3 and 1.4 (such as the J2SE1.4 com.sun.security.auth.module.Krb5LoginModule
), then no additional configuration is needed. The OracleAS JAAS Provider can locate the default login modules.
If you are deploying your application with a custom login module, then you must deploy the login module and configure the OracleAS JAAS Provider properly so that the module can be found at runtime.
The following options are available when packaging and deploying your custom login modules:
The remainder of this section discusses these options in greater detail.
If you deploy your login modules as standard extensions, the OracleAS JAAS Provider will be able to find them. No additional configuration is necessary. Deploying login modules as standard extensions allows multiple applications to share the deployed login modules.
For example, one way to deploy your login modules as standard extensions is to deploy them to the $J2EE_HOME
/lib/ext
directory.
If your login module is used only by a single J2EE application rather than shared among multiple applications, then you can simply package your login module as part of your application, and the OracleAS JAAS Provider will be able to find it. No additional configuration is necessary.
If a later application needs the same LoginModule
, you must repackage the login module and any relevant classes with the new application.
If you want to enable multiple applications to share the same LoginModule
but you cannot deploy the LoginModule
as an extension, then you can consider using the OC4J classloading mechanism.
The OracleAS JAAS Provider is integrated with OC4J's classloading architecture. If you configure your application so that the deployed custom login modules are part of your application classpath
, then the OracleAS JAAS Provider can locate them.
One way to accomplish this is using the <library>
element in either of the following files:
application.xml
(instance-specific)
orion-application.xml
(application-specific)
See Also: The Oracle Application Server Containers for J2EE Services Guide for more information about the<library> element.
|
You modify the following files to configure your application to take advantage of custom login modules:
This section gives details on the configuration files.
Note: You must choose the XML-based provider when using custom login modules. See "Integrating Custom JAAS LoginModules". |
All login module configuration information is stored in the bootstrap jazn-data.xml
file. This file is usually located in the directory ORACLE_HOME
/j2ee/
instance_name
/config
.
Note: The bootstrapjazn-data.xml must contain accounts for "admin " and "anonymous ". Do not remove these accounts; if you do, the administrative functions of the OracleAS JAAS Provider will not work.
|
Because the bootstrap jazn-data.xml
file is instance-specific, you must modify it whenever you deploy your application into a new OC4J instance. You edit this file using the JAZN Admintool.
The following sections discuss these XML elements:
This tag contains information that associates applications with login modules.
Example 10-1 Example jazn-loginconfig element
<jazn-loginconfig> <application> <name>sampleLM</name> <login-modules> <login-module> <class>oracle.security.jazn.samples.SampleLoginModule</class> <control-flag>required</control-flag> </login-module> </login-modules> </application> </jazn-loginconfig>
This fragment associates the application sampleLM
with the login module sample.SampleLoginModule
.
Note: Do not remove login configuration information on RealmLoginModule. |
This tag contains information that associates grantees with permissions. If you want to make your fat client accessible to an EJB, you must explicitly make the permissions available. When you deploy a custom LoginModule
in OC4J, you normally use custom principal classes or types. To grant or revoke permissions to these types, use the JAZN Admintool.
Example 10-2 Example jazn-policy element
<jazn-policy> <grant> <grantee> <principals> <principal> <class>oracle.security.jazn.samples.SampleUser</class> <name>admin</name> </principal> </principals> </grantee> <permissions> <permission> <class>com.evermind.server.rmi.RMIPermission</class> <name>login</name> </permission> </permissions> </grant> </jazn-policy>
This fragment grants the permission com.evermind.server.rmi.RMIPermission
with target name login
to the principal with class oracle.security.jazn.samples.SampleUser
and name admin
.
Note: Oracle recommends that you manage the contents ofjazn-data.xml using the JAZN Admintool.
|
For more information about the JAZN Admintool, see Chapter 8, "Configuring the XML-Based Provider".
To take advantage of J2EE declarative security in your application, you must configure the appropriate security constraints, either using your IDE or by hand-editing either web.xml
or ejb-jar.xml
. For details on these files, see the J2EE standard documentation at http://java.sun.com/j2ee
.
This file is a container-specific deployment descriptor that is generated for each application deployed in OC4J. The following elements are relevant to writing custom LoginModules
:
Note: This section discusses only elements relevant to security. For a full discussion of this file, see the Oracle Application Server Containers for J2EE User's Guide. |
The following <jazn>
property is specific to LoginModule
configuration:
role.mapping.dynamic
This property, when set to true
, instructs the OracleAS JAAS Provider to base authorization checks on the authenticated Subject
instead of basing checks on the users and roles defined in the application specific jazn-data.xml
.
The LoginModule
instance(s) must ensure that the appropriate principals (users, roles, or groups) are associated with the Subject
instance during the commit phase of the authentication process, in order for the principals to be taken into consideration during the authorization process. This association of principals to the Subject
is typically implemented using the standard JAAS API.
<jazn provider="XML" location="./jazn-data.xml"> <property name="role.mapping.dynamic" value="true" /> </jazn>
When you set J2EE security constraints in web.xml
or ejb-jar.xml
, you must configure security role mapping.The optional <security-role-mapping>
element describes static security-role mapping information. If you set J2EE security constraints in your application's deployment descriptors (web.xml
or ejb-jar.xml
), you must configure security role mapping.
For details, see "Authenticating and Authorizing EJB Applications" .
Each <connector-factory>
element in oc4j-ra.xml
can specify a different JAAS login module, as in the following example. This also shows <config-property>
setup to connect to a database through Oracle JDBC.
<connector-factory connector-name="myBlackbox" location="eis/myEIS1"> <config-property name="connectionURL" value="jdbc:oracle:thin:@localhost:5521/myservice" /> <security-config use="jaas-module"> <jaas-module> <jaas-application-name>JAASModuleDemo</jaas-application-name> </jaas-module> </security-config> </connector-factory>
Developing a simple LoginModule
follows the standard development, packaging, and deployment cycle. The following sections discuss each step in the cycle.
Develop a JAAS-compliant LoginModule
according to the JAAS SPI (see the Javadoc for javax.security.auth.spi.LoginModule
for more information).
Package your LoginModule
classes as part of your application's EAR file. For Web applications, include the classes under the WEB-INF/classes
.
To deploy your LoginModule
in the bootstrap jazn-data.xml
file:
Register your application's login module within the <application>
tag.
The following entry registers the login module oracle.security.jazn.samples.SampleLoginModule
to be used for authenticating users accessing the sampleLM
application.
<application> <name>sampleLM</name> <login-modules> <login-module> <class>oracle.security.jazn.samples.SampleLoginModule</class> <control-flag>required</control-flag> <options> <option> <name>debug</name> <value>true</value> </option> </options> </login-module> </login-modules> </application>
Optional. Grant relevant permissions to your users and roles.
For example, if the principal admin
needs EJB access, then you must grant the permission com.evermind.rmi.RMIPermission
to admin
.
<grant> <grantee> <principals> <principal> <class>oracle.security.jazn.samples.SampleUser</class> <name>admin</name> </principal> </principals> </grantee> <permissions> <permission> <class>com.evermind.server.rmi.RMIPermission</class> <name>login</name> </permission> </permissions> </grant>
To deploy your LoginModule
in the application-specific orion-application.xml
file:
Set the <jazn>
property role.mapping.dynamic
to true
:
<jazn provider="XML" location="./jazn-data.xml" > <property name="role.mapping.dynamic" value="true" /> </jazn>
Create appropriate <security-role-mapping>
entries.
<security-role-mapping name="sr_developer"> <user name="developer" /> </security-role-mapping> <security-role-mapping name="sr_manager"> <group name="managers" /> </security-role-mapping>
This section gives source code for a simple custom LoginModule
to be used by the CallerInfo
example; you can find the complete source code for the revised example by searching the Oracle Technology Network at http://www.oracle.com/technology/index.html
.
Example 10-3 SampleLoginModule.java
package oracle.security.jazn.samples; import java.util.Set; import java.util.Iterator; import java.util.Map; import java.security.Principal; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.Callback; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; public class SampleLoginModule implements LoginModule { // initial state protected Subject _subject; protected CallbackHandler _callbackHandler; protected Map _sharedState; protected Map _options; // configuration options protected boolean _debug; // the authentication status protected boolean _succeeded; protected boolean _commitSucceeded; // username and password protected String _name; protected char[] _password; protected Principal[] _authPrincipals; /** * Initialize this <code>LoginModule</code>. * <p/> * <p/> * * @param subject the <code>Subject</code> to be authenticated. <p> * @param callbackHandler a <code>CallbackHandler</code> for communicating * with the end user (prompting for usernames and * passwords, for example). <p> * @param sharedState shared <code>LoginModule</code> state. <p> * @param options options specified in the login * <code>Configuration</code> for this particular * <code>LoginModule</code>. */ public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { this._subject = subject; this._callbackHandler = callbackHandler; this._sharedState = sharedState; this._options = options; // initialize any configured options _debug = "true".equalsIgnoreCase((String) _options.get("debug")); if (debug()) { printConfiguration(this); } } final public boolean debug() { return _debug; } protected Principal[] getAuthPrincipals() { return _authPrincipals; } /** * Authenticate the user by prompting for a username and password. * <p/> * <p/> * * @return true if the authentication succeeded, or false if this * <code>LoginModule</code> should be ignored. * @throws FailedLoginException if the authentication fails. <p> * @throws LoginException if this <code>LoginModule</code> * is unable to perform the authentication. */ public boolean login() throws LoginException { if (debug()) System.out.println("\t\t[SampleLoginModule] login"); if (_callbackHandler == null) throw new LoginException("Error: no CallbackHandler available " + "to garner authentication information from the user"); // Setup default callback handlers. Callback[] callbacks = new Callback[] { new NameCallback("Username: "), new PasswordCallback("Password: ", false) }; try { _callbackHandler.handle(callbacks); } catch (Exception e) { _succeeded = false; throw new LoginException(e.getMessage()); } String username = ((NameCallback)callbacks[0]).getName(); String password = new String(((PasswordCallback)callbacks[1]).getPassword()); if (debug()) { System.out.println("\t\t[SampleLoginModule] username : " + username); } // Authenticate the user. On successfull authentication add principals // to the Subject. The name of the principal is used for authorization by // OC4J by mapping it to the value of the name attribute of the group // tag in the security-role-mapping for the application. if(username.equals("developer") && password.equals("welcome")) { _succeeded = true; _name = "developer"; _password = password.toCharArray(); _authPrincipals = new SamplePrincipal[2]; //Adding username as principal to the subject _authPrincipals[0] = new SamplePrincipal("developer"); //Adding role developers to the subject _authPrincipals[1] = new SamplePrincipal("developers"); } if(username.equals("manager") && password.equals("welcome")) { _succeeded = true; _name = "manager"; _password = password.toCharArray(); _authPrincipals = new SamplePrincipal[3]; //Adding username as principal to the subject _authPrincipals[0] = new SamplePrincipal("manager"); //Adding roles developers and managers to the subject _authPrincipals[1] = new SamplePrincipal("developers"); _authPrincipals[2] = new SamplePrincipal("managers"); } ((PasswordCallback)callbacks[1]).clearPassword(); callbacks[0] = null; callbacks[1] = null; if (debug()) { System.out.println("\t\t[SampleLoginModule] success : " + _succeeded); } if (!_succeeded) throw new LoginException("Authentication failed: Password does not match"); return true; } /** * <p> This method is called if the LoginContext's * overall authentication succeeded * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules * succeeded). * <p/> * <p> If this LoginModule's own authentication attempt * succeeded (checked by retrieving the private state saved by the * <code>login</code> method), then this method associates a * <code>Principal</code> * with the <code>Subject</code> located in the * <code>LoginModule</code>. If this LoginModule's own * authentication attempted failed, then this method removes * any state that was originally saved. * <p/> * <p/> * * @return true if this LoginModule's own login and commit * attempts succeeded, or false otherwise. * @throws LoginException if the commit fails. */ public boolean commit() throws LoginException { try { if (_succeeded == false) { return false; } if (_subject.isReadOnly()) { throw new LoginException("Subject is ReadOnly"); } // add authenticated principals to the Subject if (getAuthPrincipals() != null) { for (int i = 0; i < getAuthPrincipals().length; i++) { if(!_subject.getPrincipals().contains(getAuthPrincipals()[i])) { _subject.getPrincipals().add(getAuthPrincipals()[i]); } } } // in any case, clean out state cleanup(); if (debug()) { printSubject(_subject); } _commitSucceeded = true; return true; } catch (Throwable t) { if (debug()) { System.out.println(t.getMessage()); t.printStackTrace(); } throw new LoginException(t.toString()); } } /** * <p> This method is called if the LoginContext's * overall authentication failed. * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules * did not succeed). * <p/> * <p> If this LoginModule's own authentication attempt * succeeded (checked by retrieving the private state saved by the * <code>login</code> and <code>commit</code> methods), * then this method cleans up any state that was originally saved. * <p/> * <p/> * * @return false if this LoginModule's own login and/or commit attempts * failed, and true otherwise. * @throws LoginException if the abort fails. */ public boolean abort() throws LoginException { if (debug()) { System.out.println("\t\t[SampleLoginModule] aborted authentication attempt."); } if (_succeeded == false) { cleanup(); return false; } else if (_succeeded == true && _commitSucceeded == false) { // login succeeded but overall authentication failed _succeeded = false; cleanup(); } else { // overall authentication succeeded and commit succeeded, // but someone else's commit failed logout(); } return true; } protected void cleanup() { _name = null; if (_password != null) { for (int i = 0; i < _password.length; i++) { _password[i] = ' '; } _password = null; } } protected void cleanupAll() { cleanup(); if (getAuthPrincipals() != null) { for (int i = 0; i < getAuthPrincipals().length; i++) { _subject.getPrincipals().remove(getAuthPrincipals()[i]); } } } /** * Logout the user. * <p/> * <p> This method removes the <code>Principal</code> * that was added by the <code>commit</code> method. * <p/> * <p/> * * @return true in all cases since this <code>LoginModule</code> * should not be ignored. * @throws LoginException if the logout fails. */ public boolean logout() throws LoginException { _succeeded = false; _commitSucceeded = false; cleanupAll(); return true; } // helper methods // protected static void printConfiguration(SampleLoginModule slm) { if (slm == null) { return; } System.out.println("\t\t[SampleLoginModule] configuration options:"); if (slm.debug()) { System.out.println("\t\t\tdebug = " + slm.debug()); } } protected static void printSet(Set s) { try { Iterator principalIterator = s.iterator(); while (principalIterator.hasNext()) { Principal p = (Principal) principalIterator.next(); System.out.println("\t\t\t" + p.toString()); } } catch (Throwable t) { } } protected static void printSubject(Subject subject) { try { if (subject == null) { return; } Set s = subject.getPrincipals(); if ((s != null) && (s.size() != 0)) { System.out.println("\t\t[SampleLoginModule] added the following Principals:"); printSet(s); } s = subject.getPublicCredentials(); if ((s != null) && (s.size() != 0)) { System.out.println("\t\t[SampleLoginModule] added the following Public Credentials:"); printSet(s); } } catch (Throwable t) { } } }
The Principal
that this LoginModule
uses is in Example 10-4.
Example 10-4 SamplePrincipal example
package oracle.security.jazn.samples; import java.security.Principal; class SamplePrincipal implements Principal { private String _name = null; SamplePrincipal(String name) { _name = name; } public boolean equals(Object another) { return ((SamplePrincipal)another).getName().equals(_name); } public String getName() { return _name; } public int hashCode() { return _name.hashCode(); } public String toString() { return "[SamplePrincipal] : " + _name; } }