如何从.net核心客户端生成JWT承载流OAuth访问令牌?

时间:2020-05-18 11:55:53

标签: c# .net-core oauth jwt salesforce

我无法让.NET Core客户端为需要“ JWT承载流”类型的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承载流中用作签名的一部分,也许RSACryptoServiceProvider在.NET核心中没有像在.NET Framework中那样使用。

我的问题是这样-.NET Core中实际上有一种方法可以为OAuth JWT承载流生成访问令牌吗?

我正在使用的完整代码:

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 个答案:

答案 0 :(得分:2)

好吧-事实证明,发布到stackoverflow会使大脑齿轮转动。

最终答案是深入研究以在此处找到类似的问题,并使用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('/', '_');
    }