DocuSign JWT 访问令牌请求

Posted

技术标签:

【中文标题】DocuSign JWT 访问令牌请求【英文标题】:DocuSign JWT Access Token Request 【发布时间】:2020-11-01 23:14:00 【问题描述】: 我正在尝试在沙盒环境中获取访问令牌。 我有一个 VB.NET 应用程序并引用了 DocuSign.eSign.dll 我检查了 docusign C# 代码示例,但无法让它们在 vb.net 中运行

这是我尝试的第一种方法:

Dim ac As ApiClient = New ApiClient()
Dim privateKeyStream() As Byte = Convert.FromBase64String(PrivateKey)
Dim tokenInfo As OAuth.OAuthToken = ac.RequestJWTUserToken("INTEGRATION_ID", "ACCOUNT_ID", "https://account-d.docusign.com/oauth/token", privateKeyStream, 1)

这导致了以下错误:

System.Exception
  HResult=0x80131500
  Message=Unexpected PEM type
  Source=DocuSign.eSign
  StackTrace:
   at DocuSign.eSign.Client.ApiClient.CreateRSAKeyFromPem(String key) ...

第二种方法是使用以下代码:

Dim privateKeyStream As Stream = New FileStream("D:\docusign.pem", FileMode.Open)
'Dim privateKeyStream As Stream = New MemoryStream(Encoding.UTF8.GetBytes(PK))
Using SR = New StreamReader(privateKeyStream)
    If Not SR Is Nothing And SR.Peek() > 0 Then
        Dim privateKeyBytes() As Byte = ReadAsBytes(privateKeyStream)
        'Dim privateKeyBytes() As Byte = StreamToByteArray(privateKeyStream)
        'Dim privateKeyBytes() As Byte = Convert.FromBase64String(PrivateKey)
        'Dim privateKeyBytes() As Byte = Encoding.UTF8.GetBytes(PrivateKey)

        Dim privateKeyS As String = Encoding.UTF8.GetString(privateKeyBytes)

        Dim handler As JwtSecurityTokenHandler = New JwtSecurityTokenHandler()
        handler.SetDefaultTimesOnTokenCreation = False

        Dim descriptor As SecurityTokenDescriptor = New SecurityTokenDescriptor()
        descriptor.Expires = DateTime.UtcNow.AddHours(1)
        descriptor.IssuedAt = DateTime.UtcNow

        Dim scopes As List(Of String) = New List(Of String)
        scopes.Add(OAuth.Scope_SIGNATURE)

        descriptor.Subject = New ClaimsIdentity()
        descriptor.Subject.AddClaim(New Claim("scope", String.Join(" ", scopes)))
        descriptor.Subject.AddClaim(New Claim("aud", "account-d.docusign.com"))
        descriptor.Subject.AddClaim(New Claim("iss", "INTEGRATION_ID"))
        descriptor.Subject.AddClaim(New Claim("sub", "ACCOUNT_ID"))

        Dim RSA = CreateRSAKeyFromPem(privateKeyS)
        Dim rsaKey As RsaSecurityKey = New RsaSecurityKey(RSA)
        descriptor.SigningCredentials = New SigningCredentials(rsaKey, SecurityAlgorithms.RsaSha256Signature)


        Dim Token = handler.CreateToken(descriptor)
        Dim jwtToken As String = handler.WriteToken(Token)

        Dim baseUri As String = String.Format("https://0/", basePath)
        Dim RestClient As RestClient = New RestClient(baseUri)
        RestClient.Timeout = 10000

        Dim contentType As String = "application/x-www-form-urlencoded"

        Dim formParams As New Dictionary(Of String, String)
        formParams.Add("grant_type", OAuth.Grant_Type_JWT)
        formParams.Add("assertion", jwtToken)

        Dim queryParams As New Dictionary(Of String, String)

        Dim headerParams As New Dictionary(Of String, String)
        headerParams.Add("Content-Type", "application/x-www-form-urlencoded")
        headerParams.Add("Cache-Control", "no-store")
        headerParams.Add("Pragma", "no-cache")

        Dim fileParams As New Dictionary(Of String, FileParameter)
        Dim pathParams As New Dictionary(Of String, String)

        Dim postBody As Object = Nothing

        Dim request As RestRequest = PrepareRequest(basePath, Method.POST, queryParams, postBody, headerParams, formParams, fileParams, pathParams, contentType)

        Dim response As IRestResponse = RestClient.Execute(request)

        If (response.StatusCode >= HttpStatusCode.OK And response.StatusCode < HttpStatusCode.BadRequest) Then
            Dim tokenInfo As OAuth.OAuthToken = JsonConvert.DeserializeObject(Of OAuth.OAuthToken)(response.Content)
            Return tokenInfo.access_token
        Else
            Throw New ApiException(response.StatusCode, "Error while requesting server, received a non successful HTTP code " & response.ResponseStatus & " with response Body: " + response.Content, response.Content)
        End If
    Else
        Throw New ApiException(400, "Private key stream not supplied or is invalid!")
    End If
