如何从 .net 核心客户端生成 JWT Bearer Flow OAuth 访问令牌?

Posted

技术标签:

【中文标题】如何从 .net 核心客户端生成 JWT Bearer Flow OAuth 访问令牌?【英文标题】:How to generate JWT Bearer Flow OAuth access tokens from a .net core client? 【发布时间】:2020-09-04 05:14:46 【问题描述】:

我无法让我的 .NET Core 客户端为需要“JWT Bearer Flow”类型的 OAuth 的 Salesforce 端点生成 OAuth 访问令牌。

似乎有有限的 .NET Framework 示例显示 .NET 客户端执行此操作,但没有显示 .NET Core 客户端执行此操作 例如 https://salesforce.stackexchange.com/questions/53662/oauth-jwt-token-bearer-flow-returns-invalid-client-credentials

所以在我的 .NET Core 3.1 应用程序中,我生成了一个自签名证书,在加载证书时将私钥添加到上述示例的代码中,但是在这一行发生了 System.InvalidCastExceptionexception 异常:

var rsa = certificate.GetRSAPrivateKey() as RSACryptoServiceProvider;

例外:

System.InvalidCastException: 'Unable to cast object of type 'System.Security.Cryptography.RSACng' to type 'System.Security.Cryptography.RSACryptoServiceProvider'.'

这个私钥似乎在 JWT Bearer Flow 中用作签名的一部分,并且可能 RSACryptoServiceProvider 没有像在 .NET Framework 中那样在 .NET Core 中使用。

我的问题是——在 .NET Core 中是否真的有一种方法可以为 OAuth JWT Bearer Flow 生成访问令牌?

我正在使用的完整代码:

static void Main(string[] args)
    
        Console.WriteLine("Hello World!");
        var token = GetAccessToken();
    

    static dynamic GetAccessToken()
    
        // get the certificate
        var certificate = new X509Certificate2(@"C:\temp\cert.pfx");

        // create a header
        var header = new  alg = "RS256" ;

        // create a claimset
        var expiryDate = GetExpiryDate();
        var claimset = new
        
            iss = "xxxxxx",
            prn = "xxxxxx",
            aud = "https://test.salesforce.com",
            exp = expiryDate
        ;

        // encoded header
        var headerSerialized = JsonConvert.SerializeObject(header);
        var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
        var headerEncoded = ToBase64UrlString(headerBytes);

        // encoded claimset
        var claimsetSerialized = JsonConvert.SerializeObject(claimset);
        var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
        var claimsetEncoded = ToBase64UrlString(claimsetBytes);

        // input
        var input = headerEncoded + "." + claimsetEncoded;
        var inputBytes = Encoding.UTF8.GetBytes(input);

        // signature
        var rsa = (RSACryptoServiceProvider) certificate.GetRSAPrivateKey();

        var cspParam = new CspParameters
        
            KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
            KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
        ;
        var aescsp = new RSACryptoServiceProvider(cspParam)  PersistKeyInCsp = false ;
        var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
        var signatureEncoded = ToBase64UrlString(signatureBytes);

        // jwt
        var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded;

        var client = new WebClient();
        client.Encoding = Encoding.UTF8;
        var uri = "https://login.salesforce.com/services/oauth2/token";
        var content = new NameValueCollection();

        content["assertion"] = jwt;
        content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";

        string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));

        var result = JsonConvert.DeserializeObject<dynamic>(response);

        return result;
    

    static int GetExpiryDate()
    
        var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        var currentUtcTime = DateTime.UtcNow;

        var exp = (int)currentUtcTime.AddMinutes(4).Subtract(utc0).TotalSeconds;

        return exp;
    

    static string ToBase64UrlString(byte[] input)
    
        return Convert.ToBase64String(input).TrimEnd('=').Replace('+', '-').Replace('/', '_');
    

【问题讨论】:

【参考方案1】:

嗯 - 事实证明,发布到 *** 会让大脑的齿轮转动。

答案最终是深入研究以在此处找到类似的问题并使用x509certificate2 sign for jwt in .net core 2.1的解决方案

