You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

核心修正点

  1. 密钥解码逻辑:在Create-JWT中先将url-safe格式的Base64密钥转换为标准Base64,再用[System.Convert]::FromBase64String解码为字节数组,完全匹配Postman的CryptoJS.enc.Base64.parse行为。
  2. JSON序列化:添加-Compress参数生成无空格的紧凑JSON,确保Base64编码结果和Postman一致。
  3. 时间戳格式:将时间戳格式化为yyyy-MM-ddTHH:mm:ss.fffZ标准ISO 8601格式,避免PowerShell默认格式导致的签名差异。
  4. 函数名修正:将Get-HMACSHA512改为Get-HMACSHA256,和实际使用的HS256算法对应,避免混淆。

内容的提问来源于stack exchange,提问作者Brian Tabor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 22:05:22