End Using

这导致与第一个解决方案相同:

System.Exception
  HResult=0x80131500
  Message=Unexpected PEM type
  Source=PropertyServer
  StackTrace:
   at PropertyServer.classDocusign.CreateRSAKeyFromPem(String key) in...

我的第三种方法如下:

Dim ar1 As JObject = New JObject()
ar1.Add("typ", "JWT")
ar1.Add("alg", "RS256")

Dim header As String = Base64UrlEncoder.Encode(ar1.ToString)

Dim ar2 As JObject = New JObject()
ar2.Add("iss", "INTEGRATION_ID")
ar2.Add("sub", "ACCOUNT_ID")
ar2.Add("iat", DateDiff(DateInterval.Second, New Date(1970, 1, 1), Now))
ar2.Add("exp:", DateDiff(DateInterval.Second, New Date(1970, 1, 1), DateAdd(DateInterval.Hour, 1, Now)))
ar2.Add("aud:", "account-d.docusign.com")
ar2.Add("scope", "signature impersonation")

Dim body As String = Base64UrlEncoder.Encode(ar2.ToString)

Dim stringToSign As String = header & "." & body

Dim bytesToSign() As Byte = Encoding.UTF8.GetBytes(stringToSign)

'Dim data() As Byte = Encoding.UTF8.GetBytes(PrivateKey)
'Dim b64 As String = System.Text.Encoding.UTF8.GetString(data)

Dim keyBytes() As Byte = Convert.FromBase64String(PrivateKey)

Dim privKeyObj = Asn1Object.FromByteArray(keyBytes)
Dim privStruct = RsaPrivateKeyStructure.GetInstance(privKeyObj)

Dim sig As ISigner = SignerUtilities.GetSigner("SHA256withRSA")

sig.Init(True, New RsaKeyParameters(True, privStruct.Modulus, privStruct.PrivateExponent))

sig.BlockUpdate(bytesToSign, 0, bytesToSign.Length)
Dim signature() As Byte = sig.GenerateSignature()

Dim sign As String = Base64UrlEncoder.Encode(signature)

Return header & "." & body & "." & sign

在我的第 3 次尝试中,我能够获得结果,但是当我尝试将其作为 Postman 中请求的断言部分发布时,它返回以下内容:


    "error": "invalid_grant",
    "error_description": "no_valid_keys_or_signatures"

我已经花了几个小时来解决这个问题,但没有运气,在此先感谢。

注意:这些是进口的

Imports System.IO
Imports System.Net
Imports System.Text
Imports DocuSign.eSign.Api
Imports DocuSign.eSign.Client
Imports DocuSign.eSign.Client.Auth
Imports DocuSign.eSign.Model
Imports Microsoft.Azure.KeyVault.Cryptography.Algorithms
Imports Newtonsoft.Json.Linq
Imports System.Security.Cryptography
Imports System
Imports System.Collections.Generic
Imports Org.BouncyCastle.Crypto
Imports Org.BouncyCastle.Crypto.Parameters
Imports Org.BouncyCastle.Security
Imports Newtonsoft.Json
Imports Org.BouncyCastle.Asn1
Imports Org.BouncyCastle.Asn1.Pkcs
Imports RestSharp
Imports System.IdentityModel.Tokens.Jwt
Imports Microsoft.IdentityModel.Tokens
Imports System.Security.Claims
Imports Org.BouncyCastle.OpenSsl
Imports System.Security.Cryptography.X509Certificates

【问题讨论】:

【参考方案1】:

听起来你的第三种方法是最好的。特别是因为您的主张不正确。它们应该是:

