Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ Main features:
- Proper caching of tickets - make just one request to domain controller and cache the ticket on both client and server
sides
- Flexible SPN resolution - you own the code and do not have to rely on magic SPN calculation algorithms
- **NEW**: Pluggable Kerberos implementations - choose between JDK GSS API and Apache Kerby

## Kerberos Implementation Support

Kerb4J now supports multiple Kerberos implementations through a pluggable integration layer:

- **JDK Implementation**: Uses built-in JDK GSS API (default, mature)
- **Apache Kerby**: Pure Java implementation, cross-platform

See [Integration Layer Documentation](kerb4j-integration/README.md) for details.

Java Compatibility
========
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.kerb4j.common.integration;

import com.kerb4j.client.SpnegoClient;

/**
* Internal adapter class for integration layer.
* This class is only used when integration modules are available on the classpath.
*/
class IntegrationAdapter {

/**
* Create a SpnegoClient using integration layer with username/password.
*/
static SpnegoClient loginWithUsernamePassword(String username, String password, boolean useCache) {
try {
// Use reflection to avoid compile-time dependency on integration modules
Class<?> providerClass = Class.forName("com.kerb4j.integration.api.KerberosClientProvider");
Object factory = providerClass.getMethod("getDefaultFactory").invoke(null);

Class<?> factoryClass = Class.forName("com.kerb4j.integration.api.KerberosClientFactory");
Object kerberosClient = factoryClass.getMethod("loginWithUsernamePassword", String.class, String.class, boolean.class)
.invoke(factory, username, password, useCache);

// If it's a JDK implementation, extract the underlying SpnegoClient
if (kerberosClient.getClass().getName().contains("jdk")) {
Object spnegoClient = kerberosClient.getClass().getMethod("getSpnegoClient").invoke(kerberosClient);
return (SpnegoClient) spnegoClient;
} else {
// For non-JDK implementations, we would need to create an adapter
// For now, fall back to original implementation
return SpnegoClient.loginWithUsernamePassword(username, password, useCache);
}
} catch (Exception e) {
// Fall back to original implementation on any error
return SpnegoClient.loginWithUsernamePassword(username, password, useCache);
}
}

/**
* Create a SpnegoClient using integration layer with keytab.
*/
static SpnegoClient loginWithKeyTab(String principal, String keyTabLocation, boolean acceptOnly) {
try {
Class<?> providerClass = Class.forName("com.kerb4j.integration.api.KerberosClientProvider");
Object factory = providerClass.getMethod("getDefaultFactory").invoke(null);

Class<?> factoryClass = Class.forName("com.kerb4j.integration.api.KerberosClientFactory");
Object kerberosClient = factoryClass.getMethod("loginWithKeyTab", String.class, String.class, boolean.class)
.invoke(factory, principal, keyTabLocation, acceptOnly);

// If it's a JDK implementation, extract the underlying SpnegoClient
if (kerberosClient.getClass().getName().contains("jdk")) {
Object spnegoClient = kerberosClient.getClass().getMethod("getSpnegoClient").invoke(kerberosClient);
return (SpnegoClient) spnegoClient;
} else {
// For non-JDK implementations, fall back to original implementation
return SpnegoClient.loginWithKeyTab(principal, keyTabLocation, acceptOnly);
}
} catch (Exception e) {
// Fall back to original implementation on any error
return SpnegoClient.loginWithKeyTab(principal, keyTabLocation, acceptOnly);
}
}

/**
* Create a SpnegoClient using integration layer with ticket cache.
*/
static SpnegoClient loginWithTicketCache(String principal) {
try {
Class<?> providerClass = Class.forName("com.kerb4j.integration.api.KerberosClientProvider");
Object factory = providerClass.getMethod("getDefaultFactory").invoke(null);

Class<?> factoryClass = Class.forName("com.kerb4j.integration.api.KerberosClientFactory");
Object kerberosClient = factoryClass.getMethod("loginWithTicketCache", String.class)
.invoke(factory, principal);

// If it's a JDK implementation, extract the underlying SpnegoClient
if (kerberosClient.getClass().getName().contains("jdk")) {
Object spnegoClient = kerberosClient.getClass().getMethod("getSpnegoClient").invoke(kerberosClient);
return (SpnegoClient) spnegoClient;
} else {
// For non-JDK implementations, fall back to original implementation
return SpnegoClient.loginWithTicketCache(principal);
}
} catch (Exception e) {
// Fall back to original implementation on any error
return SpnegoClient.loginWithTicketCache(principal);
}
}

/**
* Get the current implementation name.
*/
static String getCurrentImplementationName() {
try {
Class<?> providerClass = Class.forName("com.kerb4j.integration.api.KerberosClientProvider");
Object factory = providerClass.getMethod("getDefaultFactory").invoke(null);

Class<?> factoryClass = Class.forName("com.kerb4j.integration.api.KerberosClientFactory");
return (String) factoryClass.getMethod("getImplementationName").invoke(factory);
} catch (Exception e) {
return "JDK";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.kerb4j.common.integration;

import com.kerb4j.client.SpnegoClient;
import com.kerb4j.client.SpnegoContext;

import java.net.URL;
import java.security.PrivilegedActionException;

/**
* Factory class that provides SpnegoClient instances with optional integration layer support.
* This allows users to switch between different Kerberos implementations if integration modules
* are available on the classpath, while maintaining backward compatibility.
*/
public class SpnegoClientFactory {

private static final String INTEGRATION_API_CLASS = "com.kerb4j.integration.api.KerberosClientProvider";
private static final boolean INTEGRATION_AVAILABLE = isIntegrationAvailable();

private static boolean isIntegrationAvailable() {
try {
Class.forName(INTEGRATION_API_CLASS);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}

/**
* Create a SpnegoClient using username and password.
* Uses integration layer if available, otherwise falls back to original implementation.
*
* @param username the username
* @param password the password
* @return SpnegoClient instance
*/
public static SpnegoClient loginWithUsernamePassword(String username, String password) {
return loginWithUsernamePassword(username, password, false);
}

/**
* Create a SpnegoClient using username and password with caching option.
* Uses integration layer if available, otherwise falls back to original implementation.
*
* @param username the username
* @param password the password
* @param useCache whether to cache the client
* @return SpnegoClient instance
*/
public static SpnegoClient loginWithUsernamePassword(String username, String password, boolean useCache) {
if (INTEGRATION_AVAILABLE) {
return IntegrationAdapter.loginWithUsernamePassword(username, password, useCache);
} else {
return SpnegoClient.loginWithUsernamePassword(username, password, useCache);
}
}

/**
* Create a SpnegoClient using keytab authentication.
* Uses integration layer if available, otherwise falls back to original implementation.
*
* @param principal the principal name
* @param keyTabLocation the keytab file location
* @return SpnegoClient instance
*/
public static SpnegoClient loginWithKeyTab(String principal, String keyTabLocation) {
return loginWithKeyTab(principal, keyTabLocation, false);
}

/**
* Create a SpnegoClient using keytab authentication with accept-only option.
* Uses integration layer if available, otherwise falls back to original implementation.
*
* @param principal the principal name
* @param keyTabLocation the keytab file location
* @param acceptOnly when true, client works offline for accepting tokens only
* @return SpnegoClient instance
*/
public static SpnegoClient loginWithKeyTab(String principal, String keyTabLocation, boolean acceptOnly) {
if (INTEGRATION_AVAILABLE) {
return IntegrationAdapter.loginWithKeyTab(principal, keyTabLocation, acceptOnly);
} else {
return SpnegoClient.loginWithKeyTab(principal, keyTabLocation, acceptOnly);
}
}

/**
* Create a SpnegoClient using ticket cache authentication.
* Uses integration layer if available, otherwise falls back to original implementation.
*
* @param principal the principal name
* @return SpnegoClient instance
*/
public static SpnegoClient loginWithTicketCache(String principal) {
if (INTEGRATION_AVAILABLE) {
return IntegrationAdapter.loginWithTicketCache(principal);
} else {
return SpnegoClient.loginWithTicketCache(principal);
}
}

/**
* Check if integration layer is available on the classpath.
* @return true if integration modules are available
*/
public static boolean isIntegrationLayerAvailable() {
return INTEGRATION_AVAILABLE;
}

/**
* Get the name of the currently used Kerberos implementation.
* @return implementation name, or "JDK" if integration layer is not available
*/
public static String getCurrentImplementationName() {
if (INTEGRATION_AVAILABLE) {
return IntegrationAdapter.getCurrentImplementationName();
} else {
return "JDK";
}
}
}
Loading
Loading