Greg and Jan were kind enough to create a branch for me to play
around
with a JASPI (Java Authentication Service Provider Interface)
integration with jetty and its getting to a point where I'm
willing to
talk about it.
Code is at
https://svn.codehaus.org/jetty/jetty/branches/jetty-7-jaspi
JASPI attempts to provide a uniform framework for messaging
systems,
both client and server side, to plug in message
authentication. On
the
client you can add auth info to a request and validate auth
info
on a
response. On the server you can validate auth info on a
request
and add
auth info to a response. The auth code can conduct arbitrary
message
exchanges to negotiate what info is needed and transmit the
info.
I've
been working on the server side auth for jetty.
The actual spec jaspi interfaces are not 100% ideal for http
and
don't
allow stuff like lazy authentication for unsecured resources
so I've
come up with interfaces similar in spirit to the jaspi ones.
I've also tried to rework the implementation so it is more
friendly to
integration with other app servers with their own ideas about
security
frameworks such as geronimo and in particular make jacc
implementations
easier. I expect these changes will also simplify integration
with
e.g.
jboss and glassfish but I haven't seriously tried to verify
this.
Currently all the authentication code (replacing the
*Authenticator
classes) is implemented in terms of jaspi but I plan to
change this
soon
to use the jetty specific interfaces directly.
So.... lets follow a HttpServletRequest/Response pair on its
voyage
through the security system...
... it arrives at AbstractSecurityHandler.handle. This is a
template
method that runs through the following structure calling out to
subclasses and the authentication system:
1. calls checkUserDataPermissions(pathInContext, base_request,
base_response, constraintInfo). This checks the user data
constraints,
basically that the request arrived over the right kind of
connection
(http/https). Two obvious implementations of this are the
existing
jetty constraint based implementation or one based on JACC.
2. calls isAuthMandatory(base_request, base_response,
constraintInfo) to
determine if the request actually needs authentication. If
it does
not
we can often delay authentication until a method relying on
auth
results
is called (such as getUserPrincipal or isUserInRole). Again
this
can be
implemented using constraints or JACC.
3. packs the request, response, and authManditory into a
JettyMessageInfo holder object which can also pass various auth
info in
a map.
4. delegates the authentication to the jaspi-like
ServerAuthResult
authResult = serverAuthentication.validateRequest(messageInfo);
assuming we are not doing lazy auth, this will extract the
credentials
from the request (possibly conducing a multi-message exchange
with the
client to request the credentials) and validate them.
Validation can use a LoginService possibly provided to the
ServerAuthentication which could be JAAS, Hash, JDBC, etc etc.
Lazy auth results in returning a lazy result that only attempts
authentication when info is actually needed. In this case no
message
exchange with the client is possible.
<<<<<<<<<<<<<<<<<<<<
5. Assuming that authentication succeeded (this includes the
lazy
case
where the request would be allowed even without
authentication), we
wrap
up the result in an identity delegate:
UserIdentity userIdentity = newUserIdentity(authResult);
base_request.setUserIdentity(userIdentity);
The UserIdentity is the delegate for run-as role
implementation and
actually answering auth questions from the application program.
This
allows app servers to handle run-as roles however they want.
6. Assuming authentication is mandatory, now that we know the
user, we
can find out if they are in the appropriate roles:
checkWebResourcePermissions(pathInContext, base_request,
base_response,
constraintInfo, userIdentity)
7. On success, we can actually handle the request:
getHandler().handle(pathInContext,
messageInfo.getRequestMessage(),
messageInfo.getResponseMessage(), dispatch);
8. Assuming no exceptions were thrown, we can now secure the
response
(normally a no-op for http):
serverAuthentication.secureResponse(messageInfo, authResult);
-------------------------------------------
JASPI implementations
I wrote a fairly complete jaspi framework implementation for
geronimo
(rather than the bits actually needed for http which I wrote
for
jetty)
and have a nearly-untested openid implementation. This
(theoretically)
lets you openid-enable your app by supplying an appropriate
login
page
and useing the openid auth module.
Theres also a glassfish implementation that I haven't looked
at and
someone wrote a SPNEGO auth module that works with it.
http://spnego.ocean.net.au/
--------------------------------------------
How does this differ from what's there now?
SecurityHandler: AbstractSecurityHandler now just has the
basic
workflow described about and delegates all actual work to
either
subclasses (for authorization decisions and object creation)
or the
authentication delegate. This makes it easy to plug in
alternate
implementations such as a JACC implementation for an EE server.
Authentication results and run-as roles: Formerly these were
either
directly set in the request (possibly using lazy evaluation,
with
code
again in Request) or stuffed into a Principal implementation
via the
UserRealm. This really overloaded the idea of a Principal
for no
apparent reason and made integration into app servers slightly
convoluted. This is replaced with a UserIdentity interface
providing
separate access to the auth results (user principal) and role
handling
(isUserInRole, and run-as handling). Subclasses of
AbstractSecurityHandler can provide their own implementations
of
this
interface. These typically delegate to implementations of
ServerAuthResult, which can handle lazy authentication if
necessary.
UserRealm IMO glues together a lot of unrelated functions,
primarily the
role handling code now in UserIdentity and the credential
validation now
in LoginService. Credential validation may not even be
needed by
the
server (e.g. openid). If needed it's called from something
that
extracts credentials from the request. Implementations are
going
to do
something like look up the user in a file or table or
delegate to
JAAS.
On the other hand the role handling is called by jetty or by
the
application and the implementation is done by the app server
(jetty or
e.g. geronimo). Aside from being related somehow to security,
these are
totally unrelated concerns.
--------------------------------------------------
How does ServerAuthentication and LoginService relate to JASPI?
The JASPI interface similar to ServerAuthentication is
ServerAuthContext:
void cleanSubject(MessageInfo messageInfo, Subject subject)
throws
AuthException;
AuthStatus secureResponse(MessageInfo messageInfo, Subject
serviceSubject) throws AuthException;
AuthStatus validateRequest(MessageInfo messageInfo, Subject
clientSubject, Subject serviceSubject) throws AuthException;
The main difference is that ServerAuthentication packages all
the
results into a ServerAuthResult object rather than modifying
the
clientSubject directly and hiding user principal and group
info in
some
callback handers. This lets ServerAuthentication support
lazy auth.
As far as configuration goes. you get a ServerAuthContext by
calling a
whole lotta methods on some other stuff. or.... you can just
create one
and stuff it into an adapter, JaspiServerAuthentication.
Probably we
want to implement the built in auth methods as direct
ServerAuthentication implementations rather than the current
ServerAuthModule implementations (a ServerAuthContext is
supposed to
delegate to one or more ServerAuthModules, which have the same
interface).
LoginService is a pretty straightforward way of asking for
password
validation and getting some info back. JASPI has a peculiar
IMO
system
based on Callbacks. The container (jetty) supplies the auth
context
with a CallbackHandler that enables bi-directional
communication.
Callbacks providing services to the auth module:
PasswordValidationCallback: this lets the auth module ask for
password
validation: this is the closest to LoginService.
CertStoreCallback, PrivateKeyCallback, SecretKeyCallback, and
TrustStoreCallback all let the auth module ask for certificate
services. AFAICT these are mostly for securing response
messages,
which
is typically not done for http.
Callbacks letting the auth module pass info to the server:
CallerPrincipalCallback: supplies the caller principal so
getCallerPrincipal can return something.
GroupPrincipalCallback supplies "groups" the user may be in.
The
meaning here is rather undefined but can be mapped to roles
in some
way,
such as by assuming the groups and roles are the same.
The use of callbacks here still seems rather weird to me but
may
make
more sense in the context of other messaging systems: jaspi is
supposed
to be applicable to all sorts of messaging, including ejb
calls,
jms,
web services, etc etc.
I've put the caller principal and groups into the
ServerAuthResult
object where they can be accessed directly (although possibly
determined
lazily).
--------------------------------------------------------------
Comments...
Right now it looks to me as if form auth needs to be non-lazy
since
part
of the message exchange involves a request to j_security_check
which is
normally not a secured response. Trying to evaluate auth for
this
lazily doesn't work... you never get back to the original
request.
I don't see how this implementation could be significantly
simplified or
sped up.... I'm certainly willing to look at problems.
I've been discussing JACC with Greg for a long time now. The
only
thing
I can see that is possible with constraint implementations that
is not
possible with jacc is redirecting an http request to the
"equivalent"
https request if a user data constraint is violated. I'm
curious
about
whether this is something people want to do or usually set up.
Many thanks,
david jencks
---------------------------------------------------------------------
To unsubscribe from this list, please visit:
http://xircles.codehaus.org/manage_email