使用 Java 管理 Azure Data Lake Storage Gen2 中的 ACL

本文介绍如何使用 Java 来获取、设置和更新目录和文件的访问控制列表。

ACL 继承已可用于在父目录下创建的新子项。 但是你也可以为父目录的现有子项以递归方式添加、更新和删除 ACL,而不必为每个子项单独进行这些更改。

包 (Maven) | 示例 | API 参考 | 提供反馈

先决条件

  • Azure 订阅。 请参阅获取 Azure 试用版

  • 已启用分层命名空间 (HNS) 的存储帐户。 按这些说明创建一个。

  • Azure CLI 版本 2.6.0 或更高版本。

  • 以下安全权限之一:

    • 一个预配的 Azure Active Directory (AD) 安全主体,它在目标容器父资源组或订阅范围中分配有存储 Blob 数据所有者角色。

    • 计划将 ACL 设置应用到的目标容器或目录的拥有用户。 为了以递归方式设置 ACL,这包括目标容器或目录中的所有子项。

    • 存储帐户密钥。

设置项目

若要开始,请打开此页,找到最新版本的 Java 库。 然后,在文本编辑器中打开 pom.xml 文件。 添加引用该版本的依赖项元素。

如果计划使用 Azure Active Directory (AD) 验证客户端应用程序,可将依赖项添加到 Azure 机密客户端库。 请参阅将机密客户端库包添加到项目

接下来,将这些 import 语句添加到代码文件。

import com.azure.storage.common.StorageSharedKeyCredential;
import com.azure.storage.file.datalake.DataLakeDirectoryClient;
import com.azure.storage.file.datalake.DataLakeFileClient;
import com.azure.storage.file.datalake.DataLakeFileSystemClient;
import com.azure.storage.file.datalake.DataLakeServiceClient;
import com.azure.storage.file.datalake.DataLakeServiceClientBuilder;
import com.azure.storage.file.datalake.models.ListPathsOptions;
import com.azure.storage.file.datalake.models.PathItem;
import com.azure.storage.file.datalake.models.AccessControlChangeCounters;
import com.azure.storage.file.datalake.models.AccessControlChangeResult;
import com.azure.storage.file.datalake.models.AccessControlType;
import com.azure.storage.file.datalake.models.PathAccessControl;
import com.azure.storage.file.datalake.models.PathAccessControlEntry;
import com.azure.storage.file.datalake.models.PathPermissions;
import com.azure.storage.file.datalake.models.PathRemoveAccessControlEntry;
import com.azure.storage.file.datalake.models.RolePermissions;
import com.azure.storage.file.datalake.options.PathSetAccessControlRecursiveOptions;

连接到帐户

若要使用本文中的代码片段,需创建一个表示存储帐户的 DataLakeServiceClient 实例。

使用 Azure Active Directory (Azure AD) 进行连接

可以使用适用于 Java 的 Azure 标识客户端库,通过 Azure AD 对应用程序进行身份验证。

获取客户端 ID、客户端机密和租户 ID。 若要执行此操作,请参阅从 Azure AD 获取用于对客户端应用程序的请求进行授权的令牌。 在该过程中,你必须为安全主体分配以下 Azure 基于角色的访问控制 (Azure RBAC) 角色之一。

角色 ACL 设置功能
存储 Blob 数据所有者 帐户中的所有目录和文件。
存储 Blob 数据参与者 仅限安全主体拥有的目录和文件。

此示例使用客户端 ID、客户端密码和租户 ID 创建 DataLakeServiceClient 实例。

static public DataLakeServiceClient GetDataLakeServiceClient
    (String accountName, String clientId, String ClientSecret, String tenantID){

    String endpoint = "https://" + accountName + ".dfs.core.chinacloudapi.cn";
    
    ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
    .clientId(clientId)
    .clientSecret(ClientSecret)
    .tenantId(tenantID)
    .build();
       
    DataLakeServiceClientBuilder builder = new DataLakeServiceClientBuilder();
    return builder.credential(clientSecretCredential).endpoint(endpoint).buildClient();
}

