Friday, December 17, 2010

Authentication and Authorization the CXF Way

I've been working recently on a number of tests and a demo showing how CXF endpoints can be protected by having consumers relying either on Basic Authentication or WS-Security UsernameTokens authenticated and the authorization rules enforced.

Often enough, when confronted with the problem of making sure the authentication and the authorization works, many users think of using either the J2EE-style authentication (configured via web.xml) or Spring Security.

The former option does not really work with WS-Security and sometimes may not be easy to configure in a fine-grained fashion, besides one may want to defer the role verification until just before the actual invocation occurs. The latter option can let you do it all, given how advanced the Spring Security is. If you're a Spring Security savvy person then wiring it in to secure the endpoints against some arbitrary WS-Security tokens can be a child's play :-) but one have to admit that it might be a bit 'intimidating' sometimes and in some cases Spring is simply not used in a given project. Additionally, making it work in OSGI may present quite a challenge on its own.

The other thing I've heard of is that users dealing with WS-Security may not necessarily want to deal directly with the WSS4J library at the level of their CXF interceptors (by the way, please subscribe to the Colm's blog for more information about WSS4J and other security-related thoughts and updates).

So in the end a number of CXF security interceptors has been introduced to make it as easy as possible for CXF users to secure the endpoints at the authentication and authorization levels.

Additionally, a CXF-specific UsernameToken has been introduced which at the moment encapsulates the information obtained from the WSS4J UsernameToken.
As you can see, it implements the SecurityToken interface with the idea being that more token types will be supported in time. For example, an AbstractSecurityContextInInterceptor converts a token to a CXF SecurityContext instance by delegating to subclasses which know about a current token type, with
AbstractUsernameTokenInterceptor being one such subclass. In the end, say a concrete AbstractUsernameTokenInterceptor implementation only sees CXF UsernameToken which it needs to use somehow for authenticating and populating a current Subject.

The Subject instance will be wrapped in a CXF SecurityContext which will be used later on to do the authorization or further wrapped by JAX-RS or JAX-RS SecurityContext objects and injected into service beans. You may want to use SimplePrincipal and SimpleGroup helpers if needed when creating custom Subjects.

Note that CXF ships two SecurityContext implementations which attempt to figure out which Subject Principals are roles. DefaultSecurityContext assumes Group instances represent primitive or group roles, while RolePrefixSecurityContextImpl find the Principals by checking if their names start from the configurable role prefix, example, from "ROLE_" (this post has some more information).

So far so good - the authentication has been taken care of if you use WS-Security. If you don't them the authentication will occur earlier on at the container entry level, with or without SpringSecurity involved. However, after working on the container-level authentication demo last week it became apparent that CXF needs a utility JAASLoginInterceptor. You only need to tell it the name of the LoginContext to resolve and optionally provide a role prefix name in case Subject does not use Groups to represent the roles. You may also need to override its getCallbackHandler method returning NamePasswordCallbackHandler instances by default, for example, this custom interceptor ensures it can handle Jetty ObjectCallbacks for setting the password.

Another good news is that the JAASLoginInterceptor will work equally well with CXF UsernameTokens (created during WS-Security-related requests) and AuthorizationPolicy capturing Basic or Digest authentication details.

Next we may need to enforce the authorization rules. SimpleAuthorizingInterceptor can be injected with a method names to roles map, while SecureAnnotationsInterceptor takes it one step further and lets users just inject a reference to a secured object and it will figure out how individual methods are supposed to be secured; it checks RolesAllowed annotations by default but one can provide the annotation class name used to specify the roles.
If you have roles stored in say the database then you may want to extend AbstractAuthorizingInterceptor and override its getExpectedRoles method.

If you are securing JAXWS or JAXRS endpoints, you only need to add one or two interceptors, say an optional JAASLoginInterceptor and SimpleAuthorizingInterceptor. When securing JAX-RS endpoints with these interceptors you may also want to add a custom CXF out fault interceptor, for example, like this one, if you want 403 or 401, instead of 500 being returned; alternatively you can let the exception propagate up to the servlet level when possible and deal with the ServletExceptions at the filter levels.

The JAX-RS frontend also ships two utility filters wrapping JAASLoginInterceptor and both authorization interceptors, The one which wraps the JAASLoginInterceptor returns 401 by default and adds a realm name to the WWW-Authenticate response header if needed. It also lets users specify the resource URI where the client needs to be redirected to instead of getting 401. If needed a simple subclass can ensure unrecognized callbacks are dealt with the same way it's done with JAASLoginInterceptor. Here is a sample filter .

Finally, some more information on how to configure the interceptors. Have a look at this beans.xml. The test assumes the authentication is managed by the container, for example, one can configure a Maven Jetty plugin to enforce it. The web.xml requests the authentication only and see how SimpleAuthorizingInterceptor and SecureAnnotationsInterceptor are set up to do the rest - the authorization.

This beans.xml introduces the JAASLoginInterceptor (and the JAX-RS filter) into the picture. I think it would be fare to say it is simple to setup both the JAAS authenticator and the authorization filters in CXF. And guess what - this very configuration can make your application secure without changing a bit in the standalone server, the servlet container and the OSGI container such as Karaf.

The varying part is ensuring the server/container sees the JAAS configuration such as this one (FYI, "BookLogin" is the name injected into JAASLoginInterceptor, while BookLoginModule is wrapping a Jetty PropertyFileLoginModule).

In my test I'm passing a system property "java.auth.security.login.config" to the test server process. In Karaf one needs to deploy a bundle containing the JAAS config. The bottom line is that the issue of bootstrapping the server with this information is an issue of its own.

So what do you do in the end once you set these new CXF interceptors ? You can just relax and see your secure CXF application working like a charm irrespectively of where it is deployed to :-)

Enjoy !



Link

4 comments:

Rafael B.C. said...

Hi Sergey,

Please note that all links in the text returns 404

Sergey Beryozkin said...

Thanks, the links should be fixed by now
Sergey

Björn Konrad said...

Is it also possible to configure, that POST-requests need authentication and GET-Requests don't? Url configure URL-Patterns (the web.xml security-constraint style)?

Sergey Beryozkin said...

Not at the moment, but it would be easy to override this filter:

https://github.com/apache/cxf/blob/master/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/security/JAASAuthenticationFilter.java

Example, you'd override filter() and use context to check UriInfo in order to ignore requests not matching a URL patterns, or check the HTTP verb, etc.

Please open a CXF JIRA Improvement request if you'd like it be supported OOB