使用 Azure PowerShell 创建服务主体来访问资源

当某个应用或脚本需要访问资源时,用户可以为该应用设置一个标识,并使用其自身的凭据进行身份验证。 此标识称为服务主体。 使用此方法可实现以下目的:

  • 将不同于自己的权限的权限分配给应用标识。 通常情况下,这些权限仅限于应用需执行的操作。
  • 执行无人参与的脚本时,使用证书进行身份验证。

本主题介绍如何通过 Azure PowerShell 为应用程序进行一切所需设置,使之能够使用自己的凭据和标识运行。

所需的权限

若要完成本主题,必须在 Azure Active Directory 和 Azure 订阅中均具有足够的权限。 具体而言,必须能够在 Azure Active Directory 中创建应用并向角色分配服务主体。

检查帐户是否有足够权限的最简方法是使用门户。 请参阅检查所需的权限

现在,继续完成进行身份验证的部分:

PowerShell 命令

若要设置服务主体,请使用:

命令 说明
New-AzureRmADServicePrincipal 创建 Azure Active Directory 服务主体
New-AzureRmRoleAssignment 在指定范围内将指定的 RBAC 角色分配给指定的主体。

使用密码创建服务主体

若要使用订阅的参与者角色创建服务主体,请使用:

Login-AzureRmAccount -EnvironmentName AzureChinaCloud
$sp = New-AzureRmADServicePrincipal -DisplayName exampleapp -Password "{provide-password}"
Sleep 20
New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $sp.ApplicationId

该示例休眠 20 秒,让新的服务主体有时间传遍 Azure Active Directory。 如果脚本等待时长不足,会显示错误,称“PrincipalNotFound: 主体 {id} 不存在于目录中”。

使用以下脚本可以指定默认订阅以外的范围,并在出错时重试角色分配:

Param (

 # Use to set scope to resource group. If no value is provided, scope is set to subscription.
 [Parameter(Mandatory=$false)]
 [String] $ResourceGroup,

 # Use to set subscription. If no value is provided, default subscription is used. 
 [Parameter(Mandatory=$false)]
 [String] $SubscriptionId,

 [Parameter(Mandatory=$true)]
 [String] $ApplicationDisplayName,

 [Parameter(Mandatory=$true)]
 [String] $Password
)

 Login-AzureRmAccount -EnvironmentName AzureChinaCloud
 Import-Module AzureRM.Resources

 if ($SubscriptionId -eq "") 
 {
    $SubscriptionId = (Get-AzureRmContext).Subscription.Id
 }
 else
 {
    Set-AzureRmContext -SubscriptionId $SubscriptionId
 }

 if ($ResourceGroup -eq "")
 {
    $Scope = "/subscriptions/" + $SubscriptionId
 }
 else
 {
    $Scope = (Get-AzureRmResourceGroup -Name $ResourceGroup -ErrorAction Stop).ResourceId
 }

 # Create Service Principal for the AD app
 $ServicePrincipal = New-AzureRMADServicePrincipal -DisplayName $ApplicationDisplayName -Password $Password
 Get-AzureRmADServicePrincipal -ObjectId $ServicePrincipal.Id 

 $NewRole = $null
 $Retries = 0;
 While ($NewRole -eq $null -and $Retries -le 6)
 {
    # Sleep here for a few seconds to allow the service principal application to become active (should only take a couple of seconds normally)
    Sleep 15
    New-AzureRMRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $ServicePrincipal.ApplicationId -Scope $Scope | Write-Verbose -ErrorAction SilentlyContinue
    $NewRole = Get-AzureRMRoleAssignment -ServicePrincipalName $ServicePrincipal.ApplicationId -ErrorAction SilentlyContinue
    $Retries++;
 }

有关该脚本的几个注意事项:

  • 若要向标识授予对默认订阅的访问权限,不需要提供 ResourceGroup 或 SubscriptionId 参数。
  • 仅要将角色分配范围限制为某个资源组时,才需要指定 ResourceGroup 参数。
  • 在此示例中,要将服务主体添加到“参与者”角色。 对于其他角色,请参阅 RBAC:内置角色
  • 该脚本休眠 15 秒,让新的服务主体有时间传遍 Azure Active Directory。 如果脚本等待时长不足,会显示错误,称“PrincipalNotFound: 主体 {id} 不存在于目录中”。
  • 如果需要向服务主体授予对其他订阅或资源组的访问权限,请使用不同的范围再次运行 New-AzureRMRoleAssignment cmdlet。

