I am attempting to implement java security in an application that is architected as
follows:
We have a web tier running in a jboss instance. This web tier has no datasources available
to it. The only way it has access to data is through a business tier, a set of
applications running in another jboss instance. For simplicity, we will refer to the two
instances as web-1 and service-1.
To complicate things further in our system, we require three bits of information to
uniquely identify a user. The loginid, the password and the domain. In our system, we
enforce unique loginids in each domain, but they all live relatively harmoniously in the
same database tables. The loginid in and of itself will not necessarily be unique, but
coupled with the domain, we could have a primary key, although the real primary key of the
user table is a userid column, which is what we REALLY want as our principal once we get
into the business tier. (We allow our customers to choose their own loginid, but the
userid is system generated)
Ok, so having read Chapter 8 of the admin guide 23 times, I understand that there will be
two parts of the JAAS login process.
I first attempted to use the ClientLoginModule that JBoss provides to marshall the login
credentials from the web layer to the service layer. We are instantiating the LoginContext
with "other", which is mapped to ClientLoginModule in login-config.xml. (The
service layer ejbs are all part of the same security-domain, which we'll refer to as
"jwdomain")
Here is my login-config.xml snippet:
| <application-policy name="jwdomain">
| <authentication>
| <login-module code = "my.package.MyLoginModule" flag =
"required">
| <module-option
name="unauthenticatedIdentity">guest</module-option>
| <module-option
name="password-stacking">useFirstPass</module-option>
| <module-option
name="multi-threaded">true</module-option>
| </login-module>
| <login-module code="org.jboss.security.ClientLoginModule"
flag="required">
| <module-option
name="password-stacking">useFirstPass</module-option>
| <module-option
name="multi-threaded">true</module-option>
| </login-module>
| </authentication>
| </application-policy>
| <application-policy name = "other">
| <authentication>
| <login-module code="org.jboss.security.ClientLoginModule"
flag="required">
| <module-option
name="unauthenticatedIdentity">guest</module-option>
| <module-option
name="multi-threaded">true</module-option>
| </login-module>
| </authentication>
| </application-policy>
|
And a snippet from my login action in the web layer:
(Since unauthenticated I can't yet look up the userid, I get creative and construct a
unique "username" from loginid, a string of text that we hope nobody ever uses
as a loginid or domain, and the actual domain strings.)
ProcessLoginAction.java snippet:
| CallbackHandler cbHandler = new UsernamePasswordHandler
| (new
StringBuffer(loginid).append("UNIQUETOKEN").append(domain).toString(),
password.toCharArray());
| LoginContext loginContext;
| try {
| loginContext = new LoginContext("other",cbHandler);
| loginContext.login();
| } catch (LoginException e) {
| throw new ApplicationException(e);
| }
|
Stepping through the executing code, things seem to progress nicely. My ProcessLoginAction
class instantiates the "other" LoginContext, and by the time the commit() method
is called, we can see that the Subject is populated with Principal and Credentials, as
expected from what is passed in via the CallbackHandler.
After the LoginContext.login() succeeds, we invoke the create() methot on an EJB that has
a) been set to unchecked method-permission and b) security-domain of "jwdomain"
(See jboss.xml and ejb-jar.xml snippets below) and the server side of the JAAS login
proceeds as planned, as we step through MyLoginModule. By the time we've gone through
the login() and commit() method, we've done actual authentication on the credentials
marshalled to the service login invocation via the client side login. So far, so good.
MyLoginModule.java:
| public class MyLoginModule extends AbstractServerLoginModule {
| private Principal csIdentity;
| public boolean login() throws LoginException {
| String userid = null;
| Callback[] callbacks = new Callback[2];
| callbacks[0] = new NameCallback("loginid+domain should have been
provided by web layer");
| callbacks[1] = new PasswordCallback("Password should have been
provided by web layer",false);
| String ltd = null;
| String password = null;
| try {
| callbackHandler.handle(callbacks);
| ltd = ((NameCallback)callbacks[0]).getName();
| if (((PasswordCallback)callbacks[1]).getPassword() != null) {
| password = new
String(((PasswordCallback)callbacks[1]).getPassword());
| }
| } catch (Exception e) {
| throw new LoginException(e.getMessage());
| }
| try {
| if (ltd != null && password != null) {
| String loginid = null;
| String domain = null;
| String token = "UNIQUETOKEN";
| String[] loginiddomain = ltd.split(token);
| if ( loginiddomain.length == 2 ) {
| loginid = loginiddomain[0];
| domain = loginiddomain[1];
| }
| userid = getUserid(loginid,domain);
| if(userid == null ) {
| throw new LoginException("Unable to determine unique
identifier for "+loginid+"/"+assoc+".");
| }
| String expectedPassword = getPassword(userid);
| if(password != null && !password.equals(expectedPassword))
{
| throw new LoginException("Passwords do not
match.");
| }
| csIdentity = createIdentity(userid);
| loginOk = true;
| }
| } catch (Exception e) {
| throw new LoginException(e.getMessage());
| }
| return super.login();
| }
| protected String getUserid(String loginid, String association) {
| //implementation details not needed
| }
| protected String getPassword(String userid) {
| //implementation details not needed
| }
| public Principal getIdentity() {
| return csIdentity;
| }
| protected Group[] getRoleSets() throws LoginException {
| Group roles = new SimpleGroup("Roles");
| //actual implementation to follow after successful test
| Principal role = new SimplePrincipal("foorole");
| roles.addMember(csIdentity);
| roles.addMember(role);
| Group cp = new SimpleGroup("CallerPrincipal");
| cp.addMember(csIdentity);
| return new Group[]{roles,cp};
| }
| }
|
The server side login() succeeds, stepping through the above code shows success, but the
subsequent EJB create() fails with:
| Insufficient method permissions, principal=myLoginidUNIQUETOKENmyDomain,
ejbName=AccountManager, method=create, interface=HOME, requiredRoles=[],
principalRoles=null
|
Now this failure happened while the ProcessLoginAction attempted to lookup and call a
method on an EJB in the service layer, secured by the "jwdomain" as follows:
| <jboss>
| <security-domain>java:/jaas/jwdomain</security-domain>
| <enterprise-beans>
| <session>
| <ejb-name>AccountManager</ejb-name>
| <jndi-name>AccountManagerManager</jndi-name>
| <local-jndi-name>AccountManagerManagerLocal</local-jndi-name>
| </session>
| ...
| </jboss>
|
But ALL of my session bean methods are flagged as unchecked! (for now)
| <ejb-jar >
|
| <description>AccountManager to retrieve info from service
tier</description>
| <enterprise-beans>
| <session >
| <ejb-name>AccountManager</ejb-name>
| ...
| <session-type>Stateless</session-type>
| <transaction-type>Bean</transaction-type>
| <security-identity>
| <use-caller-identity />
| </security-identity>
| </session>
| ...
| <method-permission >
| <unchecked/>
| <method >
| <ejb-name>AccountManager</ejb-name>
| <method-intf>Remote</method-intf>
| <method-name>create</method-name>
| <method-params>
| </method-params>
| </method>
| </method-permission>
| ...
| </ejb-jar >
|
So, I thought that maybe there's something awry between requiredRoles=[] and
principalRoles=null.... that maybe AT LEAST ONE role is expected. So I try to force this
to happen... I try it two ways:
The first way, I changed my login-config.xml as follows:
| <application-policy name = "other">
| <authentication>
| <login-module
code="org.jboss.security.auth.spi.RunAsLoginModule"
flag="required">
| <module-option
name="roleName">foo-admin</module-option>
| </login-module>
| <login-module code="org.jboss.security.ClientLoginModule"
flag="required">
| <module-option
name="unauthenticatedIdentity">guest</module-option>
| <module-option
name="multi-threaded">true</module-option>
| </login-module>
| </authentication>
| </application-policy>
|
That didn't help me at all. So, I changed ClientLoginModule to a custom client side
login module that extends UsernamePasswordLoginModule so that I could specify a dummy role
via the getRoleSets() method, overriding the validatePasswords() method as well so that it
would always return true (after all, it's a client side login module that doesn't
_really_ provide any meaningful authentication... the sole purpose of this login module is
to marshall the Subject Principal and credentials to the service layer where the real
business is transacted. So, I implement a getRoleSets() method as follows:
| public class MyClientLoginModule extends UsernamePasswordLoginModule {
| String password = "";
| public String getUsersPassword() { return password; }
| protected boolean validatePassword(String inputPassword, String
expectedPassword) { return true; }
| protected Group[] getRoleSets() throws LoginException {
| Principal identity = super.getIdentity();
| Group roles = new SimpleGroup("Roles");
| Principal role = new SimplePrincipal("foorole");
| roles.addMember(identity);
| roles.addMember(role);
| return new Group[]{roles};
| }
| }
|
Straight up, right? When I execute this code, it all _looks_ good, but still no dice, same
error. Maybe I'm missing something in the way
Subject<->Principal<->Principals<->Groups<->Roles all _really_
work together. It seems that if I can just get a bogus role associated with my client side
Subject then maybe the EJB create() would work?
Then again, maybe I'm just barking up the wrong tree, because what I really want for
my principal is not that bastardized concatenization of loginid, unique token and domain,
but the actual system primary key user id, that which the server side MyLoginModule
identity identifies once a LoginModule is executing in a place where the customer provided
loginid/domain/password can be properly authenticated and turned into a proper Principal
(rather CallerPrincipal, what I am ultimately after)
If anything glaringly obvious jumps off the page, please enlgihten me, as I simply cannot
see the forest through the trees.
Thanks in advance,
-david
View the original post :
http://www.jboss.com/index.html?module=bb&op=viewtopic&p=3972803#...
Reply to the post :
http://www.jboss.com/index.html?module=bb&op=posting&mode=reply&a...