[Home] You are not logged in. Login here

Just my stuff

SPNEGO authentication and credential delegation with Java

Almost all our Web Services are secured with SPNEGO. This way we can forward the identity of the user calling a service to another service, or/and through the tiers of the service. If, for example, a user calls the “check my mail” WS, the service can get the identity of the user from the SPNEGO token, request a new kerberos token from the KDC for that user, and use that token to check for new mail on the IMAP server on behalf of the user.
Client[1] ---> WS[2] ---> more services[3]

Client calls WS, authenticates with his kerberos token, and WS can use that token to authenticate to more services on behalf of the Client.
I had to search the Web for a few hours to find out how SPNEGO and delegation works with Java, so here is a summary:
  1. The client sends a request to the server
  2. The server answers with
    Status: 401 - Authorization Required
    WWW-Authenticate: Negotiate
    
  3. The client receives the 401 and sends the SPNEGO token to the server:
    Authorization=Negotiate YIIGHwYGKwYBBQUCoIIGE[...]
    
  4. The server then has to decode and validate that token. If the token is forwardable then the server can use it to request a new token to authenticate to other services.

This is how to decode a SPNEGO token in Java:
byte[] token = null;
byte[] tokenForPeer = null;
byte[] tokenForEndpoint = new byte[0];

String endpointSPN = null;

GSSManager manager = GSSManager.getInstance();

GSSContext context = null;

GSSCredential clientCred = null;
GSSCredential myCred = null;

