【发布时间】:2021-05-16 09:31:55
【问题描述】:
我一直在编写一个脚本,该脚本正在创建一个 JWT,以便使用本地安装的证书访问 Microsoft Graph。
使用证书中的属性PrivateKey 似乎有一个更简单的解决方案,但在某些情况下(取决于密钥提供程序 CNG 或其他)它不会显示。我必须使用 OpenSSL 手动提取私钥,然后将其转换为 RSACryptoServiceProvider 才能对标头+有效负载进行签名。
现在我正在努力使用 JWT 令牌,根据 jwt.io,该令牌似乎是有效的,但对于 Microsoft 来说是无效或格式错误的。
这是错误代码:
Invoke-RestMethod :
{
"error": "invalid_request",
"error_description": "AADSTS50027: JWT token is invalid or malformed.\r\nTrace ID:ed98bb58-8545-4667-acdd-8ce863303b00\r\nCorrelation ID: 722422ce-48d0-47ae-876d-3fb60a588e75\r\nTimestamp: 2021-02-12 13:53:47Z",
"error_codes": [50027],
"timestamp": "2021-02-12 13:53:47Z",
"trace_id": "ed98bb58-8545-4667-acdd-8ce863303b00",
"correlation_id": "722422ce-48d0-47ae-876d-3fb60a588e75",
"error_uri": "https://login.microsoftonline.com/error?code=50027"
}
At D:\Scripts_Teams\QUAL\ReportingGraphAPI\Get-AuthTokenMSALDelegate.ps1:223 char:20
+ $accessToken = Invoke-RestMethod @PostSplat
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand ConvertFrom-Json : Cannot bind argument to parameter 'InputObject' because it is null.
+
At D:\Scripts_Teams\QUAL\ReportingGraphAPI\Get-AuthTokenMSALDelegate.ps1:225 char:41
+ $accessToken=$accessToken.content | ConvertFrom-Json
+ CategoryInfo : InvalidData: (:) [ConvertFrom-Json], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ConvertFromJsonCommand
这是我从 jwt.io 得到的结果: JWT.IO
这里是代码:
function Generate-JWT (
[Parameter(Mandatory = $True)]
[string]$Issuer = $null,
[Parameter(Mandatory = $True)]
[int]$ValidforMinutes = $null,
[Parameter(Mandatory = $true)]
$CertificateThumbprint,
[Parameter(Mandatory = $true)]
$TenantName
) {
$Certificate = Get-Item "Cert:\LocalMachine\My\$CertificateThumbprint"
# Create base64 hash of certificate
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())
$tempfile = "C:\Temp\tempCert.pfx"
Export-PfxCertificate -FilePath $tempfile -Cert $Certificate -Password (ConvertTo-SecureString -AsPlainText "MyP@ssw0rd!" -Force)
D:\Tools\OpenSSL\Bin\openssl.exe pkcs12 -in $tempfile -nocerts -out "C:\Temp\key.pem" -nodes -password pass:MyP@ssw0rd!
D:\Tools\OpenSSL\Bin\openssl.exe rsa -in "C:\Temp\key.pem" -out "C:\Temp\key.key"
$Algorithm = 'RS256'
$type = 'JWT'
$x5t = $CertificateBase64Hash -replace '\+', '-' -replace '/', '_' -replace '='
$aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"
$jti = [guid]::NewGuid()
# Create JWT timestamp for expiration
$StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
$JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes($ValidforMinutes)).TotalSeconds
$JWTExpiration = [math]::Round($JWTExpirationTimeSpan, 0)
# Create JWT validity start timestamp
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan, 0)
[hashtable]$JWTHeader = @{
alg = $Algorithm
typ = $type
x5t = $x5t
}
[hashtable]$JWTPayLoad = @{
aud = $aud
exp = $JWTExpiration
iss = $Issuer
jti = $jti
nbf = $NotBefore
sub = $Issuer
}
$headerjson = $JWTHeader | ConvertTo-Json -Compress
$payloadjson = $JWTPayLoad | ConvertTo-Json -Compress
# Convert header and payload to base64
$EncodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')
$EncodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')
$ToSign = $EncodedHeader + "." + $EncodedPayload
# Define RSA signature and hashing algorithm
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
$opensslkeysource = Get-Content "D:\Scripts_Teams\QUAL\ReportingGraphAPI\opensslkey.cs" -Raw
try {
Add-Type -TypeDefinition $opensslkeysource
}
catch {
if ($_.Exception -match "already exists") {
Write-Verbose "The JavaScience.Win32 assembly (i.e. opensslkey.cs) is already loaded. Continuing..."
}
}
$PemText = [System.IO.File]::ReadAllText("C:\Temp\key.key")
$PemPrivateKey = [javascience.opensslkey]::DecodeOpenSSLPrivateKey($PemText)
[System.Security.Cryptography.RSACryptoServiceProvider]$RSA = [javascience.opensslkey]::DecodeRSAPrivateKey($PemPrivateKey)
# Create a signature of the JWT
$Signature = [Convert]::ToBase64String(
$RSA.SignData([System.Text.Encoding]::UTF8.GetBytes($ToSign), $HashAlgorithm, $RSAPadding)
) -replace '\+', '-' -replace '/', '_' -replace '='
$JWT = $ToSign + "." + $Signature
return $JWT
}
这就是它的名称:
$JWT = Generate-JWT -Issuer $clientID -ValidforMinutes 60 -CertificateThumbprint $Thumbprint -TenantName $TenantName
$authBody = @{
client_id = $clientID
client_assertion = $JWT
client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
scope = "https://graph.microsoft.com/.default"
grant_type = "client_credentials"
}
# Use the self-generated JWT as Authorization
$Header = @{
Authorization = "Bearer $JWT"
}
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = @{
ContentType = 'application/x-www-form-urlencoded'
Method = 'POST'
Body = $authBody
Uri = $Uri
Headers = $Header
}
$accessToken = Invoke-RestMethod @PostSplat
你知道为什么这个 JWT 会被拒绝吗?
【问题讨论】:
标签: powershell azure-active-directory jwt microsoft-graph-api