PROLOGUE:
When deploying enterprise application like EJB on some enterprise-based container, we also need to bound lots of important busniss resource with our application. Important means critical, security can be one of most important aspect what we should think about when we building our application. There are so many resouce and articles about javaEE security, this piece belongs to this kinds eithier, the theme center on EJB Security, but the understanding of java security API is the starting, below is Outline of this piece:
Java Security API Introducing;
JAAS Overview;
Authentication in EJB;
Authorization in EJB.
Java Security API Introducing
Since Java 2 SDK v1.4, Java security API has became a sophisticated and robust part of SDK, here are some much more related to this article:
(1) javax.security.auth.Subject
Subjects may potentially have multiple identities. Each identity is represented as a Principal within the Subject;A Subject may also own security-related attributes, which are referred to as credentials, there are teo kinds of credentials, one is sensitive sredentials which need to be protected, are stored in a private credentials set, the other is public credentials which need to be shared, are stored in a public credentials set. javax.security.auth.Subject has supplyed Methods to interact with its Principal, both private credentials and public credentials, as following:
subject.getPrincipals(); subject.getPrivateCredentials(); subject.getPublicCredentials();
(2) java.security.Principal
Principal is a Interface, its represents the abstract notion of a principal, which can be used to represent any entity, such as an individual, a corporation, and a login id. The interface has a getName() method which simply bind a name to the Subject. When we pass the authentication we should add our principal to Subject's principal set.
(3) javax.resource.spi.security.PasswordCredential
PasswordCredential acts as a holder for username and password, its treated as a private credentials in our Part 2 example, its also need to add Subjects private credential set when was been authenticated successfully.
(4) javax.security.auth.spi.LoginModule
LoginModule is a interface implemented by authentication technology providers, which provide a particular type of authentication use 5 methods:
abort() commit() initialize(Subject subject, CallbackHandler callbackHandler, Map<String,?> sharedState, Map<String,?> options) login() logout()
this method usually invoked by LoginContext which responsible for reading the Configuration and instantiating the appropriate LoginModules. Each LoginModule is initialized with a Subject, a CallbackHandler, shared LoginModule state, and LoginModule-specific options. The Subject represents the Subject currently being authenticated and is updated with relevant Credentials if authentication succeeds.
(5) javax.security.auth.callback.Callback
Implementations of this interface are passed to a CallbackHandler, allowing underlying security services the ability to interact with a calling application to retrieve specific authentication data such as usernames and passwords, in our Part 2 example we use Callback's Implementing Classes NameCallback and PasswordCallback.
(6) javax.security.auth.callback.CallbackHandler
A interface owns a method handle(Callback[] callbacks) which handle the passed callbacks
(7) javax.security.auth.login.LoginContext
The LoginContext class describes the basic methods used to authenticate Subjects and provides a way to develop an application independent of the underlying authentication technology. A Configuration specifies the authentication technology, or LoginModule, to be used with a particular application, auth.conf can be found under JBOSS_HOME\client which is the configuration file define the JBOSS login module.
JAAS Authentication Overview
Actually, the above class and interface I have listed that enables you to authenticate users in java, we all known that JAAS has a flexible design, its surprisingly complicated for us to learn. I planed to complete this section combine a authentication example and a figure to make it easier to understand.
The following figure show the basic JAAS authentication procedure:
1. The client instantiates a new login context. This is a container-provided class. It’s responsible for coordinating the authentication process.
LoginContext loginContext = new LoginContext("HomeTestClient", new MyCallbackHandler());
Instantiate a new LoginContext object with a name which refered to a loginModule in Configuration file, and MyCallbackHandler which create by our use to handle the passed callbacks.
2. The login context retrieves a configuration object. The configuration object knows about the type of authentication you want to achieve by consulting a configuration file that lists the login modules. the configuration object is underlying Object, we do not need think about more, but configuration file we should supply and need to set to JVM when the JVM starting, the following is our exampless configuration file:
default { home.jaas.HomeLoginModule required debug=false; }; HomeTestClient { home.jaas.HomeLoginModule required debug=false; };
configuration file names auth.conf, under resurce folder, we should set to JVM like below code depicted:
File authFile = new File("resource/auth.conf"); System.setProperty("java.security.auth.login.config", "file:///" + authFile.getAbsolutePath());
3. The login context asks the configuration object for the list of authentication
mechanisms to use (such as password-based and certificate-based). in our example we use password-based authentication mechanism.
4. The configuration object returns a list of authentication mechanisms. Each one is called a login module. A login module knows how to contact a specific security provider and authenticate in some proprietary way.
5. The login context instantiates your login modules. You can have many login modules if you want to authenticate across several different security providers. In the example we’re about to show, we will use only one login module, and it will know how to authenticate using a user name and password combination to a Java EE server.
6. The login context initializes the login modules. login context invoke loginModule's initialize() method:
public void initialize( Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { logger.debug("initialize()"); this.subject = subject; this.callbackHandler = callbackHandler; }
7. The client code tries to log in by calling the login() method on the login context.
loginContext.login();
8. The login context delegates the login() call to the login modules, since only the login modules know how to perform the actual authentication. which in this section loginModule's login method has been invoked. the main code:
callbackHandler.handle(callbacks); if(username.equals("admin") && "admin".equals(new String(password))) { return true; }
9. The login modules (written by you) authenticate you using a proprietary means. In the example we’re about to show, the user name and password login module will perform a local authentication only that admin and admin has been passed it succeeds. After the login succeeds, the login module is told to commit(). It can also abort() if the login process fails. the hightlight code in commit method:
PasswordCredential pc = new PasswordCredential(username, password); subject.getPrivateCredentials().add(pc);
the above code shows that our private credentials has been add to Subject's private credentials set.
10. Authentication information is kept in a subject. You can use this subject to perform secure operations or just have it sit in the context.
11. Your client code calls remote operations (such as in an EJB component) and the logged-in security context is automatically propagated along with the method call.
I will give the complete code of this example:
HomeTestClient.java
public class HomeTestClient { public static Logger logger = Logger.getLogger(HomeTestClient.class); public static void main(String[] args) { logger.debug("JAAS Example Starting..."); File authFile = new File("resource/auth.conf"); logger.debug("Client-side Configuration File Location: " + authFile.getAbsolutePath()); System.setProperty("java.security.auth.login.config", "file:///" + authFile.getAbsolutePath()); logger.debug("JVM Property 'java.security.auth.login.config' Set Completed"); try { LoginContext loginContext = new LoginContext("HomeTestClient", new MyCallbackHandler()); loginContext.login(); } catch (LoginException e) { e.printStackTrace(); } System.out.println("Invoke the method secuely"); } }
HomeLoginModule.java
public class HomeLoginModule implements LoginModule { private static Logger logger = Logger.getLogger(HomeLoginModule.class); private Subject subject; private CallbackHandler callbackHandler; private String username; private char[] password; public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { logger.debug("initialize()"); this.subject = subject; this.callbackHandler = callbackHandler; } public boolean login() throws LoginException { logger.debug("login()"); if(callbackHandler == null) { throw new LoginException("Error: No callbackHandler available to collect authentication information"); } Callback[] callbacks = new Callback[2]; callbacks[0] = new NameCallback("Username: "); callbacks[1] = new PasswordCallback("Password: ", false); try { callbackHandler.handle(callbacks); username = ((NameCallback) callbacks[0]).getName(); if(username == null) { throw new LoginException("No user specified"); } char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword(); if(tmpPassword == null) { tmpPassword = new char[0]; } password = new char[tmpPassword.length]; System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length); ((PasswordCallback) callbacks[1]).clearPassword(); } catch (IOException e) { throw new LoginException(e.getMessage()); } catch (UnsupportedCallbackException e) { throw new LoginException("Error: No callback available to authentication data: " + e.getMessage()); } //authentication if(username.equals("admin") && "admin".equals(new String(password))) { return true; } return false; } public boolean commit() throws LoginException { logger.debug("commit()"); PasswordCredential pc = new PasswordCredential(username, password); subject.getPrivateCredentials().add(pc); username = null; password = null; return true; } public boolean abort() throws LoginException { logger.debug("abort()"); return true; } public boolean logout() throws LoginException { logger.debug("logout()"); username = null; password = null; return true; } }
MyCallbackHandler.java
public class MyCallbackHandler implements CallbackHandler { private static Logger logger = Logger.getLogger(MyCallbackHandler.class); public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { logger.debug("handle method invoked, handle callbacks."); for(int i = 0 ; i < callbacks.length ; i++) { if(callbacks[i] instanceof NameCallback) { NameCallback nc = (NameCallback) callbacks[i]; System.out.print(nc.getPrompt()); String name = (new BufferedReader(new InputStreamReader(System.in))).readLine(); nc.setName(name); logger.debug("NameCallback Set Name: " + name); } else if(callbacks[i] instanceof PasswordCallback) { PasswordCallback pc = (PasswordCallback) callbacks[i]; System.out.print(pc.getPrompt()); String pwLine = (new BufferedReader(new InputStreamReader(System.in))).readLine(); pc.setPassword(pwLine.toCharArray()); logger.debug("PasswordCallback Set Password: " + pwLine); } else { logger.error("Unrecognized Callback"); throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback"); } } } }
Run the HomeTestClient class, input admin when username prompt came out, and same input admin when password prompt came out, the authentication successful result will show:
and also in log file you will find the authentication procedure log recorded by log4j:
2011:05:21 - 19:30:32 [home.jaas.HomeTestClient] DEBUG - JAAS Example Starting... 2011:05:21 - 19:30:32 [home.jaas.HomeTestClient] DEBUG - Client-side Configuration File Location: D:\dev-workspaces\home\com.home.ear.v2\resource\auth.conf 2011:05:21 - 19:30:32 [home.jaas.HomeTestClient] DEBUG - JVM Property 'java.security.auth.login.config' Set Completed 2011:05:21 - 19:30:32 [home.jaas.HomeLoginModule] DEBUG - initialize() 2011:05:21 - 19:30:32 [home.jaas.HomeLoginModule] DEBUG - login() 2011:05:21 - 19:30:32 [home.jaas.MyCallbackHandler] DEBUG - handle method invoked, handle callbacks. 2011:05:21 - 19:30:36 [home.jaas.MyCallbackHandler] DEBUG - NameCallback Set Name: admin 2011:05:21 - 19:30:38 [home.jaas.MyCallbackHandler] DEBUG - PasswordCallback Set Password: admin 2011:05:21 - 19:30:38 [home.jaas.HomeLoginModule] DEBUG - commit()
Understanding EJB Security
There are two security measures that clients must pass when you add security to an EJB system: authentication and authorization. Authentication must be performed before any EJB method is called. Authorization, on the other hand, occurs at the beginning of each EJB method call.