Nest.js应用生成CloudFront签名URL遇ERR_OSSL_UNSUPPORTED错误求助
问题:Nest.js中使用AWS SDK生成CloudFront签名URL时出现
ERR_OSSL_UNSUPPORTED错误 错误信息
Error: error:1E08010C:DECODER routines::unsupported at Sign.sign (node:internal/crypto/sig:131:29) at signPolicy (C:\Full Stack JS Apps\testCloudfront\api\node_modules\aws-sdk\lib\cloudfront\signer.js:21:29) at signWithCannedPolicy (C:\Full Stack JS Apps\testCloudfront\api\node_modules\aws-sdk\lib\cloudfront\signer.js:37:20) at Signer.getSignedUrl (C:\Full Stack JS Apps\testCloudfront\api\node_modules\aws-sdk\lib\cloudfront\signer.js:186:19) at S3Service.generateSignedUrl (C:\Full Stack JS Apps\testCloudfront\api\src\common\services\aws\s3\s3.service.ts:57:36) at processTicksAndRejections (node:internal/process/task_queues:95:5) at AppController.getFile (C:\Full Stack JS Apps\testCloudfront\api\src\app.controller.ts:24:17) { library: 'DECODER routines', reason: 'unsupported', code: 'ERR_OSSL_UNSUPPORTED' }
相关代码
生成签名URL的代码
async generateSignedUrl(objectKey: string): Promise<string> { const cloudFrontDomain = 'my-alternate-domain.com'; const keyPairId = 'MyAWSCFPublicKeyId'; try { const privateKey = await this.getCloudFrontPrivateKey(); const cloudFront = new CloudFront.Signer(keyPairId, privateKey); console.log('Signer:', cloudFront); const signedUrl = cloudFront.getSignedUrl({ url: `https://${cloudFrontDomain}/${objectKey}`, expires: Math.floor(Date.now() / 1000) + 3600, // URL expiration time (1 hour) }); return signedUrl; } catch (error) { console.log('Error generating signed URL:', error); throw error; } }
获取CloudFront私钥的代码
private async getCloudFrontPrivateKey(): Promise<string> { try { const secretsManager = new SecretsManager({ accessKeyId: 'MY_ACCESS_KEY', secretAccessKey: 'MY_SECRET_ACCESS_KEY', region: 'us-west-1', }); const response = await secretsManager .getSecretValue({ SecretId: 'test/key' }) .promise(); if (response.SecretString) { const secretData = JSON.parse(response.SecretString); return secretData.privateKey; } else { throw new Error('SecretString is empty'); } } catch (error) { console.log('Error fetching CloudFront private key:', error); throw error; } }
Signer对象打印输出
Signer: Signer { keyPairId: 'MyAWSCFPublicKeyId', privateKey: '-----BEGIN RSA PRIVATE KEY----- contents here -----END RSA PRIVATE KEY-----' }
补充信息
- 已尝试执行
SET NODE_OPTIONS=--openssl-legacy-provider - 本地生成公私钥对的命令:
openssl genrsa -out private_key.pem 2048 openssl rsa -pubout -in private_key.pem -out public_key.pem - 使用环境:node v18.15.0、nest v9.5.0
解决方案
1. 修复私钥格式(还原换行符)
从Secrets Manager获取的私钥会因JSON存储丢失实际换行符,导致OpenSSL无法解析。修改getCloudFrontPrivateKey方法,还原换行符:
private async getCloudFrontPrivateKey(): Promise<string> { try { const secretsManager = new SecretsManager({ accessKeyId: 'MY_ACCESS_KEY', secretAccessKey: 'MY_SECRET_ACCESS_KEY', region: 'us-west-1', }); const response = await secretsManager.getSecretValue({ SecretId: 'test/key' }).promise(); if (response.SecretString) { const secretData = JSON.parse(response.SecretString); // 还原转义的换行符为实际换行 return secretData.privateKey.replace(/\\n/g, '\n'); } else { throw new Error('SecretString is empty'); } } catch (error) { console.log('Error fetching CloudFront private key:', error); throw error; } }
2. 升级到AWS SDK v3
AWS SDK v1在Node.js 18+环境下存在兼容性问题,建议切换到v3:
安装依赖
npm install @aws-sdk/client-cloudfront-signer @aws-sdk/client-secrets-manager
重写签名代码
import { CloudFrontSigner } from '@aws-sdk/client-cloudfront-signer'; import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; async generateSignedUrl(objectKey: string): Promise<string> { const cloudFrontDomain = 'my-alternate-domain.com'; const keyPairId = 'MyAWSCFPublicKeyId'; try { const privateKey = await this.getCloudFrontPrivateKey(); const signer = new CloudFrontSigner({ keyPairId, privateKey, }); const signedUrl = await signer.getSignedUrl({ url: `https://${cloudFrontDomain}/${objectKey}`, expires: Math.floor(Date.now() / 1000) + 3600, }); return signedUrl; } catch (error) { console.log('Error generating signed URL:', error); throw error; } } private async getCloudFrontPrivateKey(): Promise<string> { try { const client = new SecretsManagerClient({ region: 'us-west-1', credentials: { accessKeyId: 'MY_ACCESS_KEY', secretAccessKey: 'MY_SECRET_ACCESS_KEY', }, }); const command = new GetSecretValueCommand({ SecretId: 'test/key' }); const response = await client.send(command); if (response.SecretString) { const secretData = JSON.parse(response.SecretString); return secretData.privateKey.replace(/\\n/g, '\n'); } else { throw new Error('SecretString is empty'); } } catch (error) { console.log('Error fetching CloudFront private key:', error); throw error; } }
3. 验证私钥有效性
用OpenSSL命令验证本地私钥格式是否正确:
openssl rsa -check -in private_key.pem
若输出显示私钥有效,说明算法符合要求;若报错,重新生成私钥并正确上传到Secrets Manager。
4. 确保环境变量生效(Windows)
临时生效:在启动应用的命令行先执行
set NODE_OPTIONS=--openssl-legacy-provider
再启动Nest.js应用;永久生效则在系统环境变量中添加NODE_OPTIONS,值为--openssl-legacy-provider。
内容的提问来源于stack exchange,提问作者D Dev
相关产品推荐
相关产品推荐

