Preparing for 1.0 - remaining API changes

First of all, Happy New Year to everyone! I hope everyone's holidays were fun and relaxing.

To kick off 2008, I want to surface the topic of finalizing the JSecurity API and moving to 1.0 as soon as possible.

JSecurity has been around for over 3 years now, and we still haven't released 0.2 final yet! This is just silly, but it isn't because of lazy developers (Ohloh JSecurity statistics corroborate this). It is just that the majority of the code base is stable, having been in production for quite a while. We haven't had the need to make new releases much.

Yes, we've added things and changed things over the last year especially to make things much simpler and easier to understand, but the functionality hasn't changed a whole lot. This tells me the project is quite stable and should be reflected as such in our release name.

So, to free ourselves from any potential stigma of a 'new and unstable framework' that comes with pre-1.0/low revision numbers, I really want to push for 1.0 as soon as possible. But, here's what I would like to see happen before we get to that point:

1) I want to change the signature of the Authenticator interface from this:

public SecurityContext authenticate( AuthenticationToken authenticationToken )
throws AuthenticationException;
to this:

public AuthenticationInfo authenticate( AuthenticationToken authenticationToken )
throws AuthenticationException;

Reason:

A SecurityContext is an overarching concept that concerns authorization and session management after authentication. In the interest of Separation of Concerns, I don't think an Authenticator should ever be responsible for constructing a SecurityContext. Instead, I feel that this is the job of the SecurityManager, since the SM has knowledge of the Authenticator, Authorizer, etc.

I also don't feel the Authenticator should be responsible for binding the created SecurityContext to the application for further use (as it currently does). I feel this too is the responsibility of the SecurityManager implementation, since binding is inherently app-environment specific (web, standalone, etc) and that a respective SecurityManager implementation would handle this logic depending on the deployment environment.

So in the end, the SecurityManager should call Authenticator#authenticate, get back an AuthenticationInfo object, and then create and bind a newly created SecurityContext to the application.

This is better imo because after this change, nothing in the Authentication hierarchy would know anything about a SecurityContext - very clean separation of concerns.

2) This means that the SecurityManager interface would need a new method to support authentication that returns a SecurityContext. I propose that method be called:

SecurityManager#login( AuthenticationToken ) throws AuthenticationException

The default implementation would be very simple:
- call the delegate Authenticator#authenticate method which returns the AuthenticationInfo
- construct the SecurityContext based on the return value of authenticate
- bind the newly created SecurityContext to the application
- return the SecurityContext to the caller

3) Rename the AuthenticationInfo interface to 'Subject' or 'Account', whichever people prefer. The current interface encapsulates what really amounts to data associated with a specific account or subject _after_ an authentication attempt has been completed. The current name is somewhat vague and misleading, and the rename just 'feels right', better reflecting is nature as well as being more readable. This change would in turn alter the APIs specified in #1 above.

I would also rename SimpleAuthenticationInfo to CompositeSubject or CompositeAccount to better reflect that it is an aggregator of subject/account-related data from multiple realms.

(We could just create this new Subject or Account interface, move the methods from AuthenticationInfo into it, and then just make AuthenticationInfo an immediate sub-interface for backwards-compatibility for now. I'd like to remove it entirely if possible though. What we choose will most likely be based on Peter's feedback as to how it might affect the Grails community).

4) Remove or deprecate org.jsecurity.SecurityUtils. All this class does is currently get the SecurityContext from a ThreadLocal, which is inherently app-environment specific - i.e. it is not usuable in any environment other than maybe a web container or maybe standalone server. Plus, it is based on statics which drives me nuts.

Instead, I propose that the SecurityManager implementation has the same method:

public SecurityContext getSecurityContext()

The SecurityManager implementation, which is inherently app-environment specific, would know where to pull it from - thread local, static memory, Spring application context in a gui environment, etc.

JSecurity users would always then interact with the application's SecurityManager instance to get the current calling context's SecurityContext. This is much better OO in my opinion.

Removing or deprecating it would mostly likely depend on how much it would affect current users (e.g. Grails plugin users?).

5) Remove Realm#getName(). I don't think this is necessary. I executed a "find usages" in Idea, and its not used very often at all and is only really used in logging statements. It would be just as easy to print out the realm.getClass().getName() in these cases. I.e. JSecurity doesn't _require_ this method to function, so I don't think we should impose its use on framework users.

I'm looking forward to any feedback. I feel if I can get these done asap and do a little more testing w/ SessionManagement, 1.0 is right around the corner.

Thanks,

Les

Hi Les, That all looks fine

Hi Les,
 
That all looks fine to me - as long as the API doesn't change again after this! :) On a point-by-point basis:
 
  1. I'm generally in favour of separation of concerns, so I'll give this the thumbs up.
  2. login() method sounds good.
  3. I would prefer 'Subject' simply because I have come across it more often (I think that's the term JAAS uses). I would also drop AuthenticationInfo completely - the Grails plugin does not expose it to users, so only those who know what they are doing are likely to be affected.
  4. I have to ask: is it worth having a SecurityContext at all if you have to access it via the security manager? In other words, once you have the security manager, don't you already have access to everything that the security context provides? Anyway, SecurityUtils is used in various places by the plugin, but it's probably worth deprecating it - I think there are better approaches that I should take with the plugin.
  5. I certainly have no problem with this. The plugin simply uses the class name anyway for this property.
Finally, what sort of security model is suited to the new isAuthorized()-related methods and classes? I'm wondering whether I should support this feature from the plugin.
 
Cheers,
 
Peter 

Hi Peter,

Hi Peter,

Thanks for the feedback :)