备注

有关更多示例,请参阅适用于 Java 的 Azure 标识客户端库文档。

使用帐户密钥进行连接

这是连接到帐户的最简单方法。

此示例使用帐户密钥创建 DataLakeServiceClient 实例。

static public DataLakeServiceClient GetDataLakeServiceClient
(String accountName, String accountKey){

    StorageSharedKeyCredential sharedKeyCredential =
        new StorageSharedKeyCredential(accountName, accountKey);

    DataLakeServiceClientBuilder builder = new DataLakeServiceClientBuilder();

    builder.credential(sharedKeyCredential);
    builder.endpoint("https://" + accountName + ".dfs.core.chinacloudapi.cn");

    return builder.buildClient();
}

设置 ACL

设置 ACL 时,你将替换整个 ACL,包括其所有条目。 如果要更改安全主体的权限级别,或将新的安全主体添加到 ACL 而不影响其他现有项,则应改为更新 ACL。 若要更新 ACL 而不是替换它,请参阅本文的更新 ACL 部分。

如果选择设置 ACL,则必须为责任用户添加一个条目,为责任组添加一个条目,为所有其他用户添加一个条目。 若要详细了解责任用户、责任组和所有其他用户,请参阅用户和标识

本节介绍如何完成下列操作:

  • 设置目录的 ACL
  • 设置文件的 ACL
  • 以递归方式设置 ACL

设置目录的 ACL

此示例获取并设置名为 my-directory 的目录的 ACL。 此示例为拥有用户提供读取、写入和执行权限,为拥有组授予读取和执行权限,并为所有其他用户提供读取访问权限。

public void ManageDirectoryACLs(DataLakeFileSystemClient fileSystemClient){

      DataLakeDirectoryClient directoryClient =
        fileSystemClient.getDirectoryClient("");

      PathAccessControl directoryAccessControl =
          directoryClient.getAccessControl();

      List<PathAccessControlEntry> pathPermissions = directoryAccessControl.getAccessControlList();
     
      System.out.println(PathAccessControlEntry.serializeList(pathPermissions));
           
      RolePermissions groupPermission = new RolePermissions();
      groupPermission.setExecutePermission(true).setReadPermission(true);

      RolePermissions ownerPermission = new RolePermissions();
      ownerPermission.setExecutePermission(true).setReadPermission(true).setWritePermission(true);

      RolePermissions otherPermission = new RolePermissions();
      otherPermission.setReadPermission(true);

      PathPermissions permissions = new PathPermissions();

      permissions.setGroup(groupPermission);
      permissions.setOwner(ownerPermission);
      permissions.setOther(otherPermission);

      directoryClient.setPermissions(permissions, null, null);

      pathPermissions = directoryClient.getAccessControl().getAccessControlList();
   
      System.out.println(PathAccessControlEntry.serializeList(pathPermissions));

  }

还可以获取和设置容器根目录的 ACL。 若要获取根目录,请将空字符串 ("") 传递到“DataLakeFileSystemClient.getDirectoryClient”方法。

设置文件的 ACL

此示例获取并设置名为 upload-file.txt 的文件的 ACL。 此示例为拥有用户提供读取、写入和执行权限,为拥有组授予读取和执行权限,并为所有其他用户提供读取访问权限。

