我有一个Java Web应用程序,可以在Windows Active Directory环境中对客户端进行SPNEGO身份验证。 为了验证用户身份,我们使用了旧的SPNEGO SourceForge项目中的代码。
String encodedAuthToken = (String) credentials;
LOG.debug("Encoded auth token: " + encodedAuthToken);
byte[] authToken = B64Code.decode(encodedAuthToken);
GSSManager manager = GSSManager.getInstance();
try {
Oid krb5Oid = new Oid("1.3.6.1.5.5.2");
GSSName gssName = manager.createName(_targetName, null);
GSSCredential serverCreds = manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, krb5Oid, GSSCredential.INITIATE_AND_ACCEPT);
GSSContext gContext = manager.createContext(serverCreds);
if (gContext != null) {
while (!gContext.isEstablished()) {
authToken = gContext.acceptSecContext(authToken, 0, authToken.length);
}
if (gContext.isEstablished()) {
// Login succeeded!
String clientName = gContext.getSrcName().toString();
}
}
}
身份验证工作正常,但我们还要求使用约束委派将用户凭据委派给后端服务(Exchange EWS)。 在我们的AD中进行配置时,它看起来很小,但事实并非如此。看到: AD delegation settings
这里描述的不同之处:msdn.microsoft.com/en-us/library/cc246080.aspx?f=255&MSPPError=-2147217396 通过无约束委派,我们可以在调用后端服务时简单地使用可用的委派凭据,这一切都很好:
GSSCredential delegatedCreds = gContext.getDelegCred()
SpnegoHttpURLConnection conn = new SpnegoHttpURLConnection(clientCreds);
通过约束委派,我们无法访问用户TGT,似乎我们需要使用Java 8假设支持的MS-SFU(S4U2proxy)Kerberos扩展。 我能找到的唯一例子就是这一个:https://github.com/ymartin59/java-kerberos-sfudemo(感谢Yves Martin!)
现在我的问题...在我的身份验证之后,我基本上得到了经过身份验证的用户的用户名(请参阅" clientName"在上面的代码中)。
我们真的需要使用S4U2self机制来冒充用户吗? 客户端刚刚向我们发送了它的Kerberos服务票证(包含在我无法解码的SPNEGO令牌中)。 理想情况下,我们应该能够使用该服务票据和我自己的服务TGT来验证用户(使用S4U2proxy机制)? 但我不明白怎么做。
现在我想知道是否可以将我们的SPNEGO身份验证与S4U2proxy委派联系在一起?
非常感谢您对此提出的任何意见。
答案 0 :(得分:5)
我最近一直在做这样的事情,但我正在使用春季安全kerberos。我在github here上放了一个例子。我发现需要设置使用约束委托的关键是你想要它和S4U2Proxy是为了确保(如果你正在使用Oracle / OpenJDK)你在JAAS配置中设置isInitiator=true
以便当调用getDelegCred时,你会得到一个Krb5ProxyCredential。请参阅评论here。使用该凭据,您可以使用它来代表用户创建服务票证令牌,以限制您以正常方式使用的服务,例如this。
答案 1 :(得分:2)
我对Kerberos约束委派进行了很多研究,最后我找到了使用Java的正确方法。
域控制器上的设置
1)没有委派:不信任此帐户进行委派
您(服务用户)无法获取该用户的委派凭据。这意味着您不能代表最终用户执行任何任务。 最多只能做的是接受来自用户(通常是浏览器)的传入票证,并将其传递给KDC进行验证。作为响应,KDC会告诉您将此票证发给哪个用户(或委托人),但不会传递任何凭据。
2)不受约束的委派:信任此帐户可以委派给任何服务(仅限Kerberos)
使用此选项,您(服务用户)将获得用户的委派凭据。而且,您得到的是用户的TGT。使用此TGT,您可以代表用户请求TGS(服务票证)以获得任何服务。
3)信任此帐户可以委派给指定的服务(仅限Kerberos)
在这里,您可以指定可以使用委派凭据的服务。这意味着启用此选项后,您将获得委托的凭据,但是,只允许使用它们来获取指定服务的最终用户的TGS。
另一个要点是,您必须拥有最终用户的TGS(您的Web应用程序的最终用户的TGS)。然后,您可以使用此TGS,向KDC请求最终用户的另一服务的TGS。
4)信任此帐户以委派给指定的服务(任何协议)
这也称为协议转换。同样,在此选项中,您还需要指定可以代表用户向KDC请求TGS的服务。
您(服务用户)可以“模拟”最终用户,而无需最终用户提供任何形式的凭单。 您可以模拟任何用户,并获得指定服务的TGS。 此选项对于无法进行最终用户交互的背景处理或计划很有用。
Java代码示例
1)获取委派证书(在上述选项2和3中很有用)
// ---------------------------------
// step 1: Login using service user credentials and get its TGT
// ---------------------------------
Subject subject = new Subject();
Krb5LoginModule krb5LoginModule = new Krb5LoginModule();
Map<String,String> optionMap = new HashMap<String,String>();
optionMap.put("keyTab", "c:\\ticket\\sapuser.keytab");
optionMap.put("principal", "HTTP/TEST"); // SPN you mapped to the service user while creating the keytab file
optionMap.put("doNotPrompt", "true");
optionMap.put("refreshKrb5Config", "true");
optionMap.put("useTicketCache", "true");
optionMap.put("renewTGT", "true");
optionMap.put("useKeyTab", "true");
optionMap.put("storeKey", "true");
optionMap.put("isInitiator", "true"); // needed for delegation
optionMap.put("debug", "true"); // trace will be printed on console
krb5LoginModule.initialize(subject, null, new HashMap<String,String>(), optionMap);
krb5LoginModule.login();
krb5LoginModule.commit();
// ---------------------------------
// Step 2: Use login context of this service user, accept the kerberos token (TGS) coming from end user
// ---------------------------------
public GSSCredential validateTicket(byte[] token) {
try {
return Subject.doAs(this.serviceSubject, new KerberosValidateAction(token));
}
catch (PrivilegedActionException e) {
throw new BadCredentialsException("Kerberos validation not successful", e);
}
}
private class KerberosValidateAction implements PrivilegedExceptionAction<GSSCredential> {
byte[] kerberosTicket;
public KerberosValidateAction(byte[] kerberosTicket) {
this.kerberosTicket = kerberosTicket;
}
@Override
public GSSCredential run() throws Exception {
byte[] responseToken = new byte[0];
GSSName gssName = null;
GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
while (!context.isEstablished()) {
responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
gssName = context.getSrcName();
if (gssName == null) {
throw new BadCredentialsException("GSSContext name of the context initiator is null");
}
}
//check if the credentials can be delegated
if (!context.getCredDelegState()) {
SecurityLogger.getLogger().error("Credentials can not be delegated. Please make sure that delegation is enabled for the service user. This may cause failures while creating Kerberized application.");
return null;
}
// only accepts the delegated credentials from the calling peer
GSSCredential clientCred = context.getDelegCred(); // in case of Unconstrained Delegation, you get the end user's TGT, otherwise TGS only
return clientCred;
}
}
// ---------------------------------
// Step 3: Initiate TGS request for another service using delegated credentials obtained in previous step
// ---------------------------------
private Object getServiceTicket(GSSCredential clientCred) throws PrivilegedActionException {
Object o = Subject.doAs(new Subject(), (PrivilegedExceptionAction<Object>) () -> {
GSSManager manager = GSSManager.getInstance();
Oid SPNEGO_OID = new Oid("1.3.6.1.5.5.2");
Oid KRB5_PRINCIPAL_OID = new Oid("1.2.840.113554.1.2.2.1");
GSSName servicePrincipal = manager.createName("HTTP/TEST", KRB5_PRINCIPAL_OID); // service to which the service user is allowed to delegate credentials
ExtendedGSSContext extendedContext = (ExtendedGSSContext) manager.createContext(servicePrincipal, SPNEGO_OID, clientCred, GSSContext.DEFAULT_LIFETIME);
extendedContext.requestCredDeleg(true);
byte[] token = new byte[0];
token = extendedContext.initSecContext(token, 0, token.length); // this token is the end user's TGS for "HTTP/TEST" service, you can pass this to the actual HTTP/TEST service endpoint in "Authorization" header.
return token;
});
return o;
}
2)获取模拟凭据(在上述选项4中很有用)
初始步骤与上面步骤1中提到的相似。您需要使用服务用户凭据登录。 “运行”方法的变化很小,如下所示:
@Override
public GSSCredential run() throws Exception {
GSSName gssName = null;
GSSManager manager = GSSManager.getInstance();
GSSCredential serviceCredentials = manager.createCredential(GSSCredential.INITIATE_ONLY);
GSSName other = manager.createName("bhushan", GSSName.NT_USER_NAME, kerberosOid); // any existing user
GSSCredential impersonatedCredentials = ((ExtendedGSSCredential) serviceCredentials).impersonate(other);
return impersonatedCredentials;
}
}
您会发现在这种情况下我们不需要用户的TGS。
代表用户获取TGS来进行其他服务,与上述代码中的步骤3相同。只需传递这些模拟的凭据而不是委托的凭据即可。
我希望这会有所帮助。
谢谢,
hu山