我正在使用无服务器框架将无服务器堆栈部署到AWS。我的堆栈由一些lambda函数、DynamoDB表和API网关组成。
我使用所谓的lambda授权程序来保护API网关。另外,我有一个自定义的独立自托管Auth服务,可以生成令牌。
下面是我的Serverless.yml中的lambda授权程序定义的一个示例,以及我如何使用它来保护其他API网关endpoint:(您可以看到addUserInfo函数具有使用自定义授权程序保护的API)
functions:
# =================================================================
# API Gateway event handlers
# ================================================================
auth:
handler: api/auth/mda-auth-server.handler
addUserInfo:
handler: api/user/create-replace-user-info.handler
description: Create Or Replace user section
events:
- http:
path: user
method: post
authorizer:
name: auth
resultTtlInSeconds: ${self:custom.resultTtlInSeconds}
identitySource: method.request.header.Authorization
type: token
cors:
origin: '*'
headers: ${self:custom.allowedHeaders}
现在我想扩展我的API,以便允许用户添加图像,所以我遵循了这种方法。因此,在这种方法中,用户将启动所谓的S3签名URL,我可以使用这个S3签名URL将图像放到我的桶中。
另外,S3 bucket不能公开访问,但它连接到CloudFront发行版。现在我错过了这里的东西,我不明白如何保护我的图像。这样我就可以用我的自定义身份验证服务保护CloudFront CDN中的映像,以便拥有有效令牌的用户可以访问这些资源吗?如何使用自定义身份验证服务保护CDN(CloudFront)并使用无服务器框架进行配置?
这有点棘手,我花了大约一天的时间来做好准备。
首先,我们在这里有选择:
首先,在我的auth-service.js中有以下代码(它只是一些帮助器,允许我验证自定义jwt):
import * as jwtDecode from 'jwt-decode';
import * as util from 'util';
import * as jwt from 'jsonwebtoken';
import * as jwksClient from 'jwks-rsa';
export function getToken(bearerToken) {
if(bearerToken && bearerToken.startsWith("Bearer "))
{
return bearerToken.replace(/^Bearer\s/, '');
}
throw new Error("Invalid Bearer Token.");
};
export function getDecodedHeader(token) {
return jwtDecode(token, { header: true });
};
export async function getSigningKey(decodedJwtTokenHeader, jwksclient){
const key = await util.promisify(jwksclient.getSigningKey)(decodedJwtTokenHeader.kid);
const signingKey = key.publicKey || key.rsaPublicKey;
if (!signingKey) {
throw new Error('could not get signing key');
}
return signingKey;
};
export async function verifyToken(token,signingKey){
return await jwt.verify(token, signingKey);
};
export function getJwksClient(jwksEndpoint){
return jwksClient({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 10,
jwksUri: jwksEndpoint
});
};
然后在serverless.yml中有我的文件:
service: mda-app-uploads
plugins:
- serverless-offline
- serverless-pseudo-parameters
- serverless-iam-roles-per-function
- serverless-bundle
custom:
stage: ${opt:stage, self:provider.stage}
resourcesBucketName: ${self:custom.stage}-mda-resources-bucket
resourcesStages:
prod: prod
dev: dev
resourcesStage: ${self:custom.resourcesStages.${self:custom.stage}, self:custom.resourcesStages.dev}
provider:
name: aws
runtime: nodejs12.x
stage: ${opt:stage, 'dev'}
region: us-east-1
versionFunctions: true
functions:
oauthEdge:
handler: src/mda-edge-auth.handler
role: LambdaEdgeFunctionRole
memorySize: 128
timeout: 5
resources:
- ${file(resources/s3-cloudfront.yml)}
这里的快速要点:
Resources:
AuthEdgeLambdaVersion:
Type: Custom::LatestLambdaVersion
Properties:
ServiceToken: !GetAtt PublishLambdaVersion.Arn
FunctionName: !Ref OauthEdgeLambdaFunction
Nonce: "Test"
PublishLambdaVersion:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Runtime: nodejs12.x
Role: !GetAtt PublishLambdaVersionRole.Arn
Code:
ZipFile: |
const {Lambda} = require('aws-sdk')
const {send, SUCCESS, FAILED} = require('cfn-response')
const lambda = new Lambda()
exports.handler = (event, context) => {
const {RequestType, ResourceProperties: {FunctionName}} = event
if (RequestType == 'Delete') return send(event, context, SUCCESS)
lambda.publishVersion({FunctionName}, (err, {FunctionArn}) => {
err
? send(event, context, FAILED, err)
: send(event, context, SUCCESS, {FunctionArn})
})
}
PublishLambdaVersionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: PublishVersion
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: lambda:PublishVersion
Resource: '*'
LambdaEdgeFunctionRole:
Type: "AWS::IAM::Role"
Properties:
Path: "/"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Sid: "AllowLambdaServiceToAssumeRole"
Effect: "Allow"
Action:
- "sts:AssumeRole"
Principal:
Service:
- "lambda.amazonaws.com"
- "edgelambda.amazonaws.com"
LambdaEdgeFunctionPolicy:
Type: "AWS::IAM::Policy"
Properties:
PolicyName: MainEdgePolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
Effect: "Allow"
Action:
- "lambda:GetFunction"
- "lambda:GetFunctionConfiguration"
Resource: !GetAtt AuthEdgeLambdaVersion.FunctionArn
Roles:
- !Ref LambdaEdgeFunctionRole
ResourcesBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.resourcesBucketName}
AccessControl: Private
CorsConfiguration:
CorsRules:
- AllowedHeaders: ['*']
AllowedMethods: ['PUT']
AllowedOrigins: ['*']
ResourcesBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: ResourcesBucket
PolicyDocument:
Statement:
# Read permission for CloudFront
- Action: s3:GetObject
Effect: "Allow"
Resource:
Fn::Join:
- ""
-
- "arn:aws:s3:::"
-
Ref: "ResourcesBucket"
- "/*"
Principal:
CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
- Action: s3:PutObject
Effect: "Allow"
Resource:
Fn::Join:
- ""
-
- "arn:aws:s3:::"
-
Ref: "ResourcesBucket"
- "/*"
Principal:
AWS: !GetAtt LambdaEdgeFunctionRole.Arn
- Action: s3:GetObject
Effect: "Allow"
Resource:
Fn::Join:
- ""
-
- "arn:aws:s3:::"
-
Ref: "ResourcesBucket"
- "/*"
Principal:
AWS: !GetAtt LambdaEdgeFunctionRole.Arn
CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment:
Fn::Join:
- ""
-
- "Identity for accessing CloudFront from S3 within stack "
-
Ref: "AWS::StackName"
- ""
# Cloudfront distro backed by ResourcesBucket
ResourcesCdnDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
# S3 origin for private resources
- DomainName: !Sub '${self:custom.resourcesBucketName}.s3.amazonaws.com'
Id: S3OriginPrivate
S3OriginConfig:
OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
# S3 origin for public resources
- DomainName: !Sub '${self:custom.resourcesBucketName}.s3.amazonaws.com'
Id: S3OriginPublic
S3OriginConfig:
OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/#{CloudFrontOriginAccessIdentity}'
Enabled: true
Comment: CDN for public and provate static content.
DefaultRootObject: index.html
HttpVersion: http2
DefaultCacheBehavior:
AllowedMethods:
- DELETE
- GET
- HEAD
- OPTIONS
- PATCH
- POST
- PUT
Compress: true
TargetOriginId: S3OriginPublic
ForwardedValues:
QueryString: false
Headers:
- Origin
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
CacheBehaviors:
-
PathPattern: 'private/*'
TargetOriginId: S3OriginPrivate
AllowedMethods:
- DELETE
- GET
- HEAD
- OPTIONS
- PATCH
- POST
- PUT
Compress: true
LambdaFunctionAssociations:
-
EventType: viewer-request
LambdaFunctionARN: !GetAtt AuthEdgeLambdaVersion.FunctionArn
ForwardedValues:
QueryString: false
Headers:
- Origin
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
-
PathPattern: 'public/*'
TargetOriginId: S3OriginPublic
AllowedMethods:
- DELETE
- GET
- HEAD
- OPTIONS
- PATCH
- POST
- PUT
Compress: true
ForwardedValues:
QueryString: false
Headers:
- Origin
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_200
最后,这里是用于CDN Auth的lambda edge函数的实际代码:
import {getJwksClient, getToken, getDecodedHeader, getSigningKey, verifyToken} from '../../../../libs/services/auth-service';
import config from '../../../../config';
const response401 = {
status: '401',
statusDescription: 'Unauthorized'
};
exports.handler = async (event) => {
try{
const cfrequest = event.Records[0].cf.request;
const headers = cfrequest.headers;
if(!headers.authorization) {
console.log("no auth header");
return response401;
}
const jwtValue = getToken(headers.authorization);
const client = getJwksClient(`https://${config.authDomain}/.well-known/openid-configuration/jwks`);
const decodedJwtHeader = getDecodedHeader(jwtValue);
if(decodedJwtHeader)
{
const signingKey = await getSigningKey(decodedJwtHeader, client);
const verifiedToken = await verifyToken(jwtValue, signingKey);
if(verifiedToken)
{
return cfrequest;
}
}else{
throw Error("Unauthorized");
}
}catch(err){
console.log(err);
return response401;
}
};
如果您感兴趣,我将使用IdentityServer4,并将其作为Azure中的docker映像托管,并将其用作自定义授权程序。
所以完整的场景现在我们有了一个完全私有的S3桶。它只能通过CloudFront起源访问。如果请求是通过公共源提供的,那么就不需要身份验证,但是如果请求是通过私有源提供的,那么我将触发所谓的lambda edge来对它进行身份验证并验证承载令牌。
在深入研究所有这些之前,我对AWS stack是完全陌生的,但AWS非常容易,所以我最终以完美的方式配置了所有内容。如果有不清楚的地方或有任何问题,请让我知道。
我有短过期(5分钟)的访问令牌和刷新令牌。 如何立即撤销特定用户的访问令牌? 例如,如果我想禁止某个用户?我点击了禁止按钮,现在我想立即撤销他的访问令牌。我不想等待令牌到期。 我需要以某种方式创建令牌黑名单,但如果我不知道特定用户的访问令牌,如何将其添加到黑名单? 我应该将所有 jwt 令牌存储在数据库还是 redis 中?代码示例将非常有帮助。 非常感谢。
我有一个ADFS 4.0 OpenId连接设置,应用程序组为“Web浏览器访问Web应用程序”。我使用隐式流,能够从我的web应用程序成功登录,接收id_令牌并访问_令牌。 接下来,我在从wep应用程序发送到应用编程接口服务器的请求中使用访问令牌。我想做的是自定义访问令牌格式——添加额外的参数,因为默认情况下我只有: aud、iss、iat、exp、apptype、appid、Authmethod
经过大量阅读,我找到了一种实现自定义JWT承载令牌验证器的方法,如下所示。 : 自定义验证器类: 如果令牌被盗,我想添加一个额外的安全层来验证请求是否来自生成令牌的同一个客户端。 问题: 是否有任何方法可以访问类中的,以便根据当前客户端/请求者添加自定义验证 我们是否有其他方法可以使用这种方法/中间件验证请求者的真实性
这是自定义承载令牌授权机制的可接受实现吗? 授权属性 验证服务。APISettings类用于appSettings,但验证可以扩展为使用数据库。。。显然:) 实施 应用设置 } 请求头
我正在尝试让我的授权服务器生成一个JWT访问令牌,其中包含一些自定义声明。 我正在使用以下依赖项: 授权服务器是这样配置的: 它使用类: 与调试器一起运行时,不会调用这两个增强器重写。
我能够获得Graph API的有效访问令牌,因为有丰富的示例/文档/教程。 但是,我无法为我的自定义API获取有效的访问令牌。我使用的范围看起来像这样: 使用此作用域,我可以获取访问令牌。不幸的是,它是无效的。随后,当我尝试在自定义 API 上调用某些内容时,我收到未经授权的 401 错误。 甚至有可能使用MSAL acquireTokenSilent在自定义API上请求访问令牌吗?