用法

需要使用 Kerberos 进行身份验证的客户端应用程序代码必须先向 KDC 进行身份验证并获取 Neo4j 服务的服务票据(在本例中为:neo4j/neo4j.windomain.local@WINDOMAIN.LOCAL)。该服务票据必须使用 Kerberos v5 (1.2.840.113554.1.2.2) 机制,既可以直接使用,也可以包装在 SPNEGO (1.3.6.1.5.5.2) 中。

应将服务票据以认证令牌的形式提供给 Neo4j 驱动,令牌应包含以下属性

  • 主体(Principal):空

  • 凭证(Credentials):Base64 编码的服务票据

  • 域(Realm):add-on-Neo4j-Kerberos

请注意,Kerberos 附加组件目前无法在 Neo4j Browser 中使用,仅适用于使用 Neo4j 驱动的应用程序。

示例代码

示例 1. 使用 Java 的示例

这是使用 5.18.0 版 Java 驱动的示例实现。

public void connect() throws Exception
{
	AuthTokenManager tokenManager = new AuthTokenManager()
	{
		@Override
		public CompletionStage<AuthToken> getToken()
		{
			return CompletableFuture.supplyAsync( () -> {
				try
				{
					byte[] serviceTicket = get( serviceDomainName );
					String encodedServiceTicket = Base64.getEncoder().encodeToString( serviceTicket );
					return AuthTokens.kerberos( encodedServiceTicket );
				} catch ( Exception e ) {
					e.printStackTrace();
					return null;
				}
				});
		}

		@Override
		public boolean handleSecurityException( AuthToken authToken, SecurityException exception )
		{
			return false;
		}
	};

	try ( Driver driver = GraphDatabase.driver( "bolt://" + serviceDomainName, tokenManager ) )
	{
		// do interesting things
	}
}

public byte[] get( String serviceDomainName ) throws LoginException, GSSException
{
	Map<String,String> options = Collections.singletonMap( "useTicketCache", "true" );
	Krb5Configuration loginContextConfiguration = new Krb5Configuration( options );
	LoginContext loginContext = new LoginContext(
	    "KerberosClient",
	    null, // this is the subject
	    null, // no need for this
	    loginContextConfiguration
	  );
	loginContext.login();

	return getServiceTicket( loginContext.getSubject(), "neo4j@" + serviceDomainName );
}
public static final Oid SPNEGO_OID = getOid( "1.3.6.1.5.5.2" );
public byte[] getServiceTicket( Subject subject, String servicePrincipalName ) throws GSSException
{
	GSSManager manager = GSSManager.getInstance();
	GSSName serverName = manager.createName( servicePrincipalName, GSSName.NT_HOSTBASED_SERVICE );
	final GSSContext context = manager.createContext(
					serverName, SPNEGO_OID, null, GSSContext.DEFAULT_LIFETIME );
	// The GSS context initiation has to be performed as a privileged action.
	return Subject.doAs( subject, new PrivilegedAction<byte[]>()
	{
		public byte[] run()
		{
			try
			{
				// This is a one pass context initialisation.
				context.requestMutualAuth( false );
				context.requestCredDeleg( false );
				return context.initSecContext( new byte[0], 0, 0 );
			}
			catch ( GSSException e )
			{
				e.printStackTrace();
				return null;
			}
		}
	} );
}

private class Krb5Configuration extends Configuration
{
	private final AppConfigurationEntry[] configList;

	public Krb5Configuration( Map<String,String> options )
	{
		this.configList = new AppConfigurationEntry[1];
		configList[0] =
		    new AppConfigurationEntry(
		        "com.sun.security.auth.module.Krb5LoginModule",
		        AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
		        options
		    );
	}

	@Override
	public AppConfigurationEntry[] getAppConfigurationEntry( String name )
	{
	    return configList;
	}
}
示例 2. 使用 C# 的示例

这是使用 1.3 版 .NET 驱动的 C# 示例实现。

var token = AuthTokens.kerberos(getTicket("neo4j"));
	using (var driver = GraphDatabase.Driver("bolt://neo4j.windomain.local:7687", token))
	{
		try
		{
			using (var session = driver.Session())
				{
					var result = session.Run("MATCH () RETURN count(*) AS count");
					foreach (var record in result)
					{
						Console.WriteLine($"Nodecount: {record["count"].As<string>()}");
					}
				}
		}
		catch (Exception e)
		{
			Console.WriteLine($"Error: {e.Message}");
		}
}

 private static String getTicket(string serviceName)
 {
	AppDomain.CurrentDomain.SetPrincipalPolicy(System.Security.Principal.PrincipalPolicy.WindowsPrincipal);
	var domain = Domain.GetCurrentDomain().ToString();

	using (var domainContext = new PrincipalContext(ContextType.Domain, domain))
	{
		string spn = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, serviceName).UserPrincipalName;
		Console.WriteLine("Service Principale name: " + spn);
		KerberosSecurityTokenProvider tokenProvider = new KerberosSecurityTokenProvider(spn);
		KerberosRequestorSecurityToken securityToken = tokenProvider.GetToken(TimeSpan.FromMinutes(1)) as KerberosRequestorSecurityToken;
		var token = securityToken.GetRequest();
		String ticket = Convert.ToBase64String(token);
		return ticket;
	}
 }