我正在尝试通过后端应用程序连接到 Epic 沙箱环境。我一直在遵循这个指南,但我认为我的 JWT 签名遇到了问题。这是我用来生成令牌的代码
class CreateJwt
{
public static string? Token { get; set; }
public static void Jwt()
{
try
{
// reading the content of a private key PEM file
string privateKeyPem = File.ReadAllText("./private_epic_backend_key.pem");
// keeping only the payload of the key
privateKeyPem = privateKeyPem.Replace("-----BEGIN PRIVATE KEY-----", "");
privateKeyPem = privateKeyPem.Replace("-----END PRIVATE KEY-----", "");
byte[] privateKeyRaw = Convert.FromBase64String(privateKeyPem);
// creating the RSA key
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.ImportPkcs8PrivateKey(new ReadOnlySpan<byte>(privateKeyRaw), out _);
RsaSecurityKey rsaSecurityKey = new RsaSecurityKey(provider);
// Generating the token
var now = DateTime.UtcNow;
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Iss, "1a467f07-8a84-4e25-a493-4ba9d5ef9054"),
new Claim(JwtRegisteredClaimNames.Sub, "1a467f07-8a84-4e25-a493-4ba9d5ef9054")
};
var handler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken
(
"1a467f07-8a84-4e25-a493-4ba9d5ef9054",
"https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
claims,
now.AddMilliseconds(-30),
now.AddMinutes(4),
new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha384)
);
// handler.WriteToken(token) returns the token ready to send
Token = handler.WriteToken(token);
Console.WriteLine(Token);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.WriteLine(
new System.Diagnostics.StackTrace().ToString()
);
}
}
}
我认为问题出在签名上的原因是,当我将令牌加载到 JWT.IO 中时,我收到“无效签名”响应。然后这是我发送的代码。
class GetBearerToken
{
private static readonly HttpClient client = new HttpClient();
public static string? Bearer { get; set; }
public async static Task Authorize()
{
try
{
var values = new Dictionary<string, string>
{
{ "grant_type", "client_credentials" },
{ "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" },
{ "client_assertion", CreateJwt.Token }
};
var content = new FormUrlEncodedContent(values);
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
var requeststring = await content.ReadAsStringAsync();
Console.WriteLine(requeststring);
var response = await client.PostAsync("https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token", content);
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine("response from post");
Console.WriteLine(responseString);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.WriteLine(
new System.Diagnostics.StackTrace().ToString()
);
}
}
}
这看起来与他们根据 Epic 提供的文档所期望的完全一样,但我得到的回报是
“错误”:“无效的客户端”, “错误描述”:空
Epic 提供了一个故障排除文档来解决这个特定的错误,但它似乎更像是一个包罗万象的错误。我已经执行了两次故障排除步骤,但仍然无法克服此错误。
任何帮助将不胜感激。
// This is the refactoring of these two classes that will work:
// env. .NET 7
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
namespace EPICAPI.AppTest
{
public class Program
{
static void Main(string[] args)
{
_ = GetBearerToken.Authorize();
}
}
public static class CreateJwt
{
public static string Jwt()
{
try
{
// reading the content of a private key PEM file
string privateKeyPem = File.ReadAllText(@"L:\OpenSSL\keys\privatekey.pem");
// keeping only the payload of the key
privateKeyPem = privateKeyPem.Replace("-----BEGIN PRIVATE KEY-----", string.Empty);
privateKeyPem = privateKeyPem.Replace("-----END PRIVATE KEY-----", string.Empty);
byte[] privateKeyRaw = Convert.FromBase64String(privateKeyPem);
// Generating the token
DateTime now = DateTime.UtcNow;
Guid gJti = Guid.NewGuid(); // you may improve this
IEnumerable<Claim> claims = new[] {
// new Claim(JwtRegisteredClaimNames.Iss, "xxxxx-xxx-xxx-xx-xxx"), // do not use this (Iss) in claims
new Claim(JwtRegisteredClaimNames.Sub, "xxxxx-xxx-xxx-xx-xxx"),
new Claim(JwtRegisteredClaimNames.Jti, gJti.ToString())
};
JwtSecurityTokenHandler handler = new();
// creating the RSA key
RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
provider.ImportPkcs8PrivateKey(new ReadOnlySpan<byte>(privateKeyRaw), out _);
RsaSecurityKey rsaSecurityKey = new RsaSecurityKey(provider);
JwtSecurityToken jwtToken = new JwtSecurityToken
(
issuer: "xxxxx-xxx-xxx-xx-xxx",
audience: "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
claims: claims,
notBefore: now.AddMilliseconds(-30),
expires: now.AddMinutes(4),
signingCredentials: new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha384)
);
string accessToken = new JwtSecurityTokenHandler().WriteToken(jwtToken);
Console.WriteLine(accessToken);
return accessToken;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.WriteLine(new System.Diagnostics.StackTrace().ToString());
return string.Empty;
}
}
}
public static class GetBearerToken
{
public static void Authorize()
{
string token = CreateJwt.Jwt();
try
{
HttpClient client = new();
HttpRequestMessage request = new(HttpMethod.Post, "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token");
List<KeyValuePair<string, string>> values = new()
{
new("grant_type", "client_credentials"),
new("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"),
new("client_assertion", token)
};
FormUrlEncodedContent content = new(values);
request.Content = content;
HttpResponseMessage response = client.Send(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(response.Content.ReadAsStringAsync());
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
Console.WriteLine(new System.Diagnostics.StackTrace().ToString());
}
}
}
}