通过 PowerShell 提供凭据

现在,需要以应用程序方式登录以执行相应操作。 对于用户名,请使用针对应用程序创建的 ApplicationId。 对于密码,请使用在创建帐户时指定的密码。

$creds = Get-Credential
Login-AzureRmAccount -EnvironmentName AzureChinaCloud -Credential $creds -ServicePrincipal -TenantId {tenant-id}

租户 ID 不是敏感信息,可将它直接嵌入脚本中。 如果需要检索租户 ID,请使用:

(Get-AzureRmSubscription -SubscriptionName "Contoso Default").TenantId

使用自签名证书创建服务主体

若要使用自签名证书和订阅的参与者角色创建服务主体,请使用:

Login-AzureRmAccount -EnvironmentName AzureChinaCloud
$cert = New-SelfSignedCertificate -CertStoreLocation "cert:\CurrentUser\My" -Subject "CN=exampleappScriptCert" -KeySpec KeyExchange
$keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())

$sp = New-AzureRMADServicePrincipal -DisplayName exampleapp -CertValue $keyValue -EndDate $cert.NotAfter -StartDate $cert.NotBefore
Sleep 20
New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $sp.ApplicationId

该示例休眠 20 秒,让新的服务主体有时间传遍 Azure Active Directory。 如果脚本等待时长不足,会显示错误,称“PrincipalNotFound: 主体 {id} 不存在于目录中”。

使用以下脚本可以指定默认订阅以外的范围,并在出错时重试角色分配。 必须在 Windows 10 或 Windows Server 2016 上使用 Azure PowerShell 2.0。

Param (

 # Use to set scope to resource group. If no value is provided, scope is set to subscription.
 [Parameter(Mandatory=$false)]
 [String] $ResourceGroup,

 # Use to set subscription. If no value is provided, default subscription is used. 
 [Parameter(Mandatory=$false)]
 [String] $SubscriptionId,

 [Parameter(Mandatory=$true)]
 [String] $ApplicationDisplayName
 )

 Login-AzureRmAccount -EnvironmentName AzureChinaCloud
 Import-Module AzureRM.Resources

 if ($SubscriptionId -eq "") 
 {
    $SubscriptionId = (Get-AzureRmContext).Subscription.Id
 }
 else
 {
    Set-AzureRmContext -SubscriptionId $SubscriptionId
 }

 if ($ResourceGroup -eq "")
 {
    $Scope = "/subscriptions/" + $SubscriptionId
 }
 else
 {
    $Scope = (Get-AzureRmResourceGroup -Name $ResourceGroup -ErrorAction Stop).ResourceId
 }

 $cert = New-SelfSignedCertificate -CertStoreLocation "cert:\CurrentUser\My" -Subject "CN=exampleappScriptCert" -KeySpec KeyExchange
 $keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())

 $ServicePrincipal = New-AzureRMADServicePrincipal -DisplayName $ApplicationDisplayName -CertValue $keyValue -EndDate $cert.NotAfter -StartDate $cert.NotBefore
 Get-AzureRmADServicePrincipal -ObjectId $ServicePrincipal.Id 

 $NewRole = $null
 $Retries = 0;
 While ($NewRole -eq $null -and $Retries -le 6)
 {
    # Sleep here for a few seconds to allow the service principal application to become active (should only take a couple of seconds normally)
    Sleep 15
    New-AzureRMRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $ServicePrincipal.ApplicationId -Scope $Scope | Write-Verbose -ErrorAction SilentlyContinue
    $NewRole = Get-AzureRMRoleAssignment -ServicePrincipalName $ServicePrincipal.ApplicationId -ErrorAction SilentlyContinue
    $Retries++;
 }

有关该脚本的几个注意事项:

  • 若要向标识授予对默认订阅的访问权限,不需要提供 ResourceGroup 或 SubscriptionId 参数。
  • 仅要将角色分配范围限制为某个资源组时,才需要指定 ResourceGroup 参数。
  • 在此示例中,要将服务主体添加到“参与者”角色。 对于其他角色,请参阅 RBAC:内置角色
  • 该脚本休眠 15 秒,让新的服务主体有时间传遍 Azure Active Directory。 如果脚本等待时长不足,会显示错误,称“PrincipalNotFound: 主体 {id} 不存在于目录中”。
  • 如果需要向服务主体授予对其他订阅或资源组的访问权限,请使用不同的范围再次运行 New-AzureRMRoleAssignment cmdlet。