public void ManageFileACLs(DataLakeFileSystemClient fileSystemClient){

     DataLakeDirectoryClient directoryClient =
       fileSystemClient.getDirectoryClient("my-directory");

     DataLakeFileClient fileClient = 
       directoryClient.getFileClient("uploaded-file.txt");

     PathAccessControl fileAccessControl =
         fileClient.getAccessControl();

   List<PathAccessControlEntry> pathPermissions = fileAccessControl.getAccessControlList();
  
   System.out.println(PathAccessControlEntry.serializeList(pathPermissions));
        
   RolePermissions groupPermission = new RolePermissions();
   groupPermission.setExecutePermission(true).setReadPermission(true);

   RolePermissions ownerPermission = new RolePermissions();
   ownerPermission.setExecutePermission(true).setReadPermission(true).setWritePermission(true);

   RolePermissions otherPermission = new RolePermissions();
   otherPermission.setReadPermission(true);

   PathPermissions permissions = new PathPermissions();

   permissions.setGroup(groupPermission);
   permissions.setOwner(ownerPermission);
   permissions.setOther(otherPermission);

   fileClient.setPermissions(permissions, null, null);

   pathPermissions = fileClient.getAccessControl().getAccessControlList();

   System.out.println(PathAccessControlEntry.serializeList(pathPermissions));

 }

以递归方式设置 ACL

通过调用 DataLakeDirectoryClient.setAccessControlRecursive 方法以递归方式设置 ACL。 向此方法传递 PathAccessControlEntry 对象的列表。 每个 PathAccessControlEntry 定义一个 ACL 条目。

如果要设置 默认 ACL 条目,则可以调用 PathAccessControlEntrysetDefaultScope 方法,并传入值 true

此示例设置名为 my-parent-directory 的目录的 ACL。 此方法接受一个名为 isDefaultScope 的布尔参数,该参数指定是否设置默认 ACL。 该参数在每次调用 PathAccessControlEntrysetDefaultScope 方法时使用。 ACL 的条目为所有者用户提供读取、写入和执行权限,仅为负责人组授予读取和执行权限,不为所有其他用户提供任何访问权限。 此示例中的最后一个 ACL 条目为对象 ID 为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”的特定用户提供读取和执行权限。

public void SetACLRecursively(DataLakeFileSystemClient fileSystemClient, Boolean isDefaultScope){
    
    DataLakeDirectoryClient directoryClient =
        fileSystemClient.getDirectoryClient("my-parent-directory");

    List<PathAccessControlEntry> pathAccessControlEntries = 
        new ArrayList<PathAccessControlEntry>();

    // Create owner entry.
    PathAccessControlEntry ownerEntry = new PathAccessControlEntry();

    RolePermissions ownerPermission = new RolePermissions();
    ownerPermission.setExecutePermission(true).setReadPermission(true).setWritePermission(true);

    ownerEntry.setDefaultScope(isDefaultScope);
    ownerEntry.setAccessControlType(AccessControlType.USER);
    ownerEntry.setPermissions(ownerPermission);

    pathAccessControlEntries.add(ownerEntry);

    // Create group entry.
    PathAccessControlEntry groupEntry = new PathAccessControlEntry();

    RolePermissions groupPermission = new RolePermissions();
    groupPermission.setExecutePermission(true).setReadPermission(true).setWritePermission(false);

    groupEntry.setDefaultScope(isDefaultScope);
    groupEntry.setAccessControlType(AccessControlType.GROUP);
    groupEntry.setPermissions(groupPermission);

    pathAccessControlEntries.add(groupEntry);

    // Create other entry.
    PathAccessControlEntry otherEntry = new PathAccessControlEntry();

    RolePermissions otherPermission = new RolePermissions();
    otherPermission.setExecutePermission(false).setReadPermission(false).setWritePermission(false);

    otherEntry.setDefaultScope(isDefaultScope);
    otherEntry.setAccessControlType(AccessControlType.OTHER);
    otherEntry.setPermissions(otherPermission);

    pathAccessControlEntries.add(otherEntry);

    // Create named user entry.
    PathAccessControlEntry userEntry = new PathAccessControlEntry();

    RolePermissions userPermission = new RolePermissions();
    userPermission.setExecutePermission(true).setReadPermission(true).setWritePermission(false);

    userEntry.setDefaultScope(isDefaultScope);
    userEntry.setAccessControlType(AccessControlType.USER);
    userEntry.setEntityId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
    userEntry.setPermissions(userPermission);    
    
    pathAccessControlEntries.add(userEntry);
    
    directoryClient.setAccessControlRecursive(pathAccessControlEntries);        

}

