我有以下代码:
public static void main(String args[]){
try {
//String ticket = "Negotiate YIGCBg...==";
//byte[] kerberosTicket = ticket.getBytes();
byte[] kerberosTicket = Base64.decode("YIGCBg...==");
GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
String user = context.getSrcName().toString();
context.dispose();
} catch (GSSException e) {
e.printStackTrace();
} catch (Base64DecodingException e) {
e.printStackTrace();
}
}
当然失败了。这是例外:
GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
我不知道我应该做些什么来解决这个问题。老实说,我真的不了解Kerberos。
我通过发送带有“Negotiate”的相应标题WWW-Authenticate
的401作为值来获得此票证。浏览器立即再次发出相同的请求,其中包含此票证的authorization
标题。
我希望我能验证票证并确定用户是谁。
我是否需要密钥表文件?如果是这样,我将在哪个凭据下运行?我正在尝试将Kerberos票证用于网站的身份验证。凭据是否来自IIS的凭据?
我错过了什么?
更新1 从Michael-O的回复中,我做了一些谷歌搜索,发现this article,这导致我this article。
在table 3
上,我找到了1.3.6.1.5.5.2 SPNEGO
。
我现在已经按照第一篇文章中的示例添加了我的凭据。这是我的代码:
public static void main(String args[]){
try {
Oid mechOid = new Oid("1.3.6.1.5.5.2");
GSSManager manager = GSSManager.getInstance();
GSSCredential myCred = manager.createCredential(null,
GSSCredential.DEFAULT_LIFETIME,
mechOid,
GSSCredential.ACCEPT_ONLY);
GSSContext context = manager.createContext(myCred);
byte[] ticket = Base64.decode("YIGCBg...==");
context.acceptSecContext(ticket, 0, ticket.length);
String user = context.getSrcName().toString();
context.dispose();
} catch (GSSException e) {
e.printStackTrace();
} catch (Base64DecodingException e) {
e.printStackTrace();
}
}
但是现在代码在createCredential
失败并出现此错误:
GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos credentails)
以下是整张票:YIGCBgYrBgEFBQKgeDB2oDAwLgYKKwYBBAGCNwICCgYJKoZIgvcSAQICBgkqhkiG9xIBAgIGCisGAQQBgjcCAh6iQgRATlRMTVNTUAABAAAAl7II4g4ADgAyAAAACgAKACgAAAAGAbEdAAAAD0xBUFRPUC0yNDVMSUZFQUNDT1VOVExMQw==
答案 0 :(得分:24)
从Java验证SPNEGO票证是一个有点复杂的过程。这是一个简短的概述,但请记住,这个过程可能会有很多陷阱。您真的需要了解Active Directory,Kerberos,SPNEGO和JAAS如何运行以成功诊断问题。
在开始之前,请确保您知道Windows域的kerberos域名。出于这个答案的目的,我会假设它是 MYDOMAIN 。您可以通过从cmd窗口运行echo %userdnsdomain%
来获取领域名称。请注意,kerberos 区分大小写,而且领域几乎总是ALL CAPS。
为了让kerberos客户端访问服务,它会请求代表该服务的服务主体名称[SPN]的票证。 SPN通常源自机器名称和所访问的服务类型(例如HTTP/www.my-domain.com
)。为了验证特定SPN的kerberos票证,您必须拥有一个keytab文件,其中包含Kerberos域控制器[KDC]票证授予票证[TGT]服务和服务提供商(您)已知的共享密钥。
就Active Directory而言,KDC是域控制器,共享密钥只是拥有SPN的帐户的纯文本密码。 SPN可以由AD中的计算机或用户对象拥有。
如果要定义服务,在AD中设置SPN的最简单方法是设置基于用户的SPN,如下所示:
ReallyLongRandomPass
使用Windows setspn
实用程序将服务SPN绑定到帐户。最佳做法是为主机的短名称和FQDN定义多个SPN:
setspn -U -S HTTP/myserver@MYDOMAIN SVC_HTTP_MYSERVER
setspn -U -S HTTP/myserver.my-domain.com@MYDOMAIN SVC_HTTP_MYSERVER
使用Java的ktab
实用程序为帐户生成密钥表。
ktab -k FILE:http_myserver.ktab -a HTTP/myserver@MYDOMAIN ReallyLongRandomPass
ktab -k FILE:http_myserver.ktab -a HTTP/myserver.my-domain.com@MYDOMAIN ReallyLongRandomPass
如果您尝试验证绑定到计算机帐户或您无法控制的用户帐户的预先存在的SPN,则上述操作无效。您需要从ActiveDirectory本身提取密钥表。 Wireshark Kerberos Page有一些很好的指示。
在%JAVA_HOME%/jre/lib/security
中创建一个描述您的域名的krb5.conf。确保您在此处定义的领域与您为SPN设置的领域相匹配。如果不将文件放在JVM目录中,可以通过在命令行上设置-Djava.security.krb5.conf=C:\path\to\krb5.conf
来指向它。
示例:
[libdefaults]
default_realm = MYDOMAIN
[realms]
MYDOMAIN = {
kdc = dc1.my-domain.com
default_domain = my-domain.com
}
[domain_realm]
.my-domain.com = MYDOMAIN
my-domain.com = MYDOMAIN
您的JAAS login.conf
应定义一个登录配置,将 Krb5LoginModule 设置为接受者。这是一个假设我们在上面创建的keytab在C:\http_myserver.ktab
中的示例。通过在命令行上设置-Djava.security.auth.login.config=C:\path\to\login.conf
指向JASS配置文件。
http_myserver_mydomain {
com.sun.security.auth.module.Krb5LoginModule required
principal="HTTP/myserver.my-domain.com@MYDOMAIN"
doNotPrompt="true"
useKeyTab="true"
keyTab="C:/http_myserver.ktab"
storeKey="true"
isInitiator="false";
};
或者,您可以在运行时生成JAAS配置,如下所示:
public static Configuration getJaasKrb5TicketCfg(
final String principal, final String realm, final File keytab) {
return new Configuration() {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
Map<String, String> options = new HashMap<String, String>();
options.put("principal", principal);
options.put("keyTab", keytab.getAbsolutePath());
options.put("doNotPrompt", "true");
options.put("useKeyTab", "true");
options.put("storeKey", "true");
options.put("isInitiator", "false");
return new AppConfigurationEntry[] {
new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
LoginModuleControlFlag.REQUIRED, options)
};
}
};
}
您可以为此配置创建 LoginContext ,如下所示:
LoginContext ctx = new LoginContext("doesn't matter", subject, null,
getJaasKrbValidationCfg("HTTP/myserver.my-domain.com@MYDOMAIN", "MYDOMAIN",
new File("C:/path/to/my.ktab")));
这是一个小小的袖手旁观,但一般的想法是定义一个使用故障单执行SPNEGO协议的PriviledgedAction。请注意,此示例不检查SPNEGO协议是否完整。例如,如果客户端请求服务器身份验证,则需要在HTTP响应中的身份验证标头中返回由acceptSecContext()
生成的令牌。
public class Krb5TicketValidateAction implements PrivilegedExceptionAction<String> {
public Krb5TicketValidateAction(byte[] ticket, String spn) {
this.ticket = ticket;
this.spn = spn;
}
@Override
public String run() throws Exception {
final Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
GSSManager gssmgr = GSSManager.getInstance();
// tell the GSSManager the Kerberos name of the service
GSSName serviceName = gssmgr.createName(this.spn, GSSName.NT_USER_NAME);
// get the service's credentials. note that this run() method was called by Subject.doAs(),
// so the service's credentials (Service Principal Name and password) are already
// available in the Subject
GSSCredential serviceCredentials = gssmgr.createCredential(serviceName,
GSSCredential.INDEFINITE_LIFETIME, spnegoOid, GSSCredential.ACCEPT_ONLY);
// create a security context for decrypting the service ticket
GSSContext gssContext = gssmgr.createContext(serviceCredentials);
// decrypt the service ticket
System.out.println("Entering accpetSecContext...");
gssContext.acceptSecContext(this.ticket, 0, this.ticket.length);
// get the client name from the decrypted service ticket
// note that Active Directory created the service ticket, so we can trust it
String clientName = gssContext.getSrcName().toString();
// clean up the context
gssContext.dispose();
// return the authenticated client name
return clientName;
}
private final byte[] ticket;
private final String spn;
}
然后要验证票证,您将执行以下操作。假设ticket
包含来自身份验证标头的已经基本64位解码的票证。如果格式为spn
,则Host
应从HTTP请求中的HTTP/<HOST>@<REALM>
标头派生。例如。如果Host
标题为myserver.my-domain.com
,则spn
应为HTTP/myserver.my-domain.com@MYDOMAIN
。
public boolean isTicketValid(String spn, byte[] ticket) {
LoginContext ctx = null;
try {
// this is the name from login.conf. This could also be a parameter
String ctxName = "http_myserver_mydomain";
// define the principal who will validate the ticket
Principal principal = new KerberosPrincipal(spn, KerberosPrincipal.KRB_NT_SRV_INST);
Set<Principal> principals = new HashSet<Principal>();
principals.add(principal);
// define the subject to execute our secure action as
Subject subject = new Subject(false, principals, new HashSet<Object>(),
new HashSet<Object>());
// login the subject
ctx = new LoginContext("http_myserver_mydomain", subject);
ctx.login();
// create a validator for the ticket and execute it
Krb5TicketValidateAction validateAction = new Krb5TicketValidateAction(ticket, spn);
String username = Subject.doAs(subject, validateAction);
System.out.println("Validated service ticket for user " + username
+ " to access service " + spn );
return true;
} catch(PriviledgedActionException e ) {
System.out.println("Invalid ticket for " + spn + ": " + e);
} catch(LoginException e) {
System.out.println("Error creating validation LoginContext for "
+ spn + ": " + e);
} finally {
try {
if(ctx!=null) { ctx.logout(); }
} catch(LoginException e) { /* noop */ }
}
return false;
}
答案 1 :(得分:6)
这不是Kerberos票证,而是SPNEGO票证。你的上下文有错误的机制。
编辑:虽然,您现在拥有正确的机制,但您的客户端正在向您发送GSL-API无法处理的NTLM令牌。使用Base 64令牌,解码为原始字节并显示ASCII字符。如果它以NTLMSSP
开头,则无法确定并且您已经破坏了Kerberos设置。
编辑2:这是你的票:
60 81 82 06 06 2B 06 01 05 05 02 A0 78 30 76 A0 30 30 2E 06 `..+..... x0v 00..
0A 2B 06 01 04 01 82 37 02 02 0A 06 09 2A 86 48 82 F7 12 01 .+....7.....*H÷..
02 02 06 09 2A 86 48 86 F7 12 01 02 02 06 0A 2B 06 01 04 01 ....*H÷......+....
82 37 02 02 1E A2 42 04 40 4E 54 4C 4D 53 53 50 00 01 00 00 7...¢B.@NTLMSSP....
00 97 B2 08 E2 0E 00 0E 00 32 00 00 00 0A 00 0A 00 28 00 00 .².â....2.......(..
00 06 01 B1 1D 00 00 00 0F 4C 41 50 54 4F 50 2D 32 34 35 4C ...±.....LAPTOP-245L
49 46 45 41 43 43 4F 55 4E 54 4C 4C 43 IFEACCOUNTLLC
这是SPNEGO令牌中的包装NTLM令牌。这仅仅意味着Kerberos由于某些原因而失败,例如,
最好的选择是在客户端上使用Wireshark查找根本原因。
请注意,Java 不支持NTLM作为SPNEGO子机制。 NTLM仅得到SSPI和Heimdal的支持。
答案 2 :(得分:1)
如果服务器没有注册KDC的密钥表和关联密钥,您将永远无法使用kerberos来验证故障单。
让SPNEGO工作是最棘手的,如果没有至少粗略地了解kerberos如何工作,几乎是不可能的。尝试阅读此对话框,看看是否可以更好地理解。
http://web.mit.edu/kerberos/dialogue.html
SPNEGO需要HTTP / server.example.com格式的SPN,您需要告诉GSS库启动服务器时该密钥表的位置。