我对 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 中提到的类似。您需要使用服务用户凭据登录。 'run' 方法的变化很小,如下所示:
@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 中提到的相同。只需传递这些 impersonatedCredentials 而不是 delegatedCredentials。
我希望这会有所帮助。
谢谢,
布山