身份验证和授权插件

Neo4j 提供身份验证和授权插件接口,以支持原生用户或内置的基于配置的 LDAP 连接器无法涵盖的实际部署场景。

SPI(服务提供者接口)位于 com.neo4j.server.security.enterprise.auth.plugin.spi 包中。

自定义构建的插件可以访问 <neo4j-home> 目录,以便您从那里加载的任何自定义设置。插件还可以写入安全事件日志。

身份验证插件

身份验证插件使用 authenticate 方法实现 AuthenticationPlugin 接口。

以下示例显示了一个最小的身份验证插件,该插件检查 Neo4j 用户是否具有 Neo4j 密码。

@Override
public AuthenticationInfo authenticate( AuthToken authToken )
{
    String principal = authToken.principal();
    char[] credentials = authToken.credentials();

    if ( principal.equals( "neo4j" ) && Arrays.equals( credentials, "neo4j".toCharArray() ) )
    {
        return (AuthenticationInfo) () -> "neo4j";
    }
    return null;
}

授权插件

授权插件使用 authorize 方法实现 AuthorizationPlugin 接口。

以下示例显示了一个最小的授权插件,它将读者角色分配给名为 neo4j 的用户。

@Override
public AuthorizationInfo authorize( Collection<PrincipalAndProvider> principals )
{
    if ( principals.stream().anyMatch( p -> "neo4j".equals( p.principal() ) ) )
    {
        return (AuthorizationInfo) () -> Collections.singleton( PredefinedRoles.READER );
    }
    return null;
}

请注意 PredefinedRole 辅助类的用法。

简化的组合插件

还有一个简化的组合插件接口 AuthPlugin,它在名为 authenticateAndAuthorize 的单个方法中提供身份验证和授权。

以下示例显示了一个组合插件,它验证 neo4j/neo4j 凭据并返回读者角色授权。

@Override
public AuthInfo authenticateAndAuthorize( AuthToken authToken )
{
    String principal = authToken.principal();
    char[] credentials = authToken.credentials();

    if ( principal.equals( "neo4j" ) && Arrays.equals( credentials, "neo4j".toCharArray() ) )
    {
        return AuthInfo.of( "neo4j", Collections.singleton( PredefinedRoles.READER ) );
    }
    return null;
}

可扩展平台

Neo4j 提供了一个可扩展平台,因为某些用户部署场景可能无法通过标准 LDAP 连接器轻松配置。一个已知的复杂性是与 LDAP 用户目录集成,其中组将用户作为成员,而不是相反。

以下示例首先搜索用户所属的组,然后通过调用自定义构建的 getNeo4jRoleForGroupId 方法将该组映射到 Neo4j 角色。

@Override
public AuthInfo authenticateAndAuthorize( AuthToken authToken ) throws AuthenticationException
{
    try
    {
        String username = authToken.principal();
        char[] password = authToken.credentials();

        LdapContext ctx = authenticate( username, password );
        Set<String> roles = authorize( ctx, username );

        return AuthInfo.of( username, roles );
    }
    catch ( NamingException e )
    {
        throw new AuthenticationException( e.getMessage() );
    }
}

private LdapContext authenticate( String username, char[] password ) throws NamingException
{
    Hashtable<String,Object> env = new Hashtable<>();
    env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
    env.put( Context.PROVIDER_URL, "ldap://0.0.0.0:10389" );

    env.put( Context.SECURITY_PRINCIPAL, String.format( "cn=%s,ou=users,dc=example,dc=com", username ) );
    env.put( Context.SECURITY_CREDENTIALS, password );

    return new InitialLdapContext( env, null );
}

private Set<String> authorize( LdapContext ctx, String username ) throws NamingException
{
    Set<String> roleNames = new LinkedHashSet<>();

    // Set up our search controls
    SearchControls searchCtls = new SearchControls();
    searchCtls.setSearchScope( SearchControls.SUBTREE_SCOPE );
    searchCtls.setReturningAttributes( new String[]{GROUP_ID} );

    // Use a search argument to prevent potential code injection
    Object[] searchArguments = new Object[]{username};

    // Search for groups that has the user as a member
    NamingEnumeration result = ctx.search( GROUP_SEARCH_BASE, GROUP_SEARCH_FILTER, searchArguments, searchCtls );

    if ( result.hasMoreElements() )
    {
        SearchResult searchResult = (SearchResult) result.next();

        Attributes attributes = searchResult.getAttributes();
        if ( attributes != null )
        {
            NamingEnumeration attributeEnumeration = attributes.getAll();
            while ( attributeEnumeration.hasMore() )
            {
                Attribute attribute = (Attribute) attributeEnumeration.next();
                String attributeId = attribute.getID();
                if ( attributeId.equalsIgnoreCase( GROUP_ID ) )
                {
                    // Found a group that the user is a member of. See if it has a role mapped to it
                    String groupId = (String) attribute.get();
                    String neo4jGroup = getNeo4jRoleForGroupId( groupId );
                    if ( neo4jGroup != null )
                    {
                        // Yay! Add it to your set of roles
                        roleNames.add( neo4jGroup );
                    }
                }
            }
        }
    }
    return roleNames;
}

有关更多信息和其他插件示例,请参见 https://github.com/neo4j/neo4j-example-auth-plugins