Friday, May 9, 2014

JAAS Revisited

Now we will try to use JAAS to authenticate a user, assign a principal to him/her, and checking whether he/she is permitted to do an action. But before we get our hands dirty with code, there are several terms in JAAS that we need to understand.
  • Subject: it represents a user that we want to authenticate.
  • Principal: it represents a role. A subject may have many principals.
  • Permission: it represents a permission to do an action. A permission has actions and target objects, which we will see later in policy file.
  • Policy: it is a mapping of principal and permission; that is, which role is allowed to do what kind of actions.
We will start with creating the policy file.


As shown above, we have granted AuthPermission to do createLoginContext, doAs, doAsPrivileged, etc for all principals. Then, we granted nothing for UserPrincipal. We also granted FilePermission to read file input.txt for SysAdminPrincipal. Note that we gave a name for each principal; "user" for UserPrincipal and "sysadmin" for SysAdminPrincipal. It means that we have to give a name "sysadmin" for every SysAdminPrincipal object to allow a Subject that is given that principal to be granted all permissions under SysAdminPrincipal. If, for example, a Subject is given SysAdminPrincipal name "myadmin" then it will not be granted SysAdminPrincipal's permissions.

Next we will see UserPrincipal and SysAdminPrincipal class.



As we can see, all principals implements Principal interface. It has one method that returns its name. It is the name that has to be matched with the one on policy file, "sysadmin" for SysAdminPrincipal and "user" for UserPrincipal.

The policy file is loaded when our application starts. We specify the file name as VM arguments as shown below.


In he VM arguments above, we also set argument for login configuration file. It is a configuration file that tells java security manager which class is responsible for authenticating a Subject when he/she logs in.


In the configuration file above we defined a package/application named bim. This is more like a group of modules while it has one module inside named SimpleLoginModule. Instead of having only single module, we can also have as many modules as we need. Below is an example of a configuration file that has many modules in a package/application.


It has REQUISITE as its control flag. There are four control flags available:
  1. REQUIRED: It must return true from its login() method. But, system will continue proceed the next module in the same package/application regardless of its result.
  2. REQUISITE: It must return true from its login() method. System will not continue proceed the next module in the same package/application if its result is false.
  3. SUFFICIENT: It is not required to return true from its login() method, but if it is, system will not proceed the next module in the same package/application.
  4. OPTIONAL: It is not required to return true from its login() method. Whatever its result, system will continue proceed the next module in the same package/application.
One thing to note here is that returning true from login() method means returning Boolean.TRUE while returning false means throwing a LoginException not returning Boolean.FALSE. We will see this in an example below in SimpleLoginModule class.







SimpleLoginModule implements LoginModule interface. There are five methods that we need to implement.
  1. initialize(): this is the first method called when we authenticate a user. We can initialize our class variables here. initialize() has 4 arguments on its method header and three of them are useful for us. First is Subject, it is the Subject that is being logged in. Then CallbackHandler, it is an object that is responsible for gathering credentials (username & password). We will see this class later. Last is a Map of options. On the configuration file, we put an option isUsed="true". This option is available in the Map of options. Our SimpleLoginModule's initialize() method above initialized our private variables Subject and CallbackHandler for future usage.
  2. login(): this method is used to gather and validate username and password. If the validation succeeds, it returns true. Otherwise we should throw LoginException. In the login() method, we declared two callback objects, NameCallback and PasswordCallback. They are used to gather username and password credentials through callbackHandler.handle(callbacks) method. We will see how this is done later when we visit our callback handler class. After getting the credentials, we simply validated them. If the validation succeed, we returns true, otherwise we throw a FailedLoginException. Note that we didn't return false if the validation fails. If it returns true, then commit() method is called; but if it throws LoginException, then abort() method is called instead. Returning false (Boolean.FALSE) will not make the abort() method called.
  3. commit(): this method is called if login() method returns true. We can put principles to a Subject here. In our commit() method, we added principles to Subject since he/she has successfully logged in. If the username is "sysadmin", a SysAdminPrincipal is added to the Subject. If the username is "user", a UserPrincipal is added to the Subject.
  4. abort(): this method is called if login() method throws LoginException. We can remove all credentials (username, password) here. In this method we cleaned up all credentials.
  5. logout(): this method is called if we logout from JAAS. When a user logout, we clean up all credentials and remove all principals that were assigned when he/she logged in.
Next we will see the callback handler, SimpleCallbackHandler class.


Our SimpleCallbackHandler above implements CallbackHandler. We need to implement handle() method. In handle() we iterate callbacks array that was sent by SimpleLoginModule.login() method as we did before. During iteration, we populate NameCallback with username and PasswordCallback with password. Now one thing missing is how we get the username and password credentials to be sent to this SimpleCallbackHandler. This is where LoginContext plays its role.



To authenticate a user, first we create SimpleCallbackHandler object and pass the username and password. Then we create LoginContext object by passing package/application name from configuration file we created before and the SimpleCallbackHandler object. LoginContext.login() is used to log the user in by passing control to the SimpleLoginModule. If the authentication succeeds, we can obtain the Subject. If fails, LoginException is thrown.

Now that the user is authenticated, we can start checking if a user is authorized to do some action. We have granted permissions as we did in policy file before. These permissions will be used to check user authorization. Subject.doAsPrivileged is the method we need. In the code above we test if a user is allowed to read file input.txt. If he/she is allowed, it simply returns. Otherwise, it throws SecurityException.

Below is the result from running the code:


From the result we can see that the Subject "user" and "sysadmin" authentication succeeds. The "user" is not allowed to access the input.txt file since we didn't grant any permission to it while "sysadmin" can.

Below is the project structure:


0 comments:

 

©2009 Stay the Same | by TNB