问题已被编辑以包含一些开发代码。
只是想了解通过各种 EMR 供应商进行 FHIR 身份验证的各种方法。 我在 AthenaHealth、eClinicalWorks 和 EPIC 拥有沙箱帐户。 我在 Athenahealth 中使用 BasicAuthentication 和 clientID/Secret 来检索令牌取得了一些成功,然后该令牌可用于通过对各种范围的适当授权来发出后续请求。我还获得了 EPIC FHIR 沙箱来与 JWT 配合使用。
我当前的问题是,迄今为止我无法通过 eClinicalWorks 沙箱环境进行身份验证。 类似的代码实际上适用于使用 JWT 方法的 EPIC 沙箱。
下面的 Python 函数适用于使用基本身份验证方法的 AtheneHealth,其中 TOKEN_URL='..../oauth2/v1/token'
def get_access_token():
response = requests.post(
TOKEN_URL,
auth=HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET),
headers={'Content-Type': 'application/x-www-form-urlencoded'},
data={
'grant_type': 'client_credentials',
'scope': '....'
}
)
response.raise_for_status()
return response.json()['access_token']
ECW 文档位于此处:ECW API 文档
我使用以下方法生成了自己的公钥-私钥对:
# Generate a private key
openssl genrsa -out private_key.pem 2048
# Extract the public key
openssl req -new -x509 -key private_key.pem -out public.pem -subj '/CN=SandboxTester'
# Get the fingerprint
openssl x509 -noout -fingerprint -sha1 -inform pem -in public.pem
# e.g. sha1 Fingerprint=A9:18:FF:9E:A2:99:0F:24:C6:1A:CF:4C:B8:09:0C:0D:0E:5D:49:B3
然后我使用此 Python 代码生成一个 JWK 集:
import hashlib
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography import x509
from base64 import urlsafe_b64encode
import json
# Load your public key from the certificate
with open("public.pem", "rb") as cert_file:
public_key = x509.load_pem_x509_certificate(cert_file.read(), default_backend()).public_key()
# Extract the modulus (n) and exponent (e) from the public key
numbers = public_key.public_numbers()
n = numbers.n
e = numbers.e
# Convert the modulus and exponent to URL-safe base64 encoding
n_b64 = urlsafe_b64encode(n.to_bytes((n.bit_length() + 7) // 8, byteorder='big')).decode('utf-8').rstrip("=")
e_b64 = urlsafe_b64encode(e.to_bytes((e.bit_length() + 7) // 8, byteorder='big')).decode('utf-8').rstrip("=")
# Generate the kid using a SHA-256 fingerprint
pub_key_bytes = public_key.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)
kid = hashlib.sha256(pub_key_bytes).hexdigest()
# Create the JWK structure
jwk = {
"kty": "RSA",
"use": "sig",
"alg": "RS384",
"n": n_b64,
"e": e_b64,
"kid": kid, # example Key ID, ensure this matches your requirements
"key_ops": ["verify"], # example key operations, adjust as needed
"ext": True
}
# Create the JWK Set structure
jwk_set = {"keys": [jwk]}
# Print the JWK in a pretty JSON format
print(json.dumps(jwk_set, indent=4))
# Save the JWK to a file
with open("jwk.json", "w") as jwk_file:
json.dump(jwk_set, jwk_file, indent=4)
print("JWK saved to jwk.json")
然后我的 Python 测试脚本是这样的,减去一些配置值。
import os
import json
import time
import jwt
import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
import uuid
CLIENT_ID = "omitted" #Staging
# TOKEN_URL = 'https://oauthserver.eclinicalworks.com/oauth/oauth2/token' #Production
TOKEN_URL = "https://staging-oauthserver.ecwcloud.com/oauth/oauth2/token" #Staging
# AUTH_URL = 'https://oauthserver.eclinicalworks.com/oauth/oauth2/authorize' #Production
AUTH_URL = "https://staging-oauthserver.ecwcloud.com/oauth/oauth2/authorize" #Staging
JWKS_URL = "omitted"
# Path to the private key file
private_key_file = "private_key.pem"
# Read the private key from the file
with open(private_key_file, "rb") as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None,
backend=default_backend()
)
# Generate a signed JWT with RS384
def generate_jwt():
now = int(time.time())
exp = now + 300 # Expiration time no more than five minutes in the future
claims = {
"iss": CLIENT_ID,
"sub": CLIENT_ID,
"aud": TOKEN_URL,
"exp": exp,
"iat": now,
"jti": str(uuid.uuid4()) # Generate a unique JWT ID
}
headers = {
"alg": "RS384",
"kid": "omitted",
"typ": "JWT",
"jku": JWKS_URL # Optional, if your JWK Set URL is available
}
token = jwt.encode(
payload=claims,
key=private_key,
algorithm="RS384",
headers=headers
)
print("Generated JWT:", token) # Debugging line to check the JWT
return token
# Function to get a new access token using JWT
def get_access_token():
signed_jwt = generate_jwt()
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
data = {
'grant_type': 'client_credentials',
'scope': 'system/Patient.read system/Encounter.read system/Group.read', # Adjust scope as needed
'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion': signed_jwt
}
print("Request Headers:", headers) # Debugging line to check headers
print("Request Data:", data) # Debugging line to check data
response = requests.post(TOKEN_URL, headers=headers, data=data)
print("Response Status Code:", response.status_code)
print("Response Body:", response.text)
response.raise_for_status() # Raise an HTTPError for bad responses
return response.json()['access_token']
# Attempt to get the access token
try:
access_token = get_access_token()
print("Access Token:", access_token)
except requests.exceptions.HTTPError as err:
print(f"HTTP error occurred: {err}")
print(f"Response content: {err.response.content}")
except Exception as err:
print(f"Other error occurred: {err}")
我已经在他们的技术支持下经历过几次,他们还验证了一切似乎都设置正确,并且 JWT 至少进行了手动验证。
任何其他建议表示赞赏。
我得到的错误是:
Response Status Code: 401
Response Body: {"error":"invalid_client"}
HTTP error occurred: 401 Client Error: for url: https://staging-oauthserver.ecwcloud.com/oauth/oauth2/token
Response content: b'{"error":"invalid_client"}'
@SScotti 您是否需要更改任何代码?尝试了解解决方案是站在您这边还是 eClinicalWorks 那边。