And yes, I agree with you 100% about no more backwards incompatible changes to the API. Once 1.0 final is out, we will be strictly adhering to the Apache APR versioning guidelines to ensure compatibility between minor and point releases. They have a nice table on the homepage showing examples of what would work, but in summary:

An upgrade from a version to any future non-Major version (1.X.Y to 2.0 is considered a Major version upgrade) is guaranteed to be backwards compatible. However, any downgrade from any existing version to a lesser version is not guaranteed to be compatible (except point releases - e.g. X.Y.6 is guaranteed to be downgradable to X.Y.3).

After the changes I described in the first post are complete, I feel extremely comfortable that the API will be resilient enough to stand up to the test of time until 2.0 comes out, but hopefully that is a long way away.

Moving along in response to your question about #4:

You're right, if you have a handle to the SecurityManager, you can perform any operation that a SecurityContext can do. But, their scopes are different to support different programming approaches:

  • A SecurityManager can perform security related operations for any subject/user or session in the system. It requires an identifying piece of information for every method call, such as a subject's principal, a session id, etc. It is the 'macro' or system-wide scope for security operations.
  • A SecurityContext performs those same security operations but for only a single subject/user as well as encapsulates access to a session for only that user. It is the 'micro' or user-only scope for security operations.

The utility in either is mostly dependent upon what type of programming one is doing: application programming or framework programming.

I've found that most application programmers are almost exclusively of the mindset "I want to check x, y, or z, or do session operations for the currently executing user" (purpose of a SecurityContext), and not a mindset of "I'm managing user ids and session ids manually, so, for any given id, check x, y or z or do session operation for session id N" (purpose of the SecurityManager). Framework programmers on the other hand find the latter convenient when needed.

Indeed much of JSecurity's web packages are framework code that do exactly the latter - they go through most of the work in managing user ids and session ids via Http specific mechanisms (cookies, http sessions, etc) and then construct a SecurityContext for the application programmer to use during the request so the application programmer doesn't have to worry about all those environment-specific details.

And finally about the isAuthorized methods:

I'm really glad you asked about these - I found some things which I had overlooked and am not currently too comfortable with:

The isAuthorized methods take in an AuthorizedAction, which is often specific to the application. It basically allows any realm to check if a given user is authorized to 'do something' on or with the AuthorizedAction.

Currently this interface doesn't have any methods, which I don't really like. Personally marker interfaces (like Serializable) do nothing for me in an API. There is type-safety associated with them, but I find that of little value in a framework. Plus we have a supports( AuthorizedAction ) method that can check if the object passed in is actually supported for an authorization check, which sort of defeats the purpose of an interface (i.e. if it implements the interface, it must be supported - otherwise, just use raw java.lang.Object parameters and keep the supports() method).

Anyway, the idea of this interface and the related isAuthorized methods was to allow Realms and ModularAuthorizers to check to see if a user can 'do something'. That 'something' is whatever the application wants it to be. JSecurity has implementations of it that represent MethodInvocations from certain AOP frameworks (Spring & JBoss currently), which allows one to use JDK 1.5 annotations - the annotated Method is the AuthorizedAction, and JSecurity can then do role/permission checks on the current SecurityContext to see if that method can be invoked.

The ModularRealmAuthorizer is the primary support for checking AuthorizedActions. It checks against two sets of things: the application's configured Realms and any configured AuthorizationModules. The current implementation always enables a RoleAnnotationModule and PermissionAnnotationModule in JDK 1.5+ environments to support annotations.

The biggest problem I have with these things is that they don't feel congruent. i.e. there is an Authorizer interface (which Realm extends) and an AuthorizationModule, both of which process AuthorizedActions but do them slightly differently - the former returns a boolean if an action is authorized, the latter returns a Vote indicating if the action should be allowed or not.

I think they need to be consolidated into a single interface to eliminate this ambiguity. I think the Realms are much more 'modular' now then they used to be early on in the framework, potentially eliminating the need for a lone ModularAuthorizer interface.

It is my opinion that the Authorization process needs to work exactly like the Authentication process: The single Authenticator used by the SecurityManager consults a collection of configured Realms to do its work (via a ModularAuthenticationStrategy). I think Authorization should work the same way: A single Authorizer used by the SecurityManager consults a collection of configured Realms (or ModularAuthorizers, or whatever we call it, as long as there are no duplicates), and that would be done via a ModularAuthorizationStrategy.

Now I have to figure out how to do that cleanly :) Any ideas and suggestions by anyone are always welcome!

Well, I've rambled on enough. Let me know if you have any other questions or comments.

Regards,

Les