如果未使用 Windows 10 或 Windows Server 2016 Technical Preview,需要从 Microsoft 脚本中心下载 自签名证书生成器 。 解压其内容,并导入所需的 cmdlet。

# Only run if you could not use New-SelfSignedCertificate
Import-Module -Name c:\ExtractedModule\New-SelfSignedCertificateEx.ps1

在脚本中替换以下两行代码以生成证书。

New-SelfSignedCertificateEx  -StoreLocation CurrentUser -StoreName My -Subject "CN=exampleapp" -KeySpec "Exchange" -FriendlyName "exampleapp"
$cert = Get-ChildItem -path Cert:\CurrentUser\my | where {$PSitem.Subject -eq 'CN=exampleapp' }

通过自动执行的 PowerShell 脚本提供证书

以服务主体方式登录时,需提供 AD 应用所在目录的租户 ID。 租户是 Azure Active Directory 的实例。 如果只有一个订阅,可以使用:

Param (

 [Parameter(Mandatory=$true)]
 [String] $CertSubject,

 [Parameter(Mandatory=$true)]
 [String] $ApplicationId,

 [Parameter(Mandatory=$true)]
 [String] $TenantId
 )

 $Thumbprint = (Get-ChildItem cert:\CurrentUser\My\ | Where-Object {$_.Subject -match $CertSubject }).Thumbprint
 Login-AzureRmAccount -EnvironmentName AzureChinaCloud -ServicePrincipal -CertificateThumbprint $Thumbprint -ApplicationId $ApplicationId -TenantId $TenantId

应用程序 ID 和租户 ID 不是敏感信息,可将它们直接嵌入脚本中。 如果需要检索租户 ID,请使用:

(Get-AzureRmSubscription -SubscriptionName "Contoso Default").TenantId

如果需要检索应用程序 ID,请使用:

(Get-AzureRmADApplication -DisplayNameStartWith {display-name}).ApplicationId

使用证书颁发机构提供的证书创建服务主体

若要使用证书颁发机构颁发的证书创建服务主体,请使用以下脚本:

Param (
 [Parameter(Mandatory=$true)]
 [String] $ApplicationDisplayName,

 [Parameter(Mandatory=$true)]
 [String] $SubscriptionId,

 [Parameter(Mandatory=$true)]
 [String] $CertPath,

 [Parameter(Mandatory=$true)]
 [String] $CertPlainPassword
 )

 Login-AzureRmAccount -EnvironmentName AzureChinaCloud
 Import-Module AzureRM.Resources
 Set-AzureRmContext -SubscriptionId $SubscriptionId

 $KeyId = (New-Guid).Guid
 $CertPassword = ConvertTo-SecureString $CertPlainPassword -AsPlainText -Force

 $PFXCert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($CertPath, $CertPassword)
 $KeyValue = [System.Convert]::ToBase64String($PFXCert.GetRawCertData())

 $KeyCredential = New-Object  Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADKeyCredential
 $KeyCredential.StartDate = $PFXCert.NotBefore
 $KeyCredential.EndDate= $PFXCert.NotAfter
 $KeyCredential.KeyId = $KeyId
 $KeyCredential.CertValue = $KeyValue

 $ServicePrincipal = New-AzureRMADServicePrincipal -DisplayName $ApplicationDisplayName -KeyCredentials $keyCredential
 Get-AzureRmADServicePrincipal -ObjectId $ServicePrincipal.Id 

 $NewRole = $null
 $Retries = 0;
 While ($NewRole -eq $null -and $Retries -le 6)
 {
    # Sleep here for a few seconds to allow the service principal application to become active (should only take a couple of seconds normally)
    Sleep 15
    New-AzureRMRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $ServicePrincipal.ApplicationId | Write-Verbose -ErrorAction SilentlyContinue
    $NewRole = Get-AzureRMRoleAssignment -ServicePrincipalName $ServicePrincipal.ApplicationId -ErrorAction SilentlyContinue
    $Retries++;
 }

 $NewRole

