java 使用 GSSManager 验证 Kerberos 票证
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25289231/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using GSSManager to validate a Kerberos ticket
提问by Josh C.
I have the following code:
我有以下代码:
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();
}
}
Of course it fails. Here's the exception:
当然失败了。这是例外:
GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
I don't know what I'm supposed to do to solve this. Honestly, I don't really understand Kerberos.
我不知道我该怎么做才能解决这个问题。老实说,我不太了解 Kerberos。
I got this ticket by sending a 401 with the appropriate header WWW-Authenticate
with 'Negotiate' as the value. The browser immediately issued the same request again with an authorization
header containing this ticket.
我通过发送带有适当标头的 401 并WWW-Authenticate
以“协商”作为值来获得这张票。浏览器立即再次使用authorization
包含此票证的标头再次发出相同的请求。
I was hoping I could validate the ticket and determine who the user is.
我希望我可以验证票证并确定用户是谁。
Do I need a keytab file? If so, what credentials would I run this under? I'm trying to use the Kerberos ticket for auth for a web-site. Would the credentials be the credentials from IIS?
我需要密钥表文件吗?如果是这样,我将在什么凭据下运行它?我正在尝试使用 Kerberos 票证对网站进行身份验证。凭据是来自 IIS 的凭据吗?
What am I missing?
我错过了什么?
Update 1From Michael-O's reply, I did a bit more googling and found this article, which led me to this article.
更新 1从 Michael-O 的回复中,我做了更多的谷歌搜索并找到了这篇文章,这让我找到了这篇文章。
On table 3
, I found 1.3.6.1.5.5.2 SPNEGO
.
上table 3
,我找到了1.3.6.1.5.5.2 SPNEGO
。
I have now added that to my credentials following the example from the first article. Here's my code:
我现在按照第一篇文章中的示例将其添加到我的凭据中。这是我的代码:
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();
}
}
But now the code is failing on createCredential
with this error:
但现在代码失败并createCredential
出现此错误:
GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos credentails)
Here's the entire ticket: YIGCBgYrBgEFBQKgeDB2oDAwLgYKKwYBBAGCNwICCgYJKoZIgvcSAQICBgkqhkiG9xIBAgIGCisGAQQBgjcCAh6iQgRATlRMTVNTUAABAAAAl7II4g4ADgAyAAAACgAKACgAAAAGAbEdAAAAD0xBUFRPUC0yNDVMSUZFQUNDT1VOVExMQw==
这是整个门票: YIGCBgYrBgEFBQKgeDB2oDAwLgYKKwYBBAGCNwICCgYJKoZIgvcSAQICBgkqhkiG9xIBAgIGCisGAQQBgjcCAh6iQgRATlRMTVNTUAABAAAAl7II4g4ADgAyAAAACgAKACgAAAAGAbEdAAAAD0xBUFRPUC0yNDVMSUZFQUNDT1VOVExMQw==
回答by Dominic A.
Validating an SPNEGO ticket from Java is a somewhat convoluted process. Here's a brief overview but bear in mind that the process can have tons of pitfalls. You really need to understand how Active Directory, Kerberos, SPNEGO, and JAAS all operate to successfully diagnose problems.
从 Java 验证 SPNEGO 票证是一个有点复杂的过程。这是一个简短的概述,但请记住,该过程可能存在大量陷阱。您确实需要了解 Active Directory、Kerberos、SPNEGO 和 JAAS 如何成功诊断问题。
Before you start, make sure you know your kerberos realm name for your windows domain. For the purposes of this answer I'll assume it's MYDOMAIN. You can obtain the realm name by running echo %userdnsdomain%
from a cmd window. Note that kerberos is case sensitiveand the realm is almost always ALL CAPS.
在开始之前,请确保您知道 Windows 域的 kerberos 领域名称。出于这个答案的目的,我假设它是MYDOMAIN。您可以通过echo %userdnsdomain%
从 cmd 窗口运行来获取领域名称。请注意,kerberos区分大小写,并且领域几乎总是全部大写。
Step 1 - Obtain a Kerberos Keytab
步骤 1 - 获取 Kerberos Keytab
In order for a kerberos client to access a service, it requests a ticket for the Service Principal Name [SPN] that represents that service. SPNs are generally derived from the machine name and the type of service being accessed (e.g. HTTP/www.my-domain.com
). In order to validate a kerberos ticket for a particular SPN, you must have a keytab file that contains a shared secret known to both the Kerberos Domain Controller [KDC] Ticket Granting Ticket [TGT] service and the service provider (you).
为了让 kerberos 客户端访问服务,它请求代表该服务的服务主体名称 [SPN] 的票证。SPN 通常源自机器名称和正在访问的服务类型(例如HTTP/www.my-domain.com
)。为了验证特定 SPN 的 kerberos 票证,您必须有一个密钥表文件,其中包含 Kerberos 域控制器 [KDC] 票证授予票证 [TGT] 服务和服务提供商(您)都知道的共享机密。
In terms of Active Directory, the KDC is the Domain Controller, and the shared secret is just the plain text password of the account that owns the SPN. A SPN may be owned by either a Computer or a User object within the AD.
在 Active Directory 方面,KDC 是域控制器,共享密钥只是拥有 SPN的帐户的纯文本密码。SPN 可以由 AD 中的计算机或用户对象拥有。
The easiest way to setup a SPN in AD if you are defining a service is to setup a user-based SPN like so:
如果您要定义服务,则在 AD 中设置 SPN 的最简单方法是设置基于用户的 SPN,如下所示:
- Create an unpriviledged service account in AD whose password doesn't expire e.g. SVC_HTTP_MYSERVER with password
ReallyLongRandomPass
Bind the service SPN to the account using the windows
setspn
utility. Best practice is to define multiple SPNs for both the short name and the FQDN of the host:setspn -U -S HTTP/myserver@MYDOMAIN SVC_HTTP_MYSERVER setspn -U -S HTTP/myserver.my-domain.com@MYDOMAIN SVC_HTTP_MYSERVER
Generate a keytab for the account using Java's
ktab
utility.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
- 在 AD 中创建一个密码不会过期的非特权服务帐户,例如带有密码的 SVC_HTTP_MYSERVER
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
If you are trying to authenticate a pre-existing SPN that is bound to a Computer account or to a User account you do not control, the above will not work. You will need to extract the keytab from ActiveDirectory itself. The Wireshark Kerberos Pagehas some good pointers for this.
如果您尝试对绑定到计算机帐户或您无法控制的用户帐户的预先存在的 SPN 进行身份验证,则上述方法将不起作用。您需要从 ActiveDirectory 本身提取密钥表。该Wireshark的Kerberos的页面有一些这方面的良好指针。
Step 2 - Setup your krb5.conf
第 2 步 - 设置您的 krb5.conf
In %JAVA_HOME%/jre/lib/security
create a krb5.conf that describes your domain. Make sure the realm you define here matches what you setup for your SPN. If you don't put the file in the JVM directory, you can point to it by setting -Djava.security.krb5.conf=C:\path\to\krb5.conf
on the command line.
在%JAVA_HOME%/jre/lib/security
创建描述您的域的 krb5.conf 中。确保您在此处定义的领域与您为 SPN 设置的领域相匹配。如果不把文件放在JVM目录下,可以-Djava.security.krb5.conf=C:\path\to\krb5.conf
在命令行中设置指向。
Example:
例子:
[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
Step 3 - Setup JAAS login.conf
第 3 步 - 设置 JAAS login.conf
Your JAAS login.conf
should define a login configuration that sets up the Krb5LoginModuleas a acceptor. Here's an example that assumes that the keytab we created above is in C:\http_myserver.ktab
. Point to the JASS config file by setting -Djava.security.auth.login.config=C:\path\to\login.conf
on the command line.
您的 JAASlogin.conf
应该定义一个登录配置,将Krb5LoginModule 设置为接受器。这是一个假设我们上面创建的密钥表在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";
};
Alternatively, you can generate a JAAS config at runtime like so:
或者,您可以在运行时生成 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)
};
}
};
}
You would create a LoginContextfor this configuration like so:
您将为该配置创建一个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")));
Step 4 - Accepting the ticket
第 4 步 - 接受票
This is a little off-the-cuff, but the general idea is to define a PriviledgedAction that performs the SPNEGO protocol using the ticket. Note that this example does not check that SPNEGO protocol is complete. For example if the client requested server authentication, you would need to return the token generated by acceptSecContext()
in the authentication header in the HTTP response.
这有点随意,但总体思路是定义一个 PriviledgedAction,它使用票证执行 SPNEGO 协议。请注意,此示例不检查 SPNEGO 协议是否完整。例如,如果客户端请求服务器身份验证,您将需要返回acceptSecContext()
HTTP 响应中的身份验证标头中生成的令牌。
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;
}
Then to authenticate the ticket, you would do something like the following. Assume that ticket
contains the already-base-64-decoded ticket from the authentication header. The spn
should be derived from the Host
header in the HTTP request if the format of HTTP/<HOST>@<REALM>
. E.g. if the Host
header was myserver.my-domain.com
then spn
should be HTTP/myserver.my-domain.com@MYDOMAIN
.
然后要验证票证,您将执行以下操作。假设它ticket
包含来自身份验证标头的已用 base-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;
}
回答by Michael-O
This is not a Kerberos ticket but a SPNEGO ticket. Your context has the wrong mechanism.
这不是 Kerberos 票证,而是 SPNEGO 票证。您的上下文有错误的机制。
Edit: Though, you now have the correct mech, you client is sending you a NTLM token which the GSS-API is not able to process. Take the Base 64 token, decode to raw bytes and display ASCII chars. If it starts with NTLMSSP
, it won't work for sure and you have broken Kerberos setup.
编辑:虽然,您现在拥有正确的机械,但您的客户端正在向您发送 GSS-API 无法处理的 NTLM 令牌。获取 Base 64 标记,解码为原始字节并显示 ASCII 字符。如果它以 开头NTLMSSP
,则肯定无法正常工作,并且您破坏了 Kerberos 设置。
Edit 2: This is your ticket:
编辑 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.a....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
This is a wrapped NTLM token inside a SPNEGO token. which simply means that Kerberos has failed for some reasons, e.g.,
这是一个封装在 SPNEGO 令牌中的 NTLM 令牌。这只是意味着 Kerberos 由于某些原因而失败,例如,
- SPN not registered
- Clockskew
- Not allowed for Kerberos
- Incorrect DNS records
- SPN 未注册
- 时钟偏移
- 不允许用于 Kerberos
- DNS 记录不正确
Best option is to use Wireshark on the client to find the root cause.
最好的选择是在客户端上使用 Wireshark 来查找根本原因。
Please note that Java does notsupport NTLM as a SPNEGO submechanism. NTLM is only supported by SSPI and Heimdal.
请注意,Java并没有支持NTLM的SPNEGO submechanism。NTLM 仅受 SSPI 和 Heimdal 支持。
回答by Fred the Magic Wonder Dog
If the server does not have a keytab and associated key registered the KDC, you will never be able use kerberos to validate a ticket.
如果服务器没有在 KDC 上注册的密钥表和关联的密钥,您将永远无法使用 kerberos 来验证票证。
Getting SPNEGO to work is tricky at best and will be next to impossible without at least a cursory understanding of how kerberos works. Try reading this dialog and see if you can get a better understanding.
使 SPNEGO 工作至多是棘手的,如果不至少粗略地了解 kerberos 的工作原理,几乎是不可能的。尝试阅读此对话框,看看您是否能更好地理解。
http://web.mit.edu/kerberos/dialogue.html
http://web.mit.edu/kerberos/dialogue.html
SPNEGO requires an SPN of the form HTTP/server.example.com and you'll need to tell the GSS libraries where that keytab is when you start the server.
SPNEGO 需要格式为 HTTP/server.example.com 的 SPN,并且您需要在启动服务器时告诉 GSS 库该密钥表的位置。