更新 ACL

更新 ACL 时,你将修改 ACL 而非替换 ACL。 例如,你可以将一个新的安全主体添加到 ACL,而不影响 ACL 中列出的其他安全主体。 若要替换 ACL 而不是更新它,请参阅本文的 设置 ACL 部分。

本节介绍如何完成下列操作:

  • 更新 ACL
  • 以递归方式更新 ACL

更新 ACL

首先,通过调用 PathAccessControl. getAccessControlList 方法获取目录的 ACL。 将 ACL 条目列表复制到类型为 PathAccessControlListEntry 的新的 List 对象。 然后找到要更新的条目并在列表中替换它。 通过调用 DataLakeDirectoryClient.setAccessControlList 方法设置 ACL。

此示例通过替换所有其他用户的条目来更新名为 my-parent-directory 的容器的 ACL。

public void UpdateACL(DataLakeFileSystemClient fileSystemClient, Boolean isDefaultScope){

       DataLakeDirectoryClient directoryClient =
       fileSystemClient.getDirectoryClient("my-parent-directory");

       List<PathAccessControlEntry> pathAccessControlEntries = 
           directoryClient.getAccessControl().getAccessControlList();

       int index = -1;

       for (PathAccessControlEntry pathAccessControlEntry : pathAccessControlEntries){
           
           if (pathAccessControlEntry.getAccessControlType() == AccessControlType.OTHER){
               index = pathAccessControlEntries.indexOf(pathAccessControlEntry);
               break;
           }
       }

       if (index > -1){

       PathAccessControlEntry userEntry = new PathAccessControlEntry();

       RolePermissions userPermission = new RolePermissions();
       userPermission.setExecutePermission(true).setReadPermission(true).setWritePermission(true);

       userEntry.setDefaultScope(isDefaultScope);
       userEntry.setAccessControlType(AccessControlType.OTHER);
       userEntry.setPermissions(userPermission);  
       
       pathAccessControlEntries.set(index, userEntry);
       }

       directoryClient.setAccessControlList(pathAccessControlEntries, 
           directoryClient.getAccessControl().getGroup(), 
           directoryClient.getAccessControl().getOwner());
   
   }

还可以获取和设置容器根目录的 ACL。 若要获取根目录,请将空字符串 ("") 传递到“DataLakeFileSystemClient.getDirectoryClient”方法。

以递归方式更新 ACL

若要以递归方式更新 ACL,请创建包含要更新的 ACL 条目的一个新 ACL 对象,然后在“更新 ACL”操作中使用该对象。 不要获取现有 ACL,只需要提供要更新的 ACL 条目。

通过调用 DataLakeDirectoryClient.updateAccessControlRecursive 方法以递归方式更新 ACL。 向此方法传递 PathAccessControlEntry 对象的列表。 每个 PathAccessControlEntry 定义一个 ACL 条目。

如果要更新 默认 ACL 条目,则可以调用 PathAccessControlEntrysetDefaultScope 方法,并传入值 true

此示例以写入权限更新某个 ACL 条目。 此方法接受一个名为 isDefaultScope 的布尔参数,该参数指定是否更新默认 ACL。 该参数在调用 PathAccessControlEntrysetDefaultScope 方法时使用。

public void UpdateACLRecursively(DataLakeFileSystemClient fileSystemClient, Boolean isDefaultScope){

    DataLakeDirectoryClient directoryClient =
    fileSystemClient.getDirectoryClient("my-parent-directory");

    List<PathAccessControlEntry> pathAccessControlEntries = 
        new ArrayList<PathAccessControlEntry>();

    // Create named user entry.
    PathAccessControlEntry userEntry = new PathAccessControlEntry();

    RolePermissions userPermission = new RolePermissions();
    userPermission.setExecutePermission(true).setReadPermission(true).setWritePermission(true);

    userEntry.setDefaultScope(isDefaultScope);
    userEntry.setAccessControlType(AccessControlType.USER);
    userEntry.setEntityId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
    userEntry.setPermissions(userPermission);    
    
    pathAccessControlEntries.add(userEntry);
    
    directoryClient.updateAccessControlRecursive(pathAccessControlEntries);        

}