有关该脚本的几个注意事项:

  • 访问范围限定于订阅。
  • 在此示例中,要将服务主体添加到“参与者”角色。 对于其他角色,请参阅 RBAC:内置角色
  • 该脚本休眠 15 秒,让新的服务主体有时间传遍 Azure Active Directory。 如果脚本等待时长不足,会显示错误,称“PrincipalNotFound: 主体 {id} 不存在于目录中”。
  • 如果需要向服务主体授予对其他订阅或资源组的访问权限,请使用不同的范围再次运行 New-AzureRMRoleAssignment cmdlet。

通过自动执行的 PowerShell 脚本提供证书

以服务主体方式登录时,需提供 AD 应用所在目录的租户 ID。 租户是 Azure Active Directory 的实例。

Param (

 [Parameter(Mandatory=$true)]
 [String] $CertPath,

 [Parameter(Mandatory=$true)]
 [String] $CertPlainPassword,

 [Parameter(Mandatory=$true)]
 [String] $ApplicationId,

 [Parameter(Mandatory=$true)]
 [String] $TenantId
 )

 $CertPassword = ConvertTo-SecureString $CertPlainPassword -AsPlainText -Force
 $PFXCert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($CertPath, $CertPassword)
 $Thumbprint = $PFXCert.Thumbprint

 Login-AzureRmAccount -EnvironmentName AzureChinaCloud -ServicePrincipal -CertificateThumbprint $Thumbprint -ApplicationId $ApplicationId -TenantId $TenantId

应用程序 ID 和租户 ID 不是敏感信息,可将它们直接嵌入脚本中。 如果需要检索租户 ID,请使用:

(Get-AzureRmSubscription -SubscriptionName "Contoso Default").TenantId

如果需要检索应用程序 ID,请使用:

(Get-AzureRmADApplication -DisplayNameStartWith {display-name}).ApplicationId

更改凭据

若要更改 AD 应用的凭据(为了保障安全或由于凭据过期的缘故),请使用 Remove-AzureRmADAppCredentialNew-AzureRmADAppCredential cmdlet。

若要删除应用程序的所有凭据,请使用:

Remove-AzureRmADAppCredential -ApplicationId 8bc80782-a916-47c8-a47e-4d76ed755275 -All

若要添加密码,请使用:

New-AzureRmADAppCredential -ApplicationId 8bc80782-a916-47c8-a47e-4d76ed755275 -Password p@ssword!

若要添加证书值,请按本主题所示创建自签名证书。 然后,使用:

New-AzureRmADAppCredential -ApplicationId 8bc80782-a916-47c8-a47e-4d76ed755275 -CertValue $keyValue -EndDate $cert.NotAfter -StartDate $cert.NotBefore

保存访问令牌来简化登录

若要避免每次登录时都需要提供服务主体凭据,可保存访问令牌。

若要在以后的会话中使用当前访问令牌,请保存该配置文件。

Save-AzureRmProfile -Path c:\Users\exampleuser\profile\exampleSP.json

打开该配置文件,并检查其内容。 请注意,它包含访问令牌。 无需再次手动登录,只需加载配置文件。

Select-AzureRmProfile -Path c:\Users\exampleuser\profile\exampleSP.json
Note

访问令牌会过期,因此使用保存的配置文件仅适合在令牌有效期间使用。

也可从要登录的 PowerShell 调用 REST 操作。 可以从身份验证响应中检索访问令牌,将其用于其他操作。 若要通过示例来了解如何通过调用 REST 操作来检索访问令牌,请参阅生成访问令牌

调试

创建服务主体时,可能会遇到以下错误:

  • “Authentication_Unauthorized”或“在上下文中找不到订阅”。 - 如果帐户不具有在 Azure Active Directory 上注册应用所需的权限,会收到此错误。 通常,当仅 Azure Active Directory 中的管理员用户可注册应用且帐户不是管理员帐户时,会看到此错误。可要求管理员分配管理员角色,或者允许用户注册应用。

  • 帐户“无权对作用域 '/subscriptions/{guid}' 执行 'Microsoft.Authorization/roleAssignments/write' 操作。”- 当帐户没有足够权限,无法为标识分配角色时,会出现此错误。 可要求订阅管理员将你添加到“用户访问管理员”角色。

示例应用程序

有关在不同平台上通过应用程序登录的信息,请参阅:

.NET

Java

Python

Node.js

Ruby

后续步骤