我写了一些代码来验证服务器上客户的kerberos票。我还为班级编写了单元测试。通过模拟对GSS库类的调用来编写单元测试。但是,由于模拟了实际的GSS调用,所以这并没有给我足够的信心。
到目前为止,我的研究表明,为了验证客户端令牌,我需要使用与KDC共享的密钥对它进行解密,该密钥可以从keytab文件中获得。因此,为了执行验证,我需要做两件事(需要纠正的立场):
现在,如果我在类路径中有这些文件,是否可以在没有任何模拟调用的情况下对令牌进行实际验证?这样做有技术上的挑战吗?如果是,那他们是什么?
似乎我还需要设置一些系统属性,以便GSS库选择正确的领域,kdc等。因此,实质上,我们需要三件事:
有了这个,我似乎可以通过验证进行端到端的测试工作,但是只能进行5分钟。 :)
这种情况是,如果我捡起KDC新鲜生成的kerberos令牌并将其放入测试中,则测试可以成功运行,但5分钟后会开始失败,但“时钟偏斜太大”除外。我更改了KDC上的kerberos策略,以生成永不过期的凭单,但错误仍然存在。这里的一线希望是,现在我有了一种方法可行的概念证明。
问题归结为克服了“时钟偏斜太大”错误。
这是堆栈跟踪:
Caused by: java.security.PrivilegedActionException: GSSException: Failure unspecified at GSS-API level (Mechanism level: Clock skew too great (37))
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:422)
at com.example.vidm.eks.request.KerberosTokenValidator.getPrincipalUserName(KerberosTokenValidator.java:91)
at com.example.vidm.eks.request.KerberosTokenValidator.lambda$validateToken$0(KerberosTokenValidator.java:80)
... 7 more
Caused by: GSSException: Failure unspecified at GSS-API level (Mechanism level: Clock skew too great (37))
at sun.security.jgss.krb5.Krb5Context.acceptSecContext(Krb5Context.java:856)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:342)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:285)
at sun.security.jgss.spnego.SpNegoContext.GSS_acceptSecContext(SpNegoContext.java:906)
at sun.security.jgss.spnego.SpNegoContext.acceptSecContext(SpNegoContext.java:556)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:342)
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:285)
at com.example.vidm.eks.krb.KerberosValidateAction.run(KerberosValidateAction.java:47)
at com.example.vidm.eks.krb.KerberosValidateAction.run(KerberosValidateAction.java:22)
... 11 more
Caused by: KrbException: Clock skew too great (37)
at sun.security.krb5.KrbApReq.authenticate(KrbApReq.java:302)
at sun.security.krb5.KrbApReq.<init>(KrbApReq.java:149)
at sun.security.jgss.krb5.InitSecContextToken.<init>(InitSecContextToken.java:108)
at sun.security.jgss.krb5.Krb5Context.acceptSecContext(Krb5Context.java:829)
... 19 more
我的功能测试代码:
public class KerberosTokenValidatorTest extends AbstractUnitTestBase {
public static final String NO_PRINCIPAL = null;
private String kerberosTicket;
public static final String USERNAME = "username";
private static final String REALM = "EXAMPLE.COM";
private static final String PRINCIPAL = USERNAME + "@" + REALM;
@BeforeClass
public void beforeClass(){
System.setProperty("java.security.krb5.kdc", "host/hw-99402.example.com");
System.setProperty("java.security.krb5.realm", "EXAMPLE.COM");
System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
}
@Test
public void myTest() throws IOException, GSSException, ExecutionException, InterruptedException {
KerberosTokenValidator kerberosTokenValidator = new KerberosTokenValidator();
String kticket = FileSystemUtils.loadClasspathResourceAsString("kerberosticket");
kerberosTokenValidator.validateToken(kticket, "hw-99402.example.com", "userPrincipalName").get();
}
}
我的验证码:
private String getPrincipalUserName(String token1, String serverName) throws LoginException, PrivilegedActionException {
javax.security.auth.Subject serviceSubject = getServiceSubject(serverName);
byte[] token = base64Decoder.decode(token1);
KerberosTicketValidation ticketValidation = javax.security.auth.Subject.doAs(serviceSubject, new KerberosValidateAction(token));
String kdcPrincipal = ticketValidation.getUsername();
if (StringUtils.isBlank(kdcPrincipal)) {
throw new LoginException("KDC principal is blank after ticket validation");
}
return kdcPrincipal;
}
private javax.security.auth.Subject getServiceSubject(String serverName) throws LoginException {
String servicePrincipal = SERVICE_PRINCIPAL_SERVICE + "/" + serverName;
final Set<Principal> princ = new HashSet<>(1);
princ.add(new KerberosPrincipal(servicePrincipal));
javax.security.auth.Subject sub = new javax.security.auth.Subject(false, princ, Collections.emptySet(), Collections.emptySet());
KerberosConfig kerberosConfig = new KerberosConfig(KEYTAB_PATH, servicePrincipal);
LoginContext lc = new LoginContext("", sub, null, kerberosConfig);
lc.login();
return lc.getSubject();
}
我的单元测试:
@BeforeMethod
public void setup() throws Exception {
reset(mockGSSContext, mockGSSManager, mockGSSName);
mockGSSManager();
}
@InjectMocks
private KerberosTokenValidator kerberosTokenValidator;
@Mock protected GSSManager mockGSSManager;
@Mock protected GSSContext mockGSSContext;
@Mock protected GSSName mockGSSName;
@Test
public void canValidateKerberosToken() throws Throwable {
when(mockGSSName.toString()).thenReturn(PRINCIPAL);
Subject subject = blockAndThrow(kerberosTokenValidator.validateToken(kerberosTicket, "hw-99402.vidmlabs.com", "sAMAccountName"));
Assert.assertEquals(subject.getNameId(), USERNAME);
}
private void mockGSSManager() throws Exception {
when(mockGSSManager.createContext((GSSCredential) null)).thenReturn(mockGSSContext);
when(mockGSSContext.isEstablished()).thenReturn(true);
when(mockGSSContext.acceptSecContext(any(byte[].class), anyInt(), anyInt())).thenReturn(null);
when(mockGSSContext.getSrcName()).thenReturn(mockGSSName);
KerberosValidateAction.setGssManager(mockGSSManager);
}
KerberosValidateAction:
public class KerberosValidateAction implements PrivilegedExceptionAction<KerberosTicketValidation> {
private static GSSManager gssManager = GSSManager.getInstance();
private byte[] kerberosTicket;
private GSSCredential serviceCredentials;
public KerberosValidateAction(byte[] kerberosTicket) {
this(kerberosTicket, null);
}
public KerberosValidateAction(byte[] kerberosTicket, GSSCredential serviceCredentials) {
this.kerberosTicket = kerberosTicket;
this.serviceCredentials = serviceCredentials;
}
@VisibleForTesting
public static void setGssManager(GSSManager manager) {
gssManager = manager;
}
@Override
public KerberosTicketValidation run() throws Exception {
GSSName gssName = null;
GSSContext context = gssManager.createContext(serviceCredentials);
byte[] token = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
if (!context.isEstablished()) {
throw new ContinueNeededException(token);
}
gssName = context.getSrcName();
if (gssName == null) {
throw new AuthenticationException("GSSContext name of the context initiator is null");
}
context.dispose();
return new KerberosTicketValidation(gssName.toString());
}
}
答案 0 :(得分:0)
执行端到端测试所需的关键组件是
除了这些服务器之外,还需要告知服务器与这些密钥表和令牌文件相对应的默认KDC和REALM值是什么。可以使用系统属性
指定这些这些已经到位的用于验证票证的GSS API仍然会产生时钟偏斜错误,因为Kerberos希望确保在5分钟的间隔内获得票证。没有直接的系统属性可以设置来修改此值。但是您可以有一个自定义的krb5.conf文件,指定要使用此文件的系统属性,然后将值放在该文件中。
krb5.conf文件:
[libdefaults]
clockskew = 999999999
实际上,其他值(kdc和realm)也可以在此文件中指定。使用此文件也将意味着必须将其写入磁盘以供库使用。 (尚未找到更好的方法)。
更直接的方法可能是使用票证生存期值而不是修改时钟偏斜,但是我所有的尝试都未能使用票证生存期来覆盖时钟偏斜。时钟偏移似乎完全忽略了票证的寿命。为此主题创建了另一个question。但是,对于测试时差情况,可以忽略时差替代,对于其他情况,可以使用较大的值将其作为默认测试配置。
该问题中所有要设置的属性均已更新。