try {
  //Oid krb5MechOid = new Oid("1.2.840.113554.1.2.2");
  Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2"); 

  //first obtain it's own credentials...
  myCred = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, spnegoMechOid, GSSCredential.ACCEPT_ONLY);
  //...and create a context for this credentials...
  context = manager.createContext(myCred);

  //...then use that context to authenticate the calling peer by reading his
  //spnego token
  System.out.println(authorization);
  token = Base64.decode(authorization);
  tokenForPeer = context.acceptSecContext(token, 0, token.length);

  if (!context.isEstablished()) return false;
  if (tokenForPeer != null) {
    System.out.println("there is a token to send back to the peer, but I leave this out for now");
  }

  System.out.println("Context Established! ");
  System.out.println("Client principal is " + context.getSrcName());
  System.out.println("Server principal is " + context.getTargName());

} catch (WSSecurityException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
} catch (GSSException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

the authorization String contains the SPNEGO token from the request. Once we have established the context, we can check if the credentials can be delegated, and then request the new token:
//check if the credentials can be delegated
if (!context.getCredDelegState()) {
  System.out.println("credentials can not be delegated!");
  return false;
}

//get the delegated credentials from the calling peer...
clientCred = context.getDelegCred();

//now create the spnego token to send to the endpoint:
//create target server SPN
endpointSPN = "HTTP/spnegotestserver.domain.com@REALM.COM";
System.out.println("Endpoint: " + endpointSPN);
GSSName gssServerName = manager.createName(endpointSPN, GSSName.NT_USER_NAME);

//...and create a new context pretending to be the caller
clientContext = manager.createContext(gssServerName.canonicalize(spnegoMechOid), spnegoMechOid, clientCred, GSSContext.DEFAULT_LIFETIME);

//this should be an option: enable GSS credential delegation
clientContext.requestCredDeleg(true);
// create a SPNEGO token for the target server
tokenForEndpoint = clientContext.initSecContext(tokenForEndpoint, 0, tokenForEndpoint.length);

Done. Now we have a new tokenForEndpoint object that contains a valid SPNEGO token with the delegated credentials from the calling user. Insert it in the headers for the HTTP request to the next service in the chain like this
"Negotiate " + Base64.encode(tokenForEndpoint)

and you are good to go.
Avatar mady said...

Hi,

Can you please post, How to create the token to access the webservice,

I understtod the following tokent need to append on http header , but how to create this token on a webservice URL . please post client if you have any.”Negotiate ” + Base64.encode(tokenForEndpoint)

Fri Jun 26 17:19:19 +0200 2009
Anonymous zhiyong said...

I have a Java client. I have forwardable = true in the krb5.ini file and I have checked “Trust this user for delegation to any services (Kerberos only)” for my service account. However, crd.getDelegState still returns false. Any suggestions?

Tue Jun 02 14:29:41 +0200 2009
Anonymous karel said...

Do you by chance know what one can do if context.getCredDelegState() returns false?
My kerberos environment is Windows 2008 server. I have checked “Trust this user for delegation to any service (Kerberos only)” on the service principal and in Firefox I have set network.negotiate-auth.trusted-uris AND network.negotiate-auth.delegation-uris.

Thu Jul 02 16:01:39 +0200 2009
Anonymous Gilbert said...

Hi,
nice to see a tutorial about SPNEGO. I searched a lot lately cause i need to write a java client to access a .Net webservice and i’m getting confused. There seems to be no clear line. Seems the code you posted is only for server side. Would be nice if you provide some client code. f.e. i have the wsdl and created the stub with Axis2 but get 401.2 only;
with commons httpclient and :
Credentials credentials = new NTCredentials(“userID”, “password”,
“machine”,”domain”);
i get access but i don’t know/understand where to put that in to make it work.

Regards, Gilbert

Thu Oct 16 21:24:19 +0200 2008
Avatar S2 said...

Hi Glibert,
the client code is in the second part of the post. Once you have the credentials of the user (no matter how you obtain them. In this post I obtain them by getting a forwardable ticket from the user, but you can obtain the with user/pass as well) you just creat the SPNEGO token that you can insert in the Authorization header of the request. Did I understand your question correctly?

Thu Oct 16 22:28:01 +0200 2008
Anonymous Micky said...

Hi S2,

long hours for looking, Ive found your code.. its exactly what I need….

But, what shall I do, if crd.getDelegState returns false ? How I get the Intenet explorer to send a token, which sends the credential with ????

Fri Dec 19 09:22:04 +0100 2008
Avatar S2 said...

Hi Micky, the token send from IE or FF needs to have the “Forwardable” flag set, and the server needs to be OK_AS_DELEGATE.

Fri Dec 19 14:13:36 +0100 2008
Anonymous Vlad said...

Does it work with Java 6 only? With Java 5 I get an exception with message “1.3.6.1.5.5.2”—presumably, OID is not known.

Thu Jan 22 04:07:42 +0100 2009
Avatar S2 said...

Hi Vlad, yes, this is for Java 6 only.
Support for HTTP/SPNEGO is available starting from Java SE 6.

Fri Mar 20 17:59:27 +0100 2009
Avatar mady said...

Hi,

Can you please post, How to create the token to access the webservice,

Fri Jun 26 17:17:19 +0200 2009
Avatar fathamster said...

Very useful post, thank you.

I’ve written an encoder/decoder to process the ASN.1 SPNEGO envelope and get at the KRB5 token within. This allows earlier versions of Java (theoretically 1.4 up, but so far only tested with 5) to create and consume SPNEGO, using the support for Kerberos in the GSS-API. If there’s any interest, then I’ll try to post my code.

P

Mon Jul 20 11:04:51 +0200 2009
Anonymous Venkatesh said...

Can I have your code ?

Thu Jul 23 19:39:30 +0200 2009
Avatar S2 said...

@zhiyong & karel, if getCredDelegState() returns false the token is not forwardable. Did you check (with a Wireshark trace) if the SPNEGO token is forwardable?

Wed Jul 29 10:55:06 +0200 2009
Avatar S2 said...

@mady, the code is in the post. You just have to add the http header (just a string) created with

"Negotiate " + Base64.encode(tokenForEndpoint)

to your request. How you do that depends on what you use to make the request. In Axis2 for example you would do something like
Map<String, String> headers = (Map<String, String>)axis2MessageCtx.getProperty(MessageContext.TRANSPORT_HEADERS);
headers.put("Authorization", "Negotiate " + Base64.encode(tokenForEndpoint);

Wed Jul 29 11:04:42 +0200 2009
Avatar S2 said...

@fathamster, sure! Post it! :)

Wed Jul 29 11:11:49 +0200 2009
Anonymous Florin said...

Hello,
Could you please help me in a problem related to decoding an SPNEGO authentication token?
I am trying to decode an authorization token from a servlet filter using your code, but I receive the following exception: GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos Key). This exception is thrown by this method invocation: myCred = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, mechs, GSSCredential.ACCEPT_ONLY);

Could you please tell me if there are other requirements for running the provided code?

Thank you!

Tue Sep 01 14:13:00 +0200 2009
Avatar Michael said...

Hi
I have the same problem like florin. I see the SPNEGO token in the header and have the same format like YIIGHwYGKwYBBQUCoIIGE[...].
As soon as I call the Method myCred = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, mechs, GSSCredential.ACCEPT_ONLY);
I get the same exception: GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos Key).
Do I have to put the token somewhere else ? Where does the method look for the ticket ?
Do you have a solution to this ?
Thanks