ar2.Add("iss", "INTEGRATION_ID")
# sub is NOT the account id
ar2.Add("sub", "GUID_VERSION_OF_USER_ID")
# Check that your date is correct
ar2.Add("iat", DateDiff(DateInterval.Second, New Date(1970, 1, 1), Now))
# should be exp, not exp:
ar2.Add("exp", DateDiff(DateInterval.Second, New Date(1970, 1, 1), 
DateAdd(DateInterval.Hour, 1, Now)))
# should be "aud", not "aud:"
ar2.Add("aud", "account-d.docusign.com")
# only need signature. impersonation is automatically implied.
ar2.Add("scope", "signature")

我找不到任何对通过 VB 创建 RS256 JWT 非常有用的东西。

This company 提供收费的东西,但我没有这方面的经验。

Microsoft 显然现在可以创建 RS256 JWT,但我找不到有用的示例。 Docs.

请在您弄清楚后提交您自己的问题的答案。谢谢!!

添加

我建议打印出您的软件生成的 JWT,然后使用验证工具或程序来验证您的软件生成的 JWT 是否符合您的预期。

不幸的是,许多在线验证者只是对声明进行解码。您还需要检查签名是否正确。 (使用 DocuSign 提供的 RSA 对中的公钥。)

【讨论】:

感谢您的回复。我已经应用了您的修复程序,包括 GUID_VERSION_OF_USER_ID * 我已经应用了与拼写错误相关的修复程序 * 根据 Sandbox > Settings > API & Keys > User ID 更新子值 * 仅在范围内包含签名 * 尝试根据更新 iat 和 exp 值到 UTC 和本地时间 * 尝试从请求中删除 iat 和 exp 值到目前为止没有任何变化:/ 查看我对答案的补充。将您的软件生成的 JWT 添加到您的问题(编辑您的问题)中,以及您的集成密钥的公钥。 @LarryK 你能看看我的问题吗?***.com/questions/62873306/…【参考方案2】:

终于在 Larry K 的帮助下成功了! 我不确定在昨天拉里的回复后我又错过了什么,但我现在可以使用以下代码获取访问令牌:(顺便说一下,将 iat 和 exp 设置为 UTC 或本地时间并没有什么区别)

...另外 PriveKey 声明如下:

Dim PrivateKey As String = "MIIEowIBAAKCAQEAjtTe7UUP/CBI9s...BLABLABLA...JfwZ2hHqFPXA9ecbhc0".Replace(vbLf, "").Replace(vbCr, "")

Dim ar1 As JObject = New JObject()
ar1.Add("typ", "JWT")
ar1.Add("alg", "RS256")

Dim header As String = Base64UrlEncoder.Encode(ar1.ToString)

Dim ar2 As JObject = New JObject()
ar2.Add("iss", "INTEGRATION_ID")
ar2.Add("sub", "GUID_VERSION_OF_USER_ID")
ar2.Add("iat", DateDiff(DateInterval.Second, New Date(1970, 1, 1), Now().ToUniversalTime))
ar2.Add("exp", DateDiff(DateInterval.Second, New Date(1970, 1, 1), DateAdd(DateInterval.Hour, 1, Now().ToUniversalTime)))
ar2.Add("aud", "account-d.docusign.com")
ar2.Add("scope", "signature")

Dim body As String = Base64UrlEncoder.Encode(ar2.ToString)

Dim stringToSign As String = header & "." & body

Dim bytesToSign() As Byte = Encoding.UTF8.GetBytes(stringToSign)

Dim keyBytes() As Byte = Convert.FromBase64String(PrivateKey)

Dim privKeyObj = Asn1Object.FromByteArray(keyBytes)
Dim privStruct = RsaPrivateKeyStructure.GetInstance(privKeyObj)

Dim sig As ISigner = SignerUtilities.GetSigner("SHA256withRSA")

sig.Init(True, New RsaKeyParameters(True, privStruct.Modulus, privStruct.PrivateExponent))

sig.BlockUpdate(bytesToSign, 0, bytesToSign.Length)
Dim signature() As Byte = sig.GenerateSignature()

Dim sign As String = Base64UrlEncoder.Encode(signature)

Return header & "." & body & "." & sign

非常感谢拉里 K!

【讨论】:

以上是关于DocuSign JWT 访问令牌请求的主要内容,如果未能解决你的问题,请参考以下文章

DocuSign JWT 访问令牌无效

Docusign REST API:使用 Microsoft Flow 的 JWT Grant 实现 OAuth 身份验证

无法使用 DocuSign Python 代码生成 jwt 令牌

DocuSign 获取 JWT 令牌 MEAN Stack

从 JWT 生成访问令牌

使用 Spring Security OAuth2 进行访问令牌请求的 JWT 不记名交换