This is nearly the same question as How to delegate the kerberos client credentials to the server?
But there is no answer. That's why I raise the question again. Hopefully someone can help.
It's possible to get a service ticket for the client (remote user) in the server side in order to use that ticket to authenticate against another backend?
Scenario: User (IE) ==> AppServer (Tomcat, under Linux) ==> Backend (webservice - REST service on Windows)
We have SPNEGO auth running and working in the AppServer
The AD user in the keytab file on in the AppServer has the rights to do the delegation (hopefully)
What are the preconditions that the GSSManager can create a credential that can be used for delegation? (´context.getDelegCred()´ should not fail after ´GSSManager.getInstance().createContext(this.serverCredentials)´?
There must be someone who has solved this problem?
Does "Forwardable Ticket true" mean that user from keytab file has delegation rights? Does anyone know this?
Thanks in advance
Output from HelloKDC.java (see extract from bellow)
Client Principal = HTTP/[email protected]
Server Principal = krbtgt/[email protected]
Session Key = EncryptionKey: keyType=23 keyBytes (hex dump)=
0000: xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx ................
Forwardable Ticket true
Forwarded Ticket false
Proxiable Ticket false
Proxy Ticket false
Postdated Ticket false
Renewable Ticket false
Initial Ticket false
Auth Time = Wed Dec 20 16:52:03 CET 2017
Start Time = Wed Dec 20 16:52:03 CET 2017
End Time = Thu Dec 21 02:52:03 CET 2017
Renew Till = null
Client Addresses Null
Private Credential: /opt/app/tomcat/ssoad1/servername.domain.com.keytab for HTTP/[email protected]
Connection test successful.
Extract from HelloKDC.java (also from net.sourceforge.spnego):
// Name of our krb5 config file
final String krbfile = "/opt/app/tomcat/ssoad1/krb5.ini";
// Name of our login config file
final String loginfile = "/opt/app/tomcat/ssoad1/jaas.conf";
// Name of our login module
//final String module = "spnego-client";
final String module = "com.sun.security.jgss.krb5.initiate";
// set some system properties
System.setProperty("java.security.krb5.conf", krbfile);
System.setProperty("java.security.auth.login.config", loginfile);
System.setProperty("sun.security.krb5.debug", "true");
final LoginContext loginContext = new LoginContext(module);
// attempt to login
loginContext.login();
// output some info
System.out.println("Subject=" + loginContext.getSubject());
// logout
loginContext.logout();
System.out.println("Connection test successful.");
jaas.conf:
com.sun.comcurity.jgss.krb5.initiate {
com.sun.comcurity.auth.module.Krb5LoginModule required
doNotPrompt=true
principal="HTTP/[email protected]"
useKeyTab=true
keyTab="/opt/app/tomcat/ssoad1/servername.domain.com.keytab"
storeKey=true;
};
com.sun.security.jgss.krb5.accept {
com.sun.security.auth.module.Krb5LoginModule required
doNotPrompt=true
principal="HTTP/[email protected]"
useKeyTab=true
keyTab="/opt/app/tomcat/ssoad1/servername.domain.com.keytab"
storeKey=true
useTicketCache=true
isInitiator=true
refreshKrb5Config=true
moduleBanner=true
storePass=true;
};
spnego-client {
com.sun.security.auth.module.Krb5LoginModule required;
};
spnego-server {
com.sun.security.auth.module.Krb5LoginModule required
principal="HTTP/[email protected]"
useKeyTab=true
keyTab="/opt/app/tomcat/ssoad1/servername.domain.com.keytab"
storeKey=true
useTicketCache=true
isInitiator=false
refreshKrb5Config=true
moduleBanner=true
;
};
krb5.ini
[libdefaults]
default_realm = CORP1.AD1.COMPANY.NET
default_keytab_name = FILE:/opt/app/tomcat/ssoad1/servername.domain.com.keytab
default_tkt_enctypes = rc4-hmac
default_tgs_enctypes = rc4-hmac
forwardable = true
renewable = true
noaddresses = true
clockskew = 300
udp_preference_limit = 1
[realms]
CORP1.AD1.COMPANY.NET = {
kdc = ndcr001k.corp1.ad1.company.net:88
default_domain = domain.com
}
[domain_realm]
.domain.com = CORP1.AD1.COMPANY.NET
from net.sourceforge.spnego.SpnegoAuthenticator.java
SpnegoAuthenticator.LOCK.lock();
try {
LOGGER.fine("create context");
LOGGER.fine("serverCredentials="+this.serverCredentials.toString());
context = SpnegoAuthenticator.MANAGER.createContext(this.serverCredentials);
context.requestCredDeleg(true);
LOGGER.fine("clientModuleName="+clientModuleName.toString());
LOGGER.fine("context.getCredDelegState()="+context.getCredDelegState());
token = context.acceptSecContext(gss, 0, gss.length); // When I understand right : gss contains the token from the authorized client (IE Windows user)
LOGGER.fine("token="+token);
LOGGER.fine("context.getDelegCred()="+context.getDelegCred());
} finally {
SpnegoAuthenticator.LOCK.unlock();
}
creates the following Exception:
javax.servlet.ServletException: GSSException: No valid credentials provided
net.sourceforge.spnego.SpnegoHttpFilter.doFilter(SpnegoHttpFilter.java:287)
Root Cause
GSSException: No valid credentials provided
sun.security.jgss.krb5.Krb5Context.getDelegCred(Krb5Context.java:511)
sun.security.jgss.GSSContextImpl.getDelegCred(GSSContextImpl.java:614)
sun.security.jgss.spnego.SpNegoContext.getDelegCred(SpNegoContext.java:1064)
sun.security.jgss.GSSContextImpl.getDelegCred(GSSContextImpl.java:614)
net.sourceforge.spnego.SpnegoAuthenticator.doSpnegoAuth(SpnegoAuthenticator.java:503)
As recommended I answer my own question:
First make sure to have delegation allowed in active directory for the user that is listed in the keytab file. For delegation we add a full qualified hostname and a username under which the service is running on the second sever (delegation target server - here windows).
AD Delegation tab
The AD admins should know how to create a keytab file and hand it over to you.
Create a jaas.conf and a krb5.ini file like described in the question.
Use the library from http://spnego.sourceforge.net/.
Add the filter to the web.xml:
<filter>
<filter-name>SpnegoHttpFilter</filter-name>
<filter-class>net.sourceforge.spnego.SpnegoHttpFilter</filter-class>
....
....
<init-param>
<param-name>spnego.allow.delegation</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SpnegoHttpFilter</filter-name>
<url-pattern>/sc2</url-pattern>
</filter-mapping>
with all needed init parameters like described on the library web page and the filter mapping of course.
Start Tomcat with the following options:
CATALINA_OPTS="-Dsun.security.krb5.debug=[true|false]
-Djava.security.auth.login.config=/opt/app/tomcat/ssoad1/jaas.conf
-Djava.security.krb5.conf=/opt/app/tomcat/ssoad1/krb5.ini
-Djavax.security.auth.useSubjectCredsOnly=false"
The last option
-Djavax.security.auth.useSubjectCredsOnly=false
is very important - without that it doesn't work. That is not mentioned on the spnego.sourceforge.net website.
And then the magic really works:
The application acts as a http client with the credentials from the user who called the application from the browser.
private void doServiceCall(HttpServletRequest request, StringBuilder sb) throws GSSException, MalformedURLException, PrivilegedActionException, IOException {
if (request instanceof DelegateServletRequest) {
DelegateServletRequest dsr = (DelegateServletRequest) request;
GSSCredential creds = dsr.getDelegatedCredential();
if (null == creds) {
sb.append("No delegated creds.");
} else {
sb.append(creds.getName().toString());
SpnegoHttpURLConnection spnego =
new SpnegoHttpURLConnection(creds);
HttpURLConnection con = spnego.connect(new URL("https://server.domain.com/ServiceFactory/servicenamexyz/Get?KeyConditionValue=ACTION_OUTPUT"));
sb.append("<br />HTTP Status Code: " + spnego.getResponseCode());
sb.append("<br />HTTP Status Message: " + spnego.getResponseMessage());
String contentType = con.getContentType();
sb.append("<br />HTTP Content Type: " + contentType);
StringBuilder result = new StringBuilder();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
while ((line = reader.readLine()) != null) {
result.append(line);
}
reader.close();
sb.append("<br />HTTP Content: " + result.toString());
spnego.disconnect();
}
} else {
sb.append("Request not a delegate.");
}
br(sb);
}