Tue Oct 20 10:09:25 +0200 2009
Avatar S2 said...

@Michael & Florin, you probably need the keytab file for the service. Look here.

Tue Nov 03 18:11:23 +0100 2009
Anonymous Pat Gonzalez said...

Hi,

Here’s an open source project with a working example and tutorials on how to do single sign-on and credential delegation. I love the fact that you can download a working source code!

http://spnego.sourceforge.net/credential_delegation.html

Wed Nov 04 07:00:13 +0100 2009
Anonymous and said...

hi
it code working, but i can not use same ticket for authenticate more then 1 times

in 1st time authorization success
in 2nt i have org.apache.axis2.AxisFault: Transport error: 401 Error: Unauthorized

it is all with one ticket

Wed Feb 17 11:57:48 +0100 2010
Anonymous and said...

it is tcpmon

POST /ReportServer/ReportService2005.asmx HTTP/1.1
Content-Type: text/xml; charset=UTF-8
SOAPAction: “http://schemas.microsoft.com/sqlserver/2005/06/30/reporting/reportingservices/ListChildren”
Authorization: Negotiate YIIJCwYGKwYBBQUCoIII/...
User-Agent: Axis2
Host: 127.0.0.1:1234
Content-Length: 356

<?xml version=’1.0’ encoding=’UTF-8’?><soapenv:envelope><soapenv:body><ns1:listchildren><ns1:item>/myproject</ns1:item><ns1:recursive>false</ns1:recursive></ns1:listchildren></soapenv:body></soapenv:envelope>POST /ReportServer/ReportService2005.asmx HTTP/1.1
Content-Type: text/xml; charset=UTF-8
SOAPAction: “http://schemas.microsoft.com/sqlserver/2005/06/30/reporting/reportingservices/ListChildren”
Authorization: Negotiate YIIJCwYGKwYBBQUCoIII/...
User-Agent: Axis2
Host: ad01:1234
Content-Length: 356

<?xml version=’1.0’ encoding=’UTF-8’?><soapenv:envelope><soapenv:body><ns1:listchildren><ns1:item>/myproject</ns1:item><ns1:recursive>false</ns1:recursive></ns1:listchildren></soapenv:body></soapenv:envelope>


HTTP/1.1 200 OK
Date: Wed, 17 Feb 2010 10:45:50 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
WWW-Authenticate: Negotiate oYGeMIGboAMKAQChCwYJ
X-AspNet-Version: 2.0.50727
Cache-Control: private, max-age=0
Content-Type: text/xml; charset=utf-8
Content-Length: 1207

<?xml version=”1.0” encoding=”utf-8”?>...</soap:envelope>

HTTP/1.1 401 Unauthorized
Content-Length: 1539
Content-Type: text/html
Server: Microsoft-IIS/6.0
WWW-Authenticate: Negotiate oYGPMIGMoAMKAQGhCwYJ
X-Powered-By: ASP.NET
Date: Wed, 17 Feb 2010 10:45:52 GMT

Wed Feb 17 12:04:38 +0100 2010
Anonymous and said...

i decided it problem, the problem was in “im using one ticket for 2 requests from one servlet”

good article!

Wed Feb 17 14:54:21 +0100 2010
Avatar S2 said...

Hi “and”, as you discovered, kerberos prevents reply attacks, therfore you can not use the same ticket two times :)

Fri Mar 26 23:29:32 +0100 2010
Anonymous Andy said...

In the scenario you describe above, where exactly does the ‘authorization’ token come from? Is this from a request header?

Fri Apr 23 20:22:12 +0200 2010
Avatar S2 said...

Yes

Wed Apr 28 23:06:35 +0200 2010
Anonymous Dale said...

S2, Do you have a Java client-side sample that can obtain the token from cache, rather than from prompting the user for a username/password, and then sending that token to the server by adding it to the http header? Thanks

Thu Apr 28 18:42:05 +0200 2011
Anonymous Aslan said...

hi friends,

I have the token , I am trying to sent it to server side by http header for RESTful web service authentication using code below,

DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Authorization", "Negotiate "+encodedToken);
HttpResponse response = client.execute(httpGet);

But I could not achive to send request..
I did not get any exception but also couldnot submit request..
Any suggestions?
Your help will be very appriciated..

Sat Jun 25 11:04:33 +0200 2011
 
Comments for this post have been disabled