Oracle® Application Server Containers for J2EE Security Guide
10g Release 2 (10.1.2) B14013-02 |
|
Previous |
Next |
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.
This chapter discusses how to write and install a LoginModule
to be used with the OracleAS JAAS Provider. The following topics are covered:
Notes:
|
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.
See Also:
|
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 during 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 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:
To turn on JAAS provider debug logging, set the system property jazn.debug.log.enable
to true
during Java Virtual Machine (JVM) startup.
You do this by modifying the <java-options>
settings for your OC4J instance. In Oracle Application Server, you normally manage these settings using Oracle Enterprise Manager 10g Application Server Control Console, which stores these settings in opmn.xml
. From the home page of your OC4J instance, do the following:
Choose Administration.
From the Administration page, choose Server Properties.
From the Server Properties page, under Command Line Options, enter the option for debug logging (as described immediately following).
When running OC4J outside Oracle Application Server, you set this property using JVM command-line options. For instance, you might start standalone OC4J with a command line such as:
java -Djazn.debug.log.enable=true -jar oc4j.jar
Or you might start the Admintool shell in debug mode with the command:
java -Djazn.debug.log.enable=true -jar jazn.jar -shell
When you turn on debug logging, the OracleAS JAAS Provider logs debugging output to the console. Under Oracle Application Server, debugging output is captured in the ORACLE_HOME
/opmn/logs
directory.
To access an EJB using a custom LoginModule
, you must:
Grant login
permission to the user JDOE_ENDUSER
in the OC4J home
instance jazn-data.xml
file.
Grant namespace-access to the user JDOE_ENDUSER
in orion-application.xml
.
To grant login permission to JDOE_ENDUSER
, use the JAZN Admintool, as in the following example:
java -jar jazn.jar -grantperm login -user JDOE_ENDUSER oracle.j2ee.server.rmi.RMIPermission
To grant namespace access to JDOE_ENDUSER
, edit orion-application.xml
to add a <namespace-access>
element like the following:
<namespace-access> <read-access> <namespace-resource root=""> <security-role-mapping> <user name="JDOE_ENDUSER" /> </security-role-mapping> </namespace-resource> </read-access> </namespace-access>
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
setting must be one of required
, requisite
, sufficient
or optional
, as specified in javax.security.auth.login.Configuration
. See Table 10-1.
Table 10-1 Login Module Control Flags
Flag | Meaning |
---|---|
|
The |
|
The |
|
The |
|
The |
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
, specify:
java -jar jazn.jar -addloginmodule myapp MyLoginModule required debug=true
To delete MyLoginModule
from myapp
, specify:
java -jar jazn.jar -remloginmodule myapp MyLoginModule
Admintool shell:
JAZN:> addloginmodule myapp MyLoginModule required debug=true JAZN: remloginmodule myapp MyLoginModule
Use the JAZN Admintool to list login modules.
java -jar jazn.jar -listloginmodules [application_name [login_module_class]]
The -listloginmodules
option displays all login modules 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 login modules for the application myapp
, specify:
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 ORACLE_HOME
/j2ee/
instance_name
/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 the OC4J 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-level)
orion-application.xml
(application-specific)
See Also:
|
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, as discussed in "Integrating Custom JAAS Login Modules". |
All login module configuration information is stored in the OC4J home
instance jazn-data.xml
file. This file is usually located in the ORACLE_HOME
/j2ee/home
/config
directory.
Note: Thehome instance jazn-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.
|
You must modify the home
instance jazn-data.xml
file 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 element 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 onRealmLoginModule .
|
This element 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.
Note: These policies must be set in thehome instance jazn-data.xml file, in the ORACLE_HOME /j2ee/home /config directory.
|
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>oracle.j2ee.server.rmi.RMIPermission</class> <name>login</name> </permission> </permissions> </grant> </jazn-policy>
This fragment grants the permission oracle.j2ee.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.
|
See Also:
|
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 the web.xml
or ejb-jar.xml
file.
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 login modules:
Note: This section discusses only elements relevant to security. |
See Also:
|
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
instance instead of basing checks on the users and roles defined in the application specific jazn-data.xml
file.
The LoginModule
instance (or instances) 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
instance 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 the web.xml
or ejb-jar.xml
file, 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 deployment descriptors (web.xml
or ejb-jar.xml
), you must configure security role mapping.
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.
Package your LoginModule
classes as part of your application EAR file. For Web applications, include the classes under the WEB-INF/classes
.
To deploy your LoginModule
in the home
instance jazn-data.xml
file:
Register your application login module within the <application>
element of the jazn-data.xml
file.
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 oracle.j2ee.rmi.RMIPermission
to admin
.
<grant> <grantee> <principals> <principal> <class>oracle.security.jazn.samples.SampleUser</class> <name>admin</name> </principal> </principals> </grantee> <permissions> <permission> <class>oracle.j2ee.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:
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 // element 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; public class SamplePrincipal implements Principal { private String _name = null; public 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; } }