JAX-RS Kerberos Support
Introduction
Please see MIT Kerberos Tutorial for a good introduction to Kerberos.
The Windows guide as well as this Wikipedia page are also worth checking.
Setup
Unix
1. Install the packages
> sudo apt-get install krb5-kdc krb5-admin-server
During the installation enter "localhost" as the host name for Kerberos servers (unless you have more specific host names to enter) and set a default realm, example, "MYCOMPANY.COM". Follow the 1.2 step from this blog entry to get this default realm set up properly.
2. Create principals
From the step 1.3 at this blog entry:
2.1 Create master key:
> sudo kdb5_util create -s
2.2 Create user and service principals
> sudo kadmin.local
followed by
> addprinc alice
> addprinc HTTP/localhost
where 'HTTP/localhost' is the typical service principal name used in the Negotiate scheme, replace 'localhost' if needed.
Add more user and service principals too as required.
3 Start KDC
> sudo krb5kdc
4. Create an optional ticket cache
> klist
returns an empty response
> kinit alice
> klist
confirms a TGT for 'alice' is in the cache.
2.4 Create keytabs
When keytabs are available, the principal password does not have to be specified in the login configuration.
Please follow the step 1.4 from this blog entry.
Note, creating a keytab actually resets an original principal password, example, after creating a keytab for 'alice' one would not be able to use the original password (TODO: apparently this can be restored - find out how). Thus, if you'd like to experiment with keytabs then you may want to have few user and service principals created, with only selected principals using keytabs.
Windows
Please check the relevant Windows configuration guide such as this one.
HTTP Negotiate scheme
'Negotiate' authentication scheme is used to pass Kerberos service tickets over HTTP.
Example:
Authorization: Negotiate "the encrypted service ticket"
GSS API
Please see this GSS API tutorial as well as check this blog for a number of GSS API examples. Understanding GSS API may help when the way CXF Kerberos handlers work needs to be customized or when the available GSS credentials created outside of CXF need to be made available to CXF (for the credential delegation).
JAAS Kerberos Module Configuration
com.sun.security.auth.module.Krb5LoginModule is typically used to login to Kerberos servers.
Client configuration
HTTPConduit
Please see this page for the information about Spnego/Kerberos HTTPConduit client support.
Interceptor
org.apache.cxf.jaxrs.security.KerberosAuthOutInterceptor can be used as an alternative to configuring HTTPConduit.
KerberosAuthOutInterceptor and the HTTPConduit Spnego handler share the same base code. Having HTTPConduit configuration can be enough in many cases
especially when SSL is also being setup at the conduit level. Using the interceptor can be handy when testing as well as when setting few extra properties which is not easy to set up at the generic HTTP Conduit Authorization Policy level.
The interceptor properties are explained in the following sub-sections
Authorization Policy
As explained on this page, Authorization Policy typically needs to have its type set to "Negotiate" and its "authorization" property set to the name of the JAAS context. AuthorizationPolicy is set as a "policy" property on the interceptor, example:
WebClient wc = WebClient.create("http://localhost:" + PORT + "/bookstore/books/123");
KerberosAuthOutInterceptor kbInterceptor = new KerberosAuthOutInterceptor();
AuthorizationPolicy policy = new AuthorizationPolicy();
policy.setAuthorizationType(HttpAuthHeader.AUTH_TYPE_NEGOTIATE);
policy.setAuthorization("KerberosClientKeyTab");
kbInterceptor.setPolicy(policy);
WebClient.getConfig(wc).getOutInterceptors().add(kbInterceptor);
Book b = wc.get(Book.class);
In this example, the KerberosClientKeyTab policy is used which links to the available keytab; otherwise AuthorizationPolicy 'UserName' and 'Password' properties would most likely have to be set too (with the possible exceptions on Windows)
Configuring the service principal name
Service principal identifies a target service.
By default, the service principal name is calculated by concatenating "HTTP", "/" and the name of the target host, example, when invoking on "http://localhost:8080/services", the service principal name is set to "HTTP/localhost".
The "servicePrincipalName" and "realm" properties can be used to customize it, example, setting "servicePrincipalName" to "HTTP/www.mycompany.com" and realm to "services.org" will result in the "HTTP/www.mycompany.com@services.org" service principal name being used.
When the "servicePrincipalName" is not specified, the target host from the provided endpoint URL is used to construct one as-is. To perform canonicalization of this hostname (e.g. if a CNAME record host.example.com points to an A record host-x.example.com, then use "host-x.example.com" when constructing the servicePrincipalName), the "useCanonicalHostname" property can be set to "true".
Using JAAS Configuration
Both HTTPConduit and interceptor handlers need a "java.security.auth.login.config" system property set up. This property needs to point to the file containing the configuration of the specific Kerberos login module.
Instead of setting this system property and maintaining a configuration file, one might want to use an implementation of javax.security.auth.login.Configuration and set it on the interceptor as a "loginConfig" property.
How to avoid setting username and password properties
Typically, one may have to set AuthorizationPolicy UserName and Password properties for the Kerberos login module to authenticate the user.
The next option is to create a keytab as noted in the Setup section, which will let one to avoid specifying a password property.
Finally, if the user actually owns the Java process which runs the code then no username and password properties have to be provided, assuming the Kerberos login configuration has 'useTicketCache' and possibly 'renewTGT' properties set to "true"
Server configuration
org.apache.cxf.jaxrs.security.KerberosAuthenticationFilter can be used to protected JAX-RS endpoints and enforce that a Negotiate authentication scheme is used by clients, example:
<bean id="kerberosFilter" class="org.apache.cxf.jaxrs.security.KerberosAuthenticationFilter">
<property name="loginContextName" value="KerberosServiceKeyTab"/>
</bean>
<jaxrs:server>
<jaxrs:serviceBeans>
<bean class="org.mycompany.MyCompanyResource"/>
</jaxrs:serviceBeans>
<jaxrs:providers>
<ref bean="kerberosFilter"/>
</jaxrs:providers>
</jaxrs:server>
KerberosAuthenticationFilter will set a CXF SecurityContext on the current message if the authentication has been successful. This SecurityContext will return an instance of KerberosAuthenticationFilter$KerberosPrincipal, this Principal will return a 'simple' and 'kerberos' source principal names, example, given "HTTP/localhost@myrealm.com", Principal#getName will return "HTTP/localhost", and KerberosPrincipal#getKerberosName will return "HTTP/localhost@myrealm.com".
Service principal name and JAAS Configuration
Service principal name and JAAS Configuration can be optionally set up the same way they can be with KerberosAuthOutInterceptor, using 'servicePrincipalName' + 'realm' and "loginConfig" properties.
CallbackHandler
javax.security.auth.callback.CallbackHandler needs to be registered if no Kerberos key tabs are used, here is an example of setting it up from Java:
public class TestResource {
public static void main(String[] args) {
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
sf.setResourceClasses(BookStore.class);
KerberosAuthenticationFilter filter = new KerberosAuthenticationFilter();
filter.setLoginContextName("KerberosServer");
CallbackHandler handler =
new org.apache.cxf.interceptor.security.NamePasswordCallbackHandler("HTTP/localhost", "http");
filter.setCallbackHandler(handler);
//filter.setLoginContextName("KerberosServerKeyTab");
//filter.setServicePrincipalName("HTTP/ktab");
sf.setProvider(filter);
sf.setAddress("http://localhost:" + PORT + "/");
sf.create();
}
}
In this example, the KerberosServer policy is used.
Credential Delegation
Please see this section on the way client-side credential delegation can be both enabled and implemented at the HTTP conduit level.
Note that if you have a JAX-RS KerberosAuthenticationFilter protecting the endpoints, then the filter will have an org.ietf.jgss.GSSContext instance available in the current CXF SecurityContext, via its KerberosAuthenticationFilter$KerberosSecurityContext implementation, which can be used to get to org.ietf.jgss.GSSCredential if the credential delegation is supported for a given source principal. The current credential if any can be set as a client property next, for example:
import org.ietf.jgss.GSSCredential;
import org.apache.cxf.jaxrs.security.KerberosAuthenticationFilter;
import org.apache.cxf.jaxrs.security.KerberosAuthenticationFilter.KerberosSecurityContext;
import org.apache.cxf.phase.PhaseInterceptorChain;
import org.apache.cxf.security.SecurityContext;
@Path("service")
public class MyResource {
@GET
public Book getBookFromKerberosProtectedStore() {
WebClient wc = webClient.create("http://internal.com/store");
SecurityContext securityContext = PhaseInterceptorChain.getCurrentMessage().get(SecurityContext.class);
if (securityContext instanceof KerberosSecurityContext) {
KerberosSecurityContext ksc = (KerberosSecurityContext)securityContext;
GSSCredential cred = ksc.getGSSContext().getDelegCred();
if (cred != null) {
WebClient.getConfig(wc).getRequestContext().put(GSSCredential.class.getName(), cred);
}
}
return wc.get(Book.class);
}
}
The HTTPConduit or KerberosAuthOutInterceptor handler will use the available GSSCredential.
Also note that KerberosAuthOutInterceptor can have its "credDelegation" property set to "true" if it is used instead of HTTPConduit on the client side, when enabling the delegation initially.