Postman JWT签名逻辑转PowerShell遇403问题求助
问题描述
我需要把供应商Postman预请求脚本中的JWT签名逻辑转换成PowerShell代码,用来调用需要JWT签名的API。Postman里运行一切正常,但自己写的PowerShell脚本生成的JWT导致API返回403 Forbidden错误。我怀疑问题出在没正确实现CryptoJS.enc.Base64.parse(use_base64_key)这一步——也就是没有把Base64格式的密钥解码成字节数组。以下是我的PowerShell代码:
# VARIABLES $Algorithm="HS256" $ContentType="application/json" $SecretKey="" $use_base64_key="" $org_alias="" # PRE-REQ Add-Type -AssemblyName System.Security # FUNCTIONS Function Get-HMACSHA512 { param( [Parameter(Mandatory=$true)][String]$data ,[Parameter(Mandatory=$true)][String]$key ) $hmacsha = New-Object System.Security.Cryptography.HMACSHA256 $hmacsha.key = [Text.Encoding]::UTF8.GetBytes($key) $bytesToSign = [Text.Encoding]::UTF8.GetBytes($data) $sign = $hmacsha.ComputeHash($bytesToSign) return $sign } Function Get-StringFromByte { param( [Parameter(Mandatory=$true)][byte[]]$byteArray ) $stringBuilder = "" $byteArray | ForEach { $stringBuilder += $_.ToString("x2") } return $stringBuilder } Function Get-Base64UrlEncodeFromString { param( [Parameter(Mandatory=$true)][String]$inputString ) $inputBytes = [Text.Encoding]::UTF8.GetBytes($inputString) # Special "url-safe" base64 encode. $base64 = [System.Convert]::ToBase64String($inputBytes,[Base64FormattingOptions]::None).Replace('+', '-').Replace('/', '_').Replace("=", "") return $base64 } Function Get-Base64UrlEncodeFromByteArray { param( [Parameter(Mandatory=$true)][byte[]]$byteArray ) # Special "url-safe" base64 encode. $base64 = [System.Convert]::ToBase64String($byteArray,[Base64FormattingOptions]::None).Replace('+', '-').Replace('/', '_').Replace("=", "") return $base64 } Function Get-Base64FromString { param( [Parameter(Mandatory=$true)][String]$inputString ) $inputBytes = [Text.Encoding]::UTF8.GetBytes($inputString) # Special "url-safe" base64 encode. $base64 = [System.Convert]::ToBase64String($inputBytes,[Base64FormattingOptions]::None) return $base64 } Function Get-Base64FromByteArray { param( [Parameter(Mandatory=$true)][byte[]]$byteArray ) $base64 = [System.Convert]::ToBase64String($byteArray,[Base64FormattingOptions]::None) return $base64 } Function Check-Base64 { # Add missing "=" at the end and check url-safe encodings param( [Parameter(Mandatory=$true)][String]$inputString ) #$input $encoded = $inputString.Replace('-','+').Replace('_','/') $d = $encoded.Length % 4 if ( $d -ne 0 ) { $encoded = $encoded.TrimEnd('=') if ( $d % 2 -gt 0 ) { $encoded += '=' } else { $encoded += '==' } } return $encoded } Function Create-JWT { param( [Parameter(Mandatory=$true)][System.Collections.Specialized.OrderedDictionary]$headers ,[Parameter(Mandatory=$true)][System.Collections.Specialized.OrderedDictionary]$payload ,[Parameter(Mandatory=$true)][string]$secret ) $headersJson = $headers | ConvertTo-Json $payloadJson = $payload | ConvertTo-Json $headersEncoded = Get-Base64UrlEncodeFromString -inputString $headersJson #[System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($headersJson),[Base64FormattingOptions]::None) $payloadEncoded = Get-Base64UrlEncodeFromString -inputString $payloadJson #[System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($payloadJson),[Base64FormattingOptions]::None) $content = "$( $headersEncoded ).$( $payloadEncoded )" $signatureByte = Get-HMACSHA512 -data $content -key $secret $signature = Get-Base64UrlEncodeFromByteArray -byteArray $signatureByte $jwt = "$( $headersEncoded ).$( $payloadEncoded ).$( $signature )" return $jwt } # MAIN CODE # Get Username to verify $username = read-host "Enter username in UPN format (i.e. SmithJ@google.com) -->" #Get-user API call #Header $headers=[ordered]@{alg=$Algorithm; org_alias= $org_alias; token=$SecretKey} #Payload $payload=[ordered]@{reqHeader = [ordered]@{ locale='en'; orgAlias=$org_alias; secretKey=$SecretKey; timestamp=((get-date).ToUniversalTime()); version='4.9'} reqBody = [ordered]@{ getSameDeviceUsers = "true"; userName = $username } } # Create JWT $secret = $secretkey $jwt = Create-JWT -headers $headers -payload $payload -secret $use_base64_key #sending request $RESTheader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $RESTheader.Add("Content-Type", "application/json") $apiresponse = Invoke-RestMethod -Uri 'https://idpxnyl3m.pingidentity.com/pingid/rest/4/getuserdetails/do' -Method 'POST' -Headers $RESTheader -Body $JWT
解决方案
你的怀疑是对的,Postman中CryptoJS.enc.Base64.parse(use_base64_key)是直接将Base64字符串解码为原始字节数组,而你的代码错误地把Base64字符串当作UTF-8文本转成了字节数组。除此之外还有几个细节需要修正,以下是完整的修正代码:
# VARIABLES $Algorithm="HS256" $ContentType="application/json" $SecretKey="" $use_base64_key="" $org_alias="" # PRE-REQ Add-Type -AssemblyName System.Security # FUNCTIONS Function Get-HMACSHA256 { param( [Parameter(Mandatory=$true)][String]$data ,[Parameter(Mandatory=$true)][byte[]]$keyBytes ) $hmacsha = New-Object System.Security.Cryptography.HMACSHA256 $hmacsha.Key = $keyBytes $bytesToSign = [Text.Encoding]::UTF8.GetBytes($data) $sign = $hmacsha.ComputeHash($bytesToSign) return $sign } Function Get-Base64UrlEncodeFromString { param( [Parameter(Mandatory=$true)][String]$inputString ) $inputBytes = [Text.Encoding]::UTF8.GetBytes($inputString) # Special "url-safe" base64 encode. $base64 = [System.Convert]::ToBase64String($inputBytes,[Base64FormattingOptions]::None).Replace('+', '-').Replace('/', '_').Replace("=", "") return $base64 } Function Get-Base64UrlEncodeFromByteArray { param( [Parameter(Mandatory=$true)][byte[]]$byteArray ) # Special "url-safe" base64 encode. $base64 = [System.Convert]::ToBase64String($byteArray,[Base64FormattingOptions]::None).Replace('+', '-').Replace('/', '_').Replace("=", "") return $base64 } Function Check-Base64 { # Add missing "=" at the end and check url-safe encodings param( [Parameter(Mandatory=$true)][String]$inputString ) $encoded = $inputString.Replace('-','+').Replace('_','/') $d = $encoded.Length % 4 if ( $d -ne 0 ) { $encoded = $encoded.TrimEnd('=') $encoded += '=' * ((4 - $d) % 4) } return $encoded } Function Create-JWT { param( [Parameter(Mandatory=$true)][System.Collections.Specialized.OrderedDictionary]$headers ,[Parameter(Mandatory=$true)][System.Collections.Specialized.OrderedDictionary]$payload ,[Parameter(Mandatory=$true)][string]$base64Secret ) # 生成紧凑格式JSON,避免多余空格导致编码不一致 $headersJson = $headers | ConvertTo-Json -Compress $payloadJson = $payload | ConvertTo-Json -Compress $headersEncoded = Get-Base64UrlEncodeFromString -inputString $headersJson $payloadEncoded = Get-Base64UrlEncodeFromString -inputString $payloadJson $content = "$headersEncoded.$payloadEncoded" # 解码Base64密钥为字节数组,对应Postman的CryptoJS.enc.Base64.parse $standardBase64 = Check-Base64 -inputString $base64Secret $keyBytes = [System.Convert]::FromBase64String($standardBase64) $signatureByte = Get-HMACSHA256 -data $content -keyBytes $keyBytes $signature = Get-Base64UrlEncodeFromByteArray -byteArray $signatureByte $jwt = "$headersEncoded.$payloadEncoded.$signature" return $jwt } # MAIN CODE # Get Username to verify $username = Read-Host "Enter username in UPN format (i.e. SmithJ@google.com) -->" #Header $headers=[ordered]@{alg=$Algorithm; org_alias= $org_alias; token=$SecretKey} #Payload $payload=[ordered]@{ reqHeader = [ordered]@{ locale='en'; orgAlias=$org_alias; secretKey=$SecretKey; # 生成标准ISO 8601格式时间戳,和Postman对齐 timestamp=((Get-Date).ToUniversalTime()).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); version='4.9' } reqBody = [ordered]@{ getSameDeviceUsers = "true"; userName = $username } } # Create JWT $jwt = Create-JWT -headers $headers -payload $payload -base64Secret $use_base64_key #sending request $RESTheader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $RESTheader.Add("Content-Type", "application/json") $apiresponse = Invoke-RestMethod -Uri 'https://idpxnyl3m.pingidentity.com/pingid/rest/4/getuserdetails/do' -Method 'POST' -Headers $RESTheader -Body $jwt
核心修正点
- 密钥解码逻辑:在
Create-JWT中先将url-safe格式的Base64密钥转换为标准Base64,再用[System.Convert]::FromBase64String解码为字节数组,完全匹配Postman的CryptoJS.enc.Base64.parse行为。 - JSON序列化:添加
-Compress参数生成无空格的紧凑JSON,确保Base64编码结果和Postman一致。 - 时间戳格式:将时间戳格式化为
yyyy-MM-ddTHH:mm:ss.fffZ标准ISO 8601格式,避免PowerShell默认格式导致的签名差异。 - 函数名修正:将
Get-HMACSHA512改为Get-HMACSHA256,和实际使用的HS256算法对应,避免混淆。
内容的提问来源于stack exchange,提问作者Brian Tabor
相关产品推荐
相关产品推荐