删除 ACL 条目

可以删除一个或多个 ACL 条目。 本节介绍如何完成下列操作:

  • 删除 ACL 条目
  • 以递归方式删除 ACL 条目

删除 ACL 条目

首先,通过调用 PathAccessControl. getAccessControlList 方法获取目录的 ACL。 将 ACL 条目列表复制到类型为 PathAccessControlListEntry 的新的 List 对象。 然后找到要删除的条目,并调用 List 对象的 Remove 方法 。 通过调用 DataLakeDirectoryClient.setAccessControlList 方法设置已更新的 ACL。

public void RemoveACLEntry(DataLakeFileSystemClient fileSystemClient, Boolean isDefaultScope){

     DataLakeDirectoryClient directoryClient =
     fileSystemClient.getDirectoryClient("my-parent-directory");

     List<PathAccessControlEntry> pathAccessControlEntries = 
         directoryClient.getAccessControl().getAccessControlList();

     PathAccessControlEntry entryToRemove = null;

     for (PathAccessControlEntry pathAccessControlEntry : pathAccessControlEntries){
         
         if (pathAccessControlEntry.getEntityId() != null){

             if (pathAccessControlEntry.getEntityId().equals("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")){
                 entryToRemove = pathAccessControlEntry;
                 break;
             }
         }

     }

     if (entryToRemove != null){
         
         pathAccessControlEntries.remove(entryToRemove);

         directoryClient.setAccessControlList(pathAccessControlEntries, 
         directoryClient.getAccessControl().getGroup(), 
         directoryClient.getAccessControl().getOwner());   
     }

 
 }

以递归方式删除 ACL 条目

若要以递归方式删除 ACL 条目,请为要删除的 ACL 条目创建一个新的 ACL 对象,然后在“删除 ACL”操作中使用该对象。 不要获取现有 ACL,只需要提供要删除的 ACL 条目。

通过调用 DataLakeDirectoryClient.removeAccessControlRecursive 方法删除 ACL 条目。 向此方法传递 PathAccessControlEntry 对象的列表。 每个 PathAccessControlEntry 定义一个 ACL 条目。

如果要删除 默认 ACL 条目,则可以调用 PathAccessControlEntrysetDefaultScope 方法,并传入值 true

此示例从名为 my-parent-directory 的目录的 ACL 中删除 ACL 条目。 此方法接受一个名为 isDefaultScope 的布尔参数,该参数指定是否删除默认 ACL 中的条目。 该参数在调用 PathAccessControlEntrysetDefaultScope 方法时使用。

public void RemoveACLEntryRecursively(DataLakeFileSystemClient fileSystemClient, Boolean isDefaultScope){

      DataLakeDirectoryClient directoryClient =
      fileSystemClient.getDirectoryClient("my-parent-directory");

      List<PathRemoveAccessControlEntry> pathRemoveAccessControlEntries = 
          new ArrayList<PathRemoveAccessControlEntry>();

      // Create named user entry.
      PathRemoveAccessControlEntry userEntry = new PathRemoveAccessControlEntry();

      RolePermissions userPermission = new RolePermissions();
      userPermission.setExecutePermission(true).setReadPermission(true).setWritePermission(true);

      userEntry.setDefaultScope(isDefaultScope);
      userEntry.setAccessControlType(AccessControlType.USER);
      userEntry.setEntityId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); 
      
      pathRemoveAccessControlEntries.add(userEntry);
      
      directoryClient.removeAccessControlRecursive(pathRemoveAccessControlEntries);      
  
  }

从故障中恢复