我最终替换了以下代码:

var cspParam = new CspParameters

      KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
      KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
;
var aescsp = new RSACryptoServiceProvider(cspParam)  PersistKeyInCsp = false ;
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = ToBase64UrlString(signatureBytes);

使用 System.IdentityModel.Tokens.Jwt nuget 包的代码:

var signingCredentials = new X509SigningCredentials(certificate, "RS256");
var signature = JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials);

解决后的完整代码:

static void Main(string[] args)
    
        Console.WriteLine("Hello World!");
        var token = GetAccessToken();
    

    static dynamic GetAccessToken()
    
        // get the certificate
        var certificate = new X509Certificate2(@"C:\temp\cert.pfx");

        // create a header
        var header = new  alg = "RS256" ;

        // create a claimset
        var expiryDate = GetExpiryDate();
        var claimset = new
        
            iss = "xxxxx",
            prn = "xxxxx",
            aud = "https://test.salesforce.com",
            exp = expiryDate
        ;

        // encoded header
        var headerSerialized = JsonConvert.SerializeObject(header);
        var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
        var headerEncoded = ToBase64UrlString(headerBytes);

        // encoded claimset
        var claimsetSerialized = JsonConvert.SerializeObject(claimset);
        var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
        var claimsetEncoded = ToBase64UrlString(claimsetBytes);

        // input
        var input = headerEncoded + "." + claimsetEncoded;
        var inputBytes = Encoding.UTF8.GetBytes(input);

        var signingCredentials = new X509SigningCredentials(certificate, "RS256");
        var signature = JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials);

        // jwt
        var jwt = headerEncoded + "." + claimsetEncoded + "." + signature;

        var client = new WebClient();
        client.Encoding = Encoding.UTF8;
        var uri = "https://test.salesforce.com/services/oauth2/token";
        var content = new NameValueCollection();

        content["assertion"] = jwt;
        content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";

        string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));

        var result = JsonConvert.DeserializeObject<dynamic>(response);

        return result;
    

    static int GetExpiryDate()
    
        var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        var currentUtcTime = DateTime.UtcNow;

        var exp = (int)currentUtcTime.AddMinutes(4).Subtract(utc0).TotalSeconds;

        return exp;
    

    static string ToBase64UrlString(byte[] input)
    
        return Convert.ToBase64String(input).TrimEnd('=').Replace('+', '-').Replace('/', '_');
    

【讨论】:

这是一个很好的例子。在我们的 sanbox 环境中尝试此操作时出现 400 错误。关于追踪问题的任何建议?证书和签名看起来都不错。 我会仔细查看 400 响应文本,它应该可以为您提供问题所在的线索。它可能是您的证书、颁发者/用户名信息、如何在 Salesforce 中为连接的应用程序设置 OAuth 等。以上内容适用于我们在所有 salesforce 环境中进行 JWT OAuth 身份验证 您好,我已按照您的步骤操作并获得了签名凭据。但是我在获得签名时遇到了问题。如果您能对此提供帮助,我们将不胜感激。获取签名时出现此异常。 "System.InvalidOperationException: 'IDX10638: 无法创建 SignatureProvider,'key.HasPrivateKey' 为 false,无法创建签名。密钥:[PII 已隐藏。更多详细信息,请参阅“

以上是关于如何从 .net 核心客户端生成 JWT Bearer Flow OAuth 访问令牌?的主要内容,如果未能解决你的问题,请参考以下文章

使用 JWT 令牌保护 asp.net 核心 Web api 时如何从 Azure AD 获取用户

如何让 JWT 在 Autorest 生成的 SDK 中工作? (ASP.NET 核心 2.0)

如何验证从另一个应用程序生成的 JWT 令牌?

如何使用 OAuth 2.0 客户端凭据流和带有 .net 核心的 JWT 证书连接到 Oracle Netsuite

JWT:如何从声明中的特定键获取值列表。 C# Asp.Net 核心

如何使用 .net 核心中的 SecurityTokenDescriptor 将孩子添加到 jwt 标头