如何验证kerberos令牌?
How to verify kerberos token?
我又遇到了一些 AD 和 Kerberos 问题。
好吧,我从 WWW-Authenticate header 获得了一个 kerberos 令牌。现在我想根据 AD 验证此令牌,但我不知道如何。
我从 GSSAPI 中找到了一些东西,但没有看到将 byte[] 用作 Kerberos 令牌或任何其他方式的函数或方法。
我正在 运行 宁 Java EE 网络应用程序。
我可以用这个令牌做什么来获取用户,尤其是来自 AD 的 "this token and user are legit"?
编辑:
正如评论中所说,我真的很接近能够执行 SSO。所以我会用我所拥有的更新你们。
我有一台名为 ping01 的服务器和我的本地计算机。 运行 在 Tomcat 作为用户管理我有我的应用程序。所以我做了这一切:
在用户管理处创建了 SPN HTTP/ping01.cool.domain@COOL.DOMAIN。
krb5.conf:
[libdefaults]
default_tkt_enctypes = aes256-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
default_tgs_enctypes = aes256-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
permitted_enctypes = aes256-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
default_realm = COOL.DOMAIN
kdc_timesync = 1
ccache_type = 5
forwardable = true
proxiable = true
[realms]
COOL.DOMAIN = {
kdc = kdc.cool.domain
admin_server = COOL.DOMAIN
default_domain = COOL.DOMAIN
}
[domain_realm]
cool.domain = COOL.DOMAIN
ping01 = COOL.DOMAIN
[login]
krb4_convert = true
krb4_get_tickets = false
我也有这个代码:
/**
* Gets the jaas krb 5 ticket cfg.
*
* @param principal the principal
* @param realm the realm
* @param keyTab the key tab
* @return the jaas krb 5 ticket cfg
*/
private 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<>();
options.put( "principal", principal );
options.put( "realm", realm );
options.put( "doNotPrompt", "true" );
options.put( "useKeyTab", "true" );
options.put( "keyTab", keyTab.getAbsolutePath() );
options.put( "storeKey", "true" );
options.put( "isInitiator", "false" );
return new AppConfigurationEntry[] {
new AppConfigurationEntry( "com.sun.security.auth.module.Krb5LoginModule", LoginModuleControlFlag.REQUIRED, options ) };
}
};
}
/** {@inheritDoc} */
@Override
public boolean isTicketValid( String spn, byte[] ticket )
{
LoginContext ctx = null;
try
{
/** define the principal who will validate the ticket */
Principal principal = new KerberosPrincipal( spn, KerberosPrincipal.KRB_NT_SRV_INST );
Set<Principal> principals = new HashSet<>();
principals.add( principal );
/** define the subject to execute our secure action as */
Subject subject = new Subject( false, principals, new HashSet<>(), new HashSet<>() );
/** login the subject */
/**
* TODO: Find the correct way to use the commented out version!
*/
// ctx = new LoginContext( "http_ping01_domain", subject );
ctx = new LoginContext( "doesn't matter", subject, null,
getJaasKrb5TicketCfg( "HTTP/ping01.cool.domain@COOL.DOMAIN", "COOL.DOMAIN",
new File( "http_ping01_test.ktab" ) ) );
ctx.login();
/** create a validator for the ticket and execute it */
SingleSignOnImpl validateAction = new SingleSignOnImpl( ticket, spn, log );
String username = Subject.doAs( subject, validateAction );
log.info( "Validated service ticket for user " + username + " to access service " + spn );
return true;
}
catch ( PrivilegedActionException e )
{
/**
* Error reasons for this Exception: - Incorrect Kerberos Mechanism - Incorrect Token received
* - Incorrect keytab
*/
log.error( "Invalid ticket for " + spn + ": " + e );
}
catch ( LoginException e )
{
/**
* Error reasons for this Exception: - False krb5.conf (can't reach KDC) - incorrect SPN -
* False login.conf
*/
log.error( "Error creating validation LoginContext for " + spn + ": " + e );
}
finally
{
try
{
if ( ctx != null )
{
ctx.logout();
}
}
catch ( LoginException e )
{
log.error( "" + e );
}
}
return false;
}
除此之外,我还有单独的特权操作 class:
public class SingleSignOnImpl implements PrivilegedExceptionAction<String>
{
/** The ticket from the client. */
private final byte[] ticket;
/** The Service principal name (SPN). */
private final String spn;
/** The log. */
private Log log;
/**
* Inits the.
*
* @param log the log
*/
@Inject
public void init( Log log )
{
this.log = log;
}
/**
* Instantiates a new single sign on impl.
*
* @param ticket the ticket
* @param spn the spn
*/
public SingleSignOnImpl( byte[] ticket, String spn, Log log )
{
this.ticket = ticket;
this.spn = spn;
this.log = log;
}
/** {@inheritDoc} */
@Override
public String run() throws Exception
{
/**
* Kerberos V5 Mechanism or SPNEGO required. the Legacy mechanism is NOT supported. SPNEGO
* (1.3.6.1.5.5.2); Kerberos V5 (1.2.840.113554.1.2.2)
*/
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 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 */
log.info( "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();
log.info( "request from Client {0}", clientName );
/** clean up the context. This is very important */
gssContext.dispose();
return clientName;
}
}
我的日志吐出这个:
AUTHTOKEN: YIIG2gYGKw......
2016-11-08 14:52:34,269 INFO Entering accpetSecContext...
2016-11-08 14:52:34,269 ERROR Invalid ticket for HTTP/ping01.cool.domain@COOL.DOMAIN: java.security.PrivilegedActionException: GSSException: Failure unspecified at GSS-API level (Mechanism level: Checksum failed)
2016-11-08 14:52:34,269 INFO VALID: false
但好消息是我获得了 Kerberos 令牌。他进入上下文并可以解密它。在 cmd 中使用 klist 时,我什至看到了我的服务的缓存票证。如此处所示:directUpload
Active Directory 和 KDC 运行 很顺利,当我请求访问服务的票证时没有显示错误或警告。
带有令牌的 byte[] 取自 header 的 httprequest,并使用 Base64.getMimeDecoder.decode();
从字符串解码为 byte[]
有人看出我的错误了吗?重新启动并清除票证缓存后,我仍然收到消息。
编辑 2:
我深入挖掘并尝试使用 ktab 和 kinit。当尝试使用 kinit 获取 SPN 的票时,我 运行 进入:
kinit -J-Dsun.security.krb5.debug=true -k -t http_ping01.ktab HTTP/ping01.cool.domain@COOL.DOMAIN
>>>KinitOptions cache name is C:\Users\Nico.DOMAIN\XXXX_Nico
Principal is HTTP/ping01.cool.domain@COOL.DOMAIN
>>> Kinit using keytab
>>> Kinit keytab file name: http_ping01.ktab
Java config name: null
LSA: Found Ticket
LSA: Made NewWeakGlobalRef
LSA: Found PrincipalName
LSA: Made NewWeakGlobalRef
LSA: Found DerValue
LSA: Made NewWeakGlobalRef
LSA: Found EncryptionKey
LSA: Made NewWeakGlobalRef
LSA: Found TicketFlags
LSA: Made NewWeakGlobalRef
LSA: Found KerberosTime
LSA: Made NewWeakGlobalRef
LSA: Found String
LSA: Made NewWeakGlobalRef
LSA: Found DerValue constructor
LSA: Found Ticket constructor
LSA: Found PrincipalName constructor
LSA: Found EncryptionKey constructor
LSA: Found TicketFlags constructor
LSA: Found KerberosTime constructor
LSA: Finished OnLoad processing
Native config name: C:\Windows\krb5.ini
Loaded from native config
>>> Kinit realm name is COOL.DOMAIN
>>> Creating KrbAsReq
>>> KrbKdcReq local addresses for my_computer are:
[My addresses]
>>> KdcAccessibility: reset
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01.cool.domain
>>> KeyTab: load() entry length: 98; type: 18
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01.cool.domain
>>> KeyTab: load() entry length: 82; type: 17
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01.cool.domain
>>> KeyTab: load() entry length: 82; type: 23
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01.cool.domain
>>> KeyTab: load() entry length: 90; type: 16
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01
>>> KeyTab: load() entry length: 80; type: 18
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01
>>> KeyTab: load() entry length: 64; type: 17
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01
>>> KeyTab: load() entry length: 64; type: 23
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01
>>> KeyTab: load() entry length: 72; type: 16
Looking for keys for: HTTP/ping01.cool.domain@COOL.DOMAIN
Added key: 16version: 2
Added key: 23version: 2
Added key: 17version: 2
Added key: 18version: 2
default etypes for default_tkt_enctypes: 18 17 23 16.
>>> KrbAsReq creating message
>>> KrbKdcReq send: kdc=KDC.cool.domain UDP:88, timeout=30000, number of retries =3, #bytes=270
>>> KDCCommunication: kdc=KDC.cool.domain UDP:88, timeout=30000,Attempt =1, #bytes=270
>>> KrbKdcReq send: #bytes read=106
>>> KdcAccessibility: remove KDC.cool.domain
>>> KDCRep: init() encoding tag is 126 req type is 11
>>>KRBError:
sTime is Wed Nov 09 10:54:46 CET 2016 1478685286000
suSec is 578393
error code is 6
error Message is Client not found in Kerberos database
sname is ActiveDirectory/COOL.DOMAIN@COOL.DOMAIN
msgType is 30
Exception: krb_error 6 Client not found in Kerberos database (6) Client not found in Kerberos database
KrbException: Client not found in Kerberos database (6)
at sun.security.krb5.KrbAsRep.<init>(KrbAsRep.java:76)
at sun.security.krb5.KrbAsReqBuilder.send(KrbAsReqBuilder.java:316)
at sun.security.krb5.KrbAsReqBuilder.action(KrbAsReqBuilder.java:361)
at sun.security.krb5.internal.tools.Kinit.<init>(Kinit.java:219)
at sun.security.krb5.internal.tools.Kinit.main(Kinit.java:113)
Caused by: KrbException: Identifier doesn't match expected value (906)
at sun.security.krb5.internal.KDCRep.init(KDCRep.java:140)
at sun.security.krb5.internal.ASRep.init(ASRep.java:64)
at sun.security.krb5.internal.ASRep.<init>(ASRep.java:59)
at sun.security.krb5.KrbAsRep.<init>(KrbAsRep.java:60)
... 4 more
所以我查看了活动目录中的 object ping01。它已经有一堆 servicePrincipalName 属性:
servicePrincipalName: someService/PING01.cool.domain
servicePrincipalName: someService/PING01
servicePrincipalName: anotherService/PING01.cool.domain
servicePrincipalName: anotherService/PING01
servicePrincipalName: HOST/PING01.cool.domain
servicePrincipalName: HOST/PING01
使用 setspn -l mgmt
输出我创建的 SPN。只是在 ldapBrowser 中根本不可见。
我不确定 object Ping01 (objectClass=computer) 是否有密码,必须等待系统管理员的回答。
编辑 3:
我发现这一定是某种 SPN 问题或至少是 AD 问题。
从 EDIT 2: 你可以看到即使是 windows 本机工具 kinit 也无法执行身份验证,因为 kdc 正在发送消息他不认识用户。为什么他以用户身份向 SPN 声明我不清楚,但打开更多调试选项给了我这个输出:
YIIG2gYGKwYBBQUCoIIGzjCCBsqgMDAuBgkqhkiC9xIBAgIGCSqGSIb3EgECAgYKKwYBBAGCNwICHgYKKwYBBAGCNwICCqKCBpQEggaQYIIGjAYJKoZIhvcSAQICAQBuggZ7MIIGd6ADAgEFoQMCAQ6iBwMFACAAAACjggUGYYIFAjCCBP6gAwIBBaETGxFGRUxURU5HUk9VUC5MT0NBTKIrMCmgAwIBAqEiMCAbBEhUVFAbGHBpbmcwMS5mZWx0ZW5ncm91cC5sb2NhbKOCBLMwggSvoAMCAR[...]
INFO [stdout] Debug is true storeKey true useTicketCache false useKeyTab true doNotPrompt true ticketCache is null isInitiator false KeyTab is C:\path\to\http_ping01_test.ktab refreshKrb5Config is false principal is HTTP/ping01.cool.domain@COOL.DOMAIN tryFirstPass is false useFirstPass is false storePass is false clearPass is false
INFO [stdout] principal is HTTP/ping01.cool.domain@COOL.DOMAIN
INFO [stdout] Will use keytab
INFO [stdout] Commit Succeeded
INFO [stdout] Found KeyTab C:\path\to\http_ping01_test.ktab for HTTP/ping01.cool.domain@COOL.DOMAIN
INFO Entering accpetSecContext...
INFO [stdout] Entered SpNegoContext.acceptSecContext with state=STATE_NEW
INFO [stdout] SpNegoContext.acceptSecContext: receiving token = a0 82 06 ce 30 82 06 ca a0 30 30 2e 06 09 2a 86 48 82 f7 12 01 02 02 06 09 2a 86 48 86 f7 12 01 02 02 06 0a 2b 06 01 04 01 82 37 02 02 1e 06 0a 2b 06 01 04 01 82 37 02 02 0a a2 82 06 94 04 82 06 90 60 82 06 8c 06 09 2a 86 48 86 f7 12 01 02 02 01 00 6e 82 06 7b 30 82 06 77 a0 03 02 01 05 a1 03 02 01 0e a2 07 03 05 00 20 00 00 00 a3 82 05 06 61 82 05 02 30 82 04 fe a0 03 02 01 05 a1 13 1b 11 46 45 4c 54 45 4e 47 52 4f 55 50 2e 4c 4f 43 41 4c a2 2b 30 29 a0 03 02 01 02 a1 22 30 20 1b 04 48 54 54 50 1b 18 70 69 6e 67 30 31 2e 66 65 6c 74 65 6e 67 72 6f 75 70 2e 6c 6f 63 61 6c a3 82 04 b3 30 82 04 af a0 03 02 01 12 a1 03 02 01 0f a2 82 04 a1 04 82 04 9d e6 c0 24 8d 0d 24 8e e1 4e e8 0d 4e 4d 5b 7e 06 58 d9 f2 04 a6 99 55 e2 61 67 99 60 ec 47 42 7d 60 64 4d bc f7 ef 99 5b f0 3e b8 2f 9a ff 2d 83 19 6d f1 5f ac 44 08 f3 50 d5 c9 53 af 6f d9 d6 81 c1 d7 24 03 6a 9d b4 9d 56 53 93 b3 1d 07 15 77 c5 fb 25 0f bc f8 97 8f 97 0c 26 ae 52 d0 fc f3 72 98 9c 79 4b af e2 88 3b a6 2b 1b 03 b0 93 b6 6a dd b3 c6 f8 c2 01 eb a4 1b 8a 64 74 cb 5b f4 4b 5c d7 02 48 1d 0d 5e 29 3d 2b 82 c5 79 a1 7a e1 4c 92 32 7c 6b f6 56 ff e1 3a 3f b7 ce 0c 92 f8 ae ce 03 f2 f5 18 53 5c 5b 08 07 60 d7 c0 38 7d d0 f5 fa 2b 63 97 61 75 86 b6 95 44 49 76 93 38 88 82 7f 90 07 d7 3d c9 bd c6 c7 b3 af 47 55 cc b0 1a cd 2a e8 4e d0 b9 42 9e 65 3e aa 88 ac b5 25 45 39 20 0f 3c 50 ed 2d 1a f5 24 04 5a 15 99 c9 2e c1 c6 40 4e 26 ea f2 c6 a9 bd 61 24 fc d4 25 6e ed c2 40 3a d6 18 9b 53 ac 4d a1 61 d2 12 aa 99 e1 90 6e 22 c9 14 82 49 78 43 ab 83 a1 60 a3 d0 1d 33 24 11 41 07 4d bb 9c 0e 38 e1 3c 86 6a 62 bc 2f 7c 47 34 b7 42 3e 28 2e 9b 26 66 a1 e8 61 5f 00 61 8a b9 2b 5b 9e b2 aa 1a 4d e7 4e d2 6d 52 e1 25 c4 89 ea 6e 85 1c 1a 56 e0 d9 a2 be 9f 7c ee 89 55 b8 39 cf b9 92 77 33 2d fa 64 29 50 38 2d 6d d7 9d be be 3c e2 04 4c 5c 3e 3b d1 09 39 08 bd 75 5b 9f 6a 89 32 f8 b2 a9 c7 a3 a1 de ca ea fd 62 18 7d df 5e 50 b5 8e 48 71 ec 66 70 ff 0e 1c 40 2a ad 9e f4 c4 15 45 ca 1b 15 b8 0e 30 76 76 9b 81 39 5b 94 c4 0a ec e0 a7 b4 ec 32 9a 4a 9d 74 86 a3 81 5a 91 8c 51 e1 5a f1 b8 44 fa 9d cc 16 34 c5 99 fb 7b 33 bc 06 99 51 9e ec 19 60 88 [...]
INFO [stdout] SpNegoToken NegTokenInit: reading Mechanism Oid = 1.2.840.48018.1.2.2
INFO [stdout] SpNegoToken NegTokenInit: reading Mechanism Oid = 1.2.840.113554.1.2.2
INFO [stdout] SpNegoToken NegTokenInit: reading Mechanism Oid = 1.3.6.1.4.1.311.2.2.30
INFO [stdout] SpNegoToken NegTokenInit: reading Mechanism Oid = 1.3.6.1.4.1.311.2.2.10
INFO [stdout] SpNegoToken NegTokenInit: reading Mech Token
INFO [stdout] SpNegoContext.acceptSecContext: received token of type = SPNEGO NegTokenInit
INFO [stdout] SpNegoContext: negotiated mechanism = 1.2.840.113554.1.2.2
INFO [stdout] SpNegoContext.acceptSecContext: negotiated mech adjusted to 1.2.840.48018.1.2.2
INFO [stdout] Entered Krb5Context.acceptSecContext with state=STATE_NEW
INFO [stdout] Looking for keys for: HTTP/ping01.cool.domain@COOL.DOMAIN
INFO [stdout] Added key: 16version: 2
INFO [stdout] Added key: 23version: 2
INFO [stdout] Added key: 17version: 2
INFO [stdout] Added key: 18version: 2
INFO [stdout] >>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
ERROR Invalid ticket for HTTP/ping01.cool.domain@COOL.DOMAIN: java.security.PrivilegedActionException: GSSException: Failure unspecified at GSS-API level (Mechanism level: Checksum failed)
INFO [stdout] [Krb5LoginModule]: Entering logout
INFO [stdout] [Krb5LoginModule]: logged out Subject
安全令牌的实际检查过程 - 包含 Kerberos 票证 - 发生在您的应用程序服务器上 - 它从不联系 AD。 GSSAPI 安全函数处理这个——您不需要为此编写代码。您可以公开令牌(看起来像一串随机的字母),但只有密钥表才能对其进行解密。当您(作为应用程序服务器)从用户那里获得 Kerberos 票证(身份验证令牌)时,您知道该用户是合法的 - 用户首先不会获得票证,除非他们的身份已经被 AD 证明 - 就是这样Kerberos 有效。查看此 URL 了解更多信息:http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/single-signon.html
我根据您编辑的问题做出的一些新观察:
- SPN 错误。您提供的格式 "SPN HTTP/ping01.domain@DOMAIN" 缺少两个标签,一个在中间的主机部分,一个在最后的领域部分。如果你打算那样使用它,应该像 "HTTP/ping01.domain.com@DOMAIN.COM"。正确运行 DNS 并正确配置 krb5.conf 后,SPN 实际上可以是这样的:"HTTP/ping01.domain.com".
像这样添加新的 SPN:
setspn -S HTTP/ping01.domain.com domain\account
(setspn -S 在添加前查找重复项,而 setspn -A 现在查找)
krb5.conf有错误。你有:
default_realm = 域
应该是:
default_realm = DOMAIN.COM
在 krb5.conf 的下方,在 [realms] 和 {domain_realm] 下,所有名称引用也需要完全限定。
这也适用于您的代码块中的相同引用,我注意到其中缺少标签的名称,例如:
getJaasKrb5TicketCfg( "HTTP/ping01.domain@DOMAIN", "DOMAIN",
应该是:
getJaasKrb5TicketCfg( "HTTP/ping01.domain.com@DOMAIN.COM", "DOMAIN.COM",
Kerberos 极度依赖 DNS,所有代码中的所有名称引用都应完全限定,krb5.conf。
所以我开始工作了。
问题是创建的 SPN 未被识别或至少未正确解析。因此,我在尝试解密密钥时遇到校验和错误。
在 technet 社区的帮助下,我们做对了。
我这样创建了 SPN:
setspn -a HTTP/ping01.cool.domain@COOL.DOMAIN mgmt
但这是不正确的,设置 SPN 的正确形式是:
setspn -a HTTP/ping01.cool.domain domain\mgmt
这个小而简单的差异让我的服务发挥了所有魔力。
希望有一天这能对某人有所帮助。
我又遇到了一些 AD 和 Kerberos 问题。
好吧,我从 WWW-Authenticate header 获得了一个 kerberos 令牌。现在我想根据 AD 验证此令牌,但我不知道如何。
我从 GSSAPI 中找到了一些东西,但没有看到将 byte[] 用作 Kerberos 令牌或任何其他方式的函数或方法。
我正在 运行 宁 Java EE 网络应用程序。
我可以用这个令牌做什么来获取用户,尤其是来自 AD 的 "this token and user are legit"?
编辑:
正如评论中所说,我真的很接近能够执行 SSO。所以我会用我所拥有的更新你们。
我有一台名为 ping01 的服务器和我的本地计算机。 运行 在 Tomcat 作为用户管理我有我的应用程序。所以我做了这一切:
在用户管理处创建了 SPN HTTP/ping01.cool.domain@COOL.DOMAIN。
krb5.conf:
[libdefaults]
default_tkt_enctypes = aes256-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
default_tgs_enctypes = aes256-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
permitted_enctypes = aes256-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
default_realm = COOL.DOMAIN
kdc_timesync = 1
ccache_type = 5
forwardable = true
proxiable = true
[realms]
COOL.DOMAIN = {
kdc = kdc.cool.domain
admin_server = COOL.DOMAIN
default_domain = COOL.DOMAIN
}
[domain_realm]
cool.domain = COOL.DOMAIN
ping01 = COOL.DOMAIN
[login]
krb4_convert = true
krb4_get_tickets = false
我也有这个代码:
/**
* Gets the jaas krb 5 ticket cfg.
*
* @param principal the principal
* @param realm the realm
* @param keyTab the key tab
* @return the jaas krb 5 ticket cfg
*/
private 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<>();
options.put( "principal", principal );
options.put( "realm", realm );
options.put( "doNotPrompt", "true" );
options.put( "useKeyTab", "true" );
options.put( "keyTab", keyTab.getAbsolutePath() );
options.put( "storeKey", "true" );
options.put( "isInitiator", "false" );
return new AppConfigurationEntry[] {
new AppConfigurationEntry( "com.sun.security.auth.module.Krb5LoginModule", LoginModuleControlFlag.REQUIRED, options ) };
}
};
}
/** {@inheritDoc} */
@Override
public boolean isTicketValid( String spn, byte[] ticket )
{
LoginContext ctx = null;
try
{
/** define the principal who will validate the ticket */
Principal principal = new KerberosPrincipal( spn, KerberosPrincipal.KRB_NT_SRV_INST );
Set<Principal> principals = new HashSet<>();
principals.add( principal );
/** define the subject to execute our secure action as */
Subject subject = new Subject( false, principals, new HashSet<>(), new HashSet<>() );
/** login the subject */
/**
* TODO: Find the correct way to use the commented out version!
*/
// ctx = new LoginContext( "http_ping01_domain", subject );
ctx = new LoginContext( "doesn't matter", subject, null,
getJaasKrb5TicketCfg( "HTTP/ping01.cool.domain@COOL.DOMAIN", "COOL.DOMAIN",
new File( "http_ping01_test.ktab" ) ) );
ctx.login();
/** create a validator for the ticket and execute it */
SingleSignOnImpl validateAction = new SingleSignOnImpl( ticket, spn, log );
String username = Subject.doAs( subject, validateAction );
log.info( "Validated service ticket for user " + username + " to access service " + spn );
return true;
}
catch ( PrivilegedActionException e )
{
/**
* Error reasons for this Exception: - Incorrect Kerberos Mechanism - Incorrect Token received
* - Incorrect keytab
*/
log.error( "Invalid ticket for " + spn + ": " + e );
}
catch ( LoginException e )
{
/**
* Error reasons for this Exception: - False krb5.conf (can't reach KDC) - incorrect SPN -
* False login.conf
*/
log.error( "Error creating validation LoginContext for " + spn + ": " + e );
}
finally
{
try
{
if ( ctx != null )
{
ctx.logout();
}
}
catch ( LoginException e )
{
log.error( "" + e );
}
}
return false;
}
除此之外,我还有单独的特权操作 class:
public class SingleSignOnImpl implements PrivilegedExceptionAction<String>
{
/** The ticket from the client. */
private final byte[] ticket;
/** The Service principal name (SPN). */
private final String spn;
/** The log. */
private Log log;
/**
* Inits the.
*
* @param log the log
*/
@Inject
public void init( Log log )
{
this.log = log;
}
/**
* Instantiates a new single sign on impl.
*
* @param ticket the ticket
* @param spn the spn
*/
public SingleSignOnImpl( byte[] ticket, String spn, Log log )
{
this.ticket = ticket;
this.spn = spn;
this.log = log;
}
/** {@inheritDoc} */
@Override
public String run() throws Exception
{
/**
* Kerberos V5 Mechanism or SPNEGO required. the Legacy mechanism is NOT supported. SPNEGO
* (1.3.6.1.5.5.2); Kerberos V5 (1.2.840.113554.1.2.2)
*/
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 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 */
log.info( "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();
log.info( "request from Client {0}", clientName );
/** clean up the context. This is very important */
gssContext.dispose();
return clientName;
}
}
我的日志吐出这个:
AUTHTOKEN: YIIG2gYGKw......
2016-11-08 14:52:34,269 INFO Entering accpetSecContext...
2016-11-08 14:52:34,269 ERROR Invalid ticket for HTTP/ping01.cool.domain@COOL.DOMAIN: java.security.PrivilegedActionException: GSSException: Failure unspecified at GSS-API level (Mechanism level: Checksum failed)
2016-11-08 14:52:34,269 INFO VALID: false
但好消息是我获得了 Kerberos 令牌。他进入上下文并可以解密它。在 cmd 中使用 klist 时,我什至看到了我的服务的缓存票证。如此处所示:directUpload
Active Directory 和 KDC 运行 很顺利,当我请求访问服务的票证时没有显示错误或警告。
带有令牌的 byte[] 取自 header 的 httprequest,并使用 Base64.getMimeDecoder.decode();
从字符串解码为 byte[]有人看出我的错误了吗?重新启动并清除票证缓存后,我仍然收到消息。
编辑 2:
我深入挖掘并尝试使用 ktab 和 kinit。当尝试使用 kinit 获取 SPN 的票时,我 运行 进入:
kinit -J-Dsun.security.krb5.debug=true -k -t http_ping01.ktab HTTP/ping01.cool.domain@COOL.DOMAIN
>>>KinitOptions cache name is C:\Users\Nico.DOMAIN\XXXX_Nico
Principal is HTTP/ping01.cool.domain@COOL.DOMAIN
>>> Kinit using keytab
>>> Kinit keytab file name: http_ping01.ktab
Java config name: null
LSA: Found Ticket
LSA: Made NewWeakGlobalRef
LSA: Found PrincipalName
LSA: Made NewWeakGlobalRef
LSA: Found DerValue
LSA: Made NewWeakGlobalRef
LSA: Found EncryptionKey
LSA: Made NewWeakGlobalRef
LSA: Found TicketFlags
LSA: Made NewWeakGlobalRef
LSA: Found KerberosTime
LSA: Made NewWeakGlobalRef
LSA: Found String
LSA: Made NewWeakGlobalRef
LSA: Found DerValue constructor
LSA: Found Ticket constructor
LSA: Found PrincipalName constructor
LSA: Found EncryptionKey constructor
LSA: Found TicketFlags constructor
LSA: Found KerberosTime constructor
LSA: Finished OnLoad processing
Native config name: C:\Windows\krb5.ini
Loaded from native config
>>> Kinit realm name is COOL.DOMAIN
>>> Creating KrbAsReq
>>> KrbKdcReq local addresses for my_computer are:
[My addresses]
>>> KdcAccessibility: reset
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01.cool.domain
>>> KeyTab: load() entry length: 98; type: 18
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01.cool.domain
>>> KeyTab: load() entry length: 82; type: 17
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01.cool.domain
>>> KeyTab: load() entry length: 82; type: 23
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01.cool.domain
>>> KeyTab: load() entry length: 90; type: 16
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01
>>> KeyTab: load() entry length: 80; type: 18
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01
>>> KeyTab: load() entry length: 64; type: 17
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01
>>> KeyTab: load() entry length: 64; type: 23
>>> KeyTabInputStream, readName(): COOL.DOMAIN
>>> KeyTabInputStream, readName(): HTTP
>>> KeyTabInputStream, readName(): ping01
>>> KeyTab: load() entry length: 72; type: 16
Looking for keys for: HTTP/ping01.cool.domain@COOL.DOMAIN
Added key: 16version: 2
Added key: 23version: 2
Added key: 17version: 2
Added key: 18version: 2
default etypes for default_tkt_enctypes: 18 17 23 16.
>>> KrbAsReq creating message
>>> KrbKdcReq send: kdc=KDC.cool.domain UDP:88, timeout=30000, number of retries =3, #bytes=270
>>> KDCCommunication: kdc=KDC.cool.domain UDP:88, timeout=30000,Attempt =1, #bytes=270
>>> KrbKdcReq send: #bytes read=106
>>> KdcAccessibility: remove KDC.cool.domain
>>> KDCRep: init() encoding tag is 126 req type is 11
>>>KRBError:
sTime is Wed Nov 09 10:54:46 CET 2016 1478685286000
suSec is 578393
error code is 6
error Message is Client not found in Kerberos database
sname is ActiveDirectory/COOL.DOMAIN@COOL.DOMAIN
msgType is 30
Exception: krb_error 6 Client not found in Kerberos database (6) Client not found in Kerberos database
KrbException: Client not found in Kerberos database (6)
at sun.security.krb5.KrbAsRep.<init>(KrbAsRep.java:76)
at sun.security.krb5.KrbAsReqBuilder.send(KrbAsReqBuilder.java:316)
at sun.security.krb5.KrbAsReqBuilder.action(KrbAsReqBuilder.java:361)
at sun.security.krb5.internal.tools.Kinit.<init>(Kinit.java:219)
at sun.security.krb5.internal.tools.Kinit.main(Kinit.java:113)
Caused by: KrbException: Identifier doesn't match expected value (906)
at sun.security.krb5.internal.KDCRep.init(KDCRep.java:140)
at sun.security.krb5.internal.ASRep.init(ASRep.java:64)
at sun.security.krb5.internal.ASRep.<init>(ASRep.java:59)
at sun.security.krb5.KrbAsRep.<init>(KrbAsRep.java:60)
... 4 more
所以我查看了活动目录中的 object ping01。它已经有一堆 servicePrincipalName 属性:
servicePrincipalName: someService/PING01.cool.domain
servicePrincipalName: someService/PING01
servicePrincipalName: anotherService/PING01.cool.domain
servicePrincipalName: anotherService/PING01
servicePrincipalName: HOST/PING01.cool.domain
servicePrincipalName: HOST/PING01
使用 setspn -l mgmt
输出我创建的 SPN。只是在 ldapBrowser 中根本不可见。
我不确定 object Ping01 (objectClass=computer) 是否有密码,必须等待系统管理员的回答。
编辑 3: 我发现这一定是某种 SPN 问题或至少是 AD 问题。 从 EDIT 2: 你可以看到即使是 windows 本机工具 kinit 也无法执行身份验证,因为 kdc 正在发送消息他不认识用户。为什么他以用户身份向 SPN 声明我不清楚,但打开更多调试选项给了我这个输出:
YIIG2gYGKwYBBQUCoIIGzjCCBsqgMDAuBgkqhkiC9xIBAgIGCSqGSIb3EgECAgYKKwYBBAGCNwICHgYKKwYBBAGCNwICCqKCBpQEggaQYIIGjAYJKoZIhvcSAQICAQBuggZ7MIIGd6ADAgEFoQMCAQ6iBwMFACAAAACjggUGYYIFAjCCBP6gAwIBBaETGxFGRUxURU5HUk9VUC5MT0NBTKIrMCmgAwIBAqEiMCAbBEhUVFAbGHBpbmcwMS5mZWx0ZW5ncm91cC5sb2NhbKOCBLMwggSvoAMCAR[...]
INFO [stdout] Debug is true storeKey true useTicketCache false useKeyTab true doNotPrompt true ticketCache is null isInitiator false KeyTab is C:\path\to\http_ping01_test.ktab refreshKrb5Config is false principal is HTTP/ping01.cool.domain@COOL.DOMAIN tryFirstPass is false useFirstPass is false storePass is false clearPass is false
INFO [stdout] principal is HTTP/ping01.cool.domain@COOL.DOMAIN
INFO [stdout] Will use keytab
INFO [stdout] Commit Succeeded
INFO [stdout] Found KeyTab C:\path\to\http_ping01_test.ktab for HTTP/ping01.cool.domain@COOL.DOMAIN
INFO Entering accpetSecContext...
INFO [stdout] Entered SpNegoContext.acceptSecContext with state=STATE_NEW
INFO [stdout] SpNegoContext.acceptSecContext: receiving token = a0 82 06 ce 30 82 06 ca a0 30 30 2e 06 09 2a 86 48 82 f7 12 01 02 02 06 09 2a 86 48 86 f7 12 01 02 02 06 0a 2b 06 01 04 01 82 37 02 02 1e 06 0a 2b 06 01 04 01 82 37 02 02 0a a2 82 06 94 04 82 06 90 60 82 06 8c 06 09 2a 86 48 86 f7 12 01 02 02 01 00 6e 82 06 7b 30 82 06 77 a0 03 02 01 05 a1 03 02 01 0e a2 07 03 05 00 20 00 00 00 a3 82 05 06 61 82 05 02 30 82 04 fe a0 03 02 01 05 a1 13 1b 11 46 45 4c 54 45 4e 47 52 4f 55 50 2e 4c 4f 43 41 4c a2 2b 30 29 a0 03 02 01 02 a1 22 30 20 1b 04 48 54 54 50 1b 18 70 69 6e 67 30 31 2e 66 65 6c 74 65 6e 67 72 6f 75 70 2e 6c 6f 63 61 6c a3 82 04 b3 30 82 04 af a0 03 02 01 12 a1 03 02 01 0f a2 82 04 a1 04 82 04 9d e6 c0 24 8d 0d 24 8e e1 4e e8 0d 4e 4d 5b 7e 06 58 d9 f2 04 a6 99 55 e2 61 67 99 60 ec 47 42 7d 60 64 4d bc f7 ef 99 5b f0 3e b8 2f 9a ff 2d 83 19 6d f1 5f ac 44 08 f3 50 d5 c9 53 af 6f d9 d6 81 c1 d7 24 03 6a 9d b4 9d 56 53 93 b3 1d 07 15 77 c5 fb 25 0f bc f8 97 8f 97 0c 26 ae 52 d0 fc f3 72 98 9c 79 4b af e2 88 3b a6 2b 1b 03 b0 93 b6 6a dd b3 c6 f8 c2 01 eb a4 1b 8a 64 74 cb 5b f4 4b 5c d7 02 48 1d 0d 5e 29 3d 2b 82 c5 79 a1 7a e1 4c 92 32 7c 6b f6 56 ff e1 3a 3f b7 ce 0c 92 f8 ae ce 03 f2 f5 18 53 5c 5b 08 07 60 d7 c0 38 7d d0 f5 fa 2b 63 97 61 75 86 b6 95 44 49 76 93 38 88 82 7f 90 07 d7 3d c9 bd c6 c7 b3 af 47 55 cc b0 1a cd 2a e8 4e d0 b9 42 9e 65 3e aa 88 ac b5 25 45 39 20 0f 3c 50 ed 2d 1a f5 24 04 5a 15 99 c9 2e c1 c6 40 4e 26 ea f2 c6 a9 bd 61 24 fc d4 25 6e ed c2 40 3a d6 18 9b 53 ac 4d a1 61 d2 12 aa 99 e1 90 6e 22 c9 14 82 49 78 43 ab 83 a1 60 a3 d0 1d 33 24 11 41 07 4d bb 9c 0e 38 e1 3c 86 6a 62 bc 2f 7c 47 34 b7 42 3e 28 2e 9b 26 66 a1 e8 61 5f 00 61 8a b9 2b 5b 9e b2 aa 1a 4d e7 4e d2 6d 52 e1 25 c4 89 ea 6e 85 1c 1a 56 e0 d9 a2 be 9f 7c ee 89 55 b8 39 cf b9 92 77 33 2d fa 64 29 50 38 2d 6d d7 9d be be 3c e2 04 4c 5c 3e 3b d1 09 39 08 bd 75 5b 9f 6a 89 32 f8 b2 a9 c7 a3 a1 de ca ea fd 62 18 7d df 5e 50 b5 8e 48 71 ec 66 70 ff 0e 1c 40 2a ad 9e f4 c4 15 45 ca 1b 15 b8 0e 30 76 76 9b 81 39 5b 94 c4 0a ec e0 a7 b4 ec 32 9a 4a 9d 74 86 a3 81 5a 91 8c 51 e1 5a f1 b8 44 fa 9d cc 16 34 c5 99 fb 7b 33 bc 06 99 51 9e ec 19 60 88 [...]
INFO [stdout] SpNegoToken NegTokenInit: reading Mechanism Oid = 1.2.840.48018.1.2.2
INFO [stdout] SpNegoToken NegTokenInit: reading Mechanism Oid = 1.2.840.113554.1.2.2
INFO [stdout] SpNegoToken NegTokenInit: reading Mechanism Oid = 1.3.6.1.4.1.311.2.2.30
INFO [stdout] SpNegoToken NegTokenInit: reading Mechanism Oid = 1.3.6.1.4.1.311.2.2.10
INFO [stdout] SpNegoToken NegTokenInit: reading Mech Token
INFO [stdout] SpNegoContext.acceptSecContext: received token of type = SPNEGO NegTokenInit
INFO [stdout] SpNegoContext: negotiated mechanism = 1.2.840.113554.1.2.2
INFO [stdout] SpNegoContext.acceptSecContext: negotiated mech adjusted to 1.2.840.48018.1.2.2
INFO [stdout] Entered Krb5Context.acceptSecContext with state=STATE_NEW
INFO [stdout] Looking for keys for: HTTP/ping01.cool.domain@COOL.DOMAIN
INFO [stdout] Added key: 16version: 2
INFO [stdout] Added key: 23version: 2
INFO [stdout] Added key: 17version: 2
INFO [stdout] Added key: 18version: 2
INFO [stdout] >>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
ERROR Invalid ticket for HTTP/ping01.cool.domain@COOL.DOMAIN: java.security.PrivilegedActionException: GSSException: Failure unspecified at GSS-API level (Mechanism level: Checksum failed)
INFO [stdout] [Krb5LoginModule]: Entering logout
INFO [stdout] [Krb5LoginModule]: logged out Subject
安全令牌的实际检查过程 - 包含 Kerberos 票证 - 发生在您的应用程序服务器上 - 它从不联系 AD。 GSSAPI 安全函数处理这个——您不需要为此编写代码。您可以公开令牌(看起来像一串随机的字母),但只有密钥表才能对其进行解密。当您(作为应用程序服务器)从用户那里获得 Kerberos 票证(身份验证令牌)时,您知道该用户是合法的 - 用户首先不会获得票证,除非他们的身份已经被 AD 证明 - 就是这样Kerberos 有效。查看此 URL 了解更多信息:http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/single-signon.html
我根据您编辑的问题做出的一些新观察:
- SPN 错误。您提供的格式 "SPN HTTP/ping01.domain@DOMAIN" 缺少两个标签,一个在中间的主机部分,一个在最后的领域部分。如果你打算那样使用它,应该像 "HTTP/ping01.domain.com@DOMAIN.COM"。正确运行 DNS 并正确配置 krb5.conf 后,SPN 实际上可以是这样的:"HTTP/ping01.domain.com".
像这样添加新的 SPN:
setspn -S HTTP/ping01.domain.com domain\account
(setspn -S 在添加前查找重复项,而 setspn -A 现在查找)
krb5.conf有错误。你有:
default_realm = 域
应该是:
default_realm = DOMAIN.COM
在 krb5.conf 的下方,在 [realms] 和 {domain_realm] 下,所有名称引用也需要完全限定。
这也适用于您的代码块中的相同引用,我注意到其中缺少标签的名称,例如:
getJaasKrb5TicketCfg( "HTTP/ping01.domain@DOMAIN", "DOMAIN",
应该是:
getJaasKrb5TicketCfg( "HTTP/ping01.domain.com@DOMAIN.COM", "DOMAIN.COM",
Kerberos 极度依赖 DNS,所有代码中的所有名称引用都应完全限定,krb5.conf。
所以我开始工作了。 问题是创建的 SPN 未被识别或至少未正确解析。因此,我在尝试解密密钥时遇到校验和错误。
在 technet 社区的帮助下,我们做对了。
我这样创建了 SPN:
setspn -a HTTP/ping01.cool.domain@COOL.DOMAIN mgmt
但这是不正确的,设置 SPN 的正确形式是:
setspn -a HTTP/ping01.cool.domain domain\mgmt
这个小而简单的差异让我的服务发挥了所有魔力。
希望有一天这能对某人有所帮助。