你可能会遇到运行时错误或权限错误。 对于运行时错误,请从头开始重启此过程。 如果安全主体没有足够的权限修改要修改的目录层次结构中的目录或文件的 ACL,则会出现权限错误。 请解决权限问题,然后选择通过使用继续标记从故障点继续执行此过程,或者从头重启此过程。 如果希望从头开始重启,则无需使用继续标记。 你可以重新应用 ACL 条目,而不会产生任何负面影响。

此示例在失败时返回一个继续标记。 应用程序可以在错误得到解决后再次调用此示例方法,并传入继续标记。 如果是第一次调用此示例方法,则应用程序可以为继续标记参数传入 null 值。

public String ResumeSetACLRecursively(DataLakeFileSystemClient fileSystemClient,
    DataLakeDirectoryClient directoryClient,
    List<PathAccessControlEntry> accessControlList, 
    String continuationToken){

    try{
        PathSetAccessControlRecursiveOptions options = new PathSetAccessControlRecursiveOptions(accessControlList);
        
        options.setContinuationToken(continuationToken);
    
       Response<AccessControlChangeResult> accessControlChangeResult =  
          directoryClient.setAccessControlRecursiveWithResponse(options, null, null);

       if (accessControlChangeResult.getValue().getCounters().getFailedChangesCount() > 0)
       {
          continuationToken =
              accessControlChangeResult.getValue().getContinuationToken();
       }
    
       return continuationToken;

    }
    catch(Exception ex){
    
        System.out.println(ex.toString());
        return continuationToken;
    }


}

如果你希望过程继续完成而不被权限错误中断,则可以指定它。

若要确保过程无中断地完成,请调用 PathSetAccessControlRecursiveOptions 对象的 setContinueOnFailure 方法,并传入值 true

此示例以递归方式设置 ACL 条目。 如果此代码遇到权限错误,它会记录该故障并继续执行。 此示例将故障数输出到控制台。

public void ContinueOnFailure(DataLakeFileSystemClient fileSystemClient,
    DataLakeDirectoryClient directoryClient,
    List<PathAccessControlEntry> accessControlList){
    
    PathSetAccessControlRecursiveOptions options = 
       new PathSetAccessControlRecursiveOptions(accessControlList);
        
    options.setContinueOnFailure(true);
    
    Response<AccessControlChangeResult> accessControlChangeResult =  
        directoryClient.setAccessControlRecursiveWithResponse(options, null, null);

    AccessControlChangeCounters counters = accessControlChangeResult.getValue().getCounters();

    System.out.println("Number of directories changes: " + 
        counters.getChangedDirectoriesCount());

    System.out.println("Number of files changed: " + 
        counters.getChangedDirectoriesCount());

    System.out.println("Number of failures: " + 
        counters.getChangedDirectoriesCount());
}

最佳实践

本部分提供了有关以递归方式设置 ACL 的一些最佳做法指南。

处理运行时错误

发生运行时错误可能有许多原因(例如:中断或客户端连接问题)。 如果遇到运行时错误,请重启递归 ACL 过程。 可以将 ACL 重新应用于项,而不会造成负面影响。

处理权限错误 (403)

如果在运行递归 ACL 过程时遇到访问控制异常,则表明 AD 安全主体可能没有足够的权限将 ACL 应用于目录层次结构中的一个或多个子项。 发生权限错误时,此过程会停止,系统会提供一个继续标记。 请修复权限问题,然后使用继续标记来处理剩余的数据集。 已成功处理的目录和文件不需要再次处理。 你还可以选择重启递归 ACL 过程。 可以将 ACL 重新应用于项,而不会造成负面影响。

凭据

建议你预配一个在目标存储帐户或容器范围中分配有存储 Blob 数据所有者角色的 Azure AD 安全主体。

性能

为了减少延迟,建议你在与存储帐户位于同一区域中的 Azure 虚拟机 (VM) 中运行递归 ACL 过程。

ACL 限制

可应用于目录或文件的 ACL 的最大数目为 32 个访问 ACL 和 32 个默认 ACL。 有关详细信息,请参阅 Azure Data Lake Storage Gen2 中的访问控制

另请参阅