我正在尝试创建一个身份验证服务,其中我有一个 lambda,它使用 keycloak 通过密码授予来对用户进行身份验证。该服务由 s3 上的 React 前端主机组成,并通过 cloudfront 访问。前端应接收重定向 uri,并将其传递给 auth lambda 以对用户进行身份验证,并使用 301 重定向将其重定向到重定向 uri。我遇到的问题是当我发送 301 重定向时出现 cors 错误
Access to XMLHttpRequest at 'https://jwt.io/' (redirected from 'https://z0kj9kfzwk.execute-api.us-west-2.amazonaws.com/dev/login') from origin 'https://auth-website-harmless-wren.s3.us-west-2.amazonaws.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
这是我的地形设置:
API网关
resource "aws_apigatewayv2_api" "auth_api_gw" {
name = "auth-api"
protocol_type = "HTTP"
cors_configuration {
allow_headers = ["*"]
allow_methods = ["POST", "OPTIONS"]
allow_origins = ["*"]
max_age = 3000
}
}
resource "aws_cloudwatch_log_group" "auth_api_gw_log_group" {
name = "/aws/api-gw/${aws_apigatewayv2_api.auth_api_gw.name}"
retention_in_days = 30
}
resource "aws_apigatewayv2_stage" "auth_api_gw_dev_stage" {
api_id = aws_apigatewayv2_api.auth_api_gw.id
name = "dev"
auto_deploy = true
access_log_settings {
destination_arn = aws_cloudwatch_log_group.auth_api_gw_log_group.arn
format = jsonencode({
requestId = "$context.requestId"
sourceIp = "$context.identity.sourceIp"
requestTime = "$context.requestTime"
protocol = "$context.protocol"
httpMethod = "$context.httpMethod"
resourcePath = "$context.resourcePath"
routeKey = "$context.routeKey"
status = "$context.status"
responseLength = "$context.responseLength"
integrationErrorMessage = "$context.integrationErrorMessage"
}
)
}
}
resource "aws_apigatewayv2_integration" "auth_api_gw_handler" {
api_id = aws_apigatewayv2_api.auth_api_gw.id
integration_type = "AWS_PROXY"
integration_uri = var.auth_lambda_invoke_arn
}
resource "aws_apigatewayv2_route" "auth_login_route" {
api_id = aws_apigatewayv2_api.auth_api_gw.id
route_key = "POST /login"
target = "integrations/${aws_apigatewayv2_integration.auth_api_gw_handler.id}"
}
resource "aws_apigatewayv2_route" "auth_register_route" {
api_id = aws_apigatewayv2_api.auth_api_gw.id
route_key = "POST /register"
target = "integrations/${aws_apigatewayv2_integration.auth_api_gw_handler.id}"
}
resource "aws_lambda_permission" "auth_api_gw_lambda_permission" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = var.auth_lambda_function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.auth_api_gw.execution_arn}/*/*"
}
拉姆达
locals {
timestamp_suffix = timestamp()
}
resource "null_resource" "auth_lambda_package_build" {
triggers = {
always_run = local.timestamp_suffix
}
provisioner "local-exec" {
command = <<EOT
SOURCE_DIR="${path.root}"
BACKEND_SOURCE_DIR="${path.module}/../../../../packages/backend"
cd $SOURCE_DIR
pnpm --filter @auth/backend install
pnpm --filter @auth/backend run build
cp "$BACKEND_SOURCE_DIR/package.json" "$BACKEND_SOURCE_DIR/build"
cp "$BACKEND_SOURCE_DIR/package-lock.json" "$BACKEND_SOURCE_DIR/build"
cd "$BACKEND_SOURCE_DIR/build"
npm ci --production
EOT
}
}
data "archive_file" "archive_auth_lambda" {
type = "zip"
source_dir = "${path.module}/../../../../packages/backend/build"
output_path = "${path.module}/../../../../packages/backend/auth-lambda_${local.timestamp_suffix}.zip"
depends_on = [ null_resource.auth_lambda_package_build ]
}
resource "random_pet" "lambda_bucket_name" {
prefix = "lambda"
length = 2
}
resource "aws_s3_bucket" "lambda_bucket" {
bucket = random_pet.lambda_bucket_name.id
force_destroy = true
}
resource "aws_s3_bucket_public_access_block" "lambda_bucket" {
bucket = aws_s3_bucket.lambda_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_object" "auth_lambda_code_s3_object" {
bucket = aws_s3_bucket.lambda_bucket.id
key = "auth-lambda.zip"
source = data.archive_file.archive_auth_lambda.output_path
etag = filemd5(data.archive_file.archive_auth_lambda.output_path)
}
resource "aws_cloudwatch_log_group" "lambda_logs" {
name = "/aws/lambda/${aws_lambda_function.auth_lambda_function.function_name}"
retention_in_days = 30
}
resource "aws_iam_role" "auth_lambda_exec" {
name = "auth_lambda_exec_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "auth_lambda_policy" {
role = aws_iam_role.auth_lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "auth_lambda_function" {
function_name = "auth"
s3_bucket = aws_s3_bucket.lambda_bucket.id
s3_key = aws_s3_object.auth_lambda_code_s3_object.key
source_code_hash = data.archive_file.archive_auth_lambda.output_base64sha256
runtime = "nodejs20.x"
handler = "handler.handler"
memory_size = 1024
timeout = 60
role = aws_iam_role.auth_lambda_exec.arn
environment {
variables = {
keycloak_auth_server_url = var.keycloak_auth_server_url
keycloak_realm = var.keycloak_realm
keycloak_client_id = var.keycloak_client_id
keycloak_client_secret = var.keycloak_client_secret
keycloak_admin_client_id = var.keycloak_admin_client_id
keycloak_admin_client_secret = var.keycloak_admin_client_secret
}
}
}
云前线
resource "aws_cloudfront_origin_access_identity" "auth_oai" {
comment = "auth-website OAI"
}
resource "aws_cloudfront_distribution" "auth_distribution" {
origin {
domain_name = var.auth_bucket_regional_domain_name
origin_id = "auth-origin"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.auth_oai.cloudfront_access_identity_path
}
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "auth-origin"
default_ttl = 3600
min_ttl = 0
max_ttl = 86400
compress = true
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
}
ordered_cache_behavior {
path_pattern = "./index.html"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "auth-origin"
default_ttl = 3600
min_ttl = 0
max_ttl = 86400
compress = true
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
}
}
S3桶
resource "random_pet" "auth_website_bucket_name" {
prefix = "auth-website"
length = 2
}
resource "aws_s3_bucket" "auth_website_bucket" {
bucket = random_pet.auth_website_bucket_name.id
force_destroy = true
}
resource "aws_s3_bucket_website_configuration" "auth_website_code_s3_configuration" {
bucket = aws_s3_bucket.auth_website_bucket.id
index_document {
suffix = "index.html"
}
error_document {
key = "index.html"
}
}
resource "aws_s3_bucket_policy" "auth_website_bucket_policy" {
depends_on = [aws_s3_bucket_acl.auth_website_bucket_acl]
bucket = aws_s3_bucket.auth_website_bucket.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject",
Resource = "${aws_s3_bucket.auth_website_bucket.arn}/*",
}]
})
}
resource "aws_s3_bucket_public_access_block" "auth_website_bucket_public_access_block" {
bucket = aws_s3_bucket.auth_website_bucket.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
resource "aws_s3_bucket_ownership_controls" "auth_website_ownership_controls" {
bucket = aws_s3_bucket.auth_website_bucket.id
rule {
object_ownership = "BucketOwnerPreferred"
}
}
resource "aws_s3_bucket_acl" "auth_website_bucket_acl" {
depends_on = [aws_s3_bucket_ownership_controls.auth_website_ownership_controls, aws_s3_bucket_public_access_block.auth_website_bucket_public_access_block]
bucket = aws_s3_bucket.auth_website_bucket.id
acl = "public-read"
}
locals {
timestamp_suffix = timestamp()
}
resource "null_resource" "auth_website_package_build" {
depends_on = [ aws_s3_bucket.auth_website_bucket ]
triggers = {
always_run = local.timestamp_suffix
}
provisioner "local-exec" {
command = <<EOT
ROOT_DIR="../"
FRONTEND_DIR="$ROOT_DIR/packages/frontend"
(cd $FRONTEND_DIR && echo "VITE_AUTH_API_ENDPOINT=${var.auth_lambda_url}" >> .env.production )
(cd $ROOT_DIR && pnpm --filter @auth/frontend install && pnpm --filter @auth/frontend run build)
aws s3 sync "$FRONTEND_DIR/dist" s3://${aws_s3_bucket.auth_website_bucket.bucket} --delete --exact-timestamps
EOT
}
}
这是我的node.js lambda处理程序,现在我没有进行重定向,而是尝试将成功登录后的请求转发到jwt.io
import {
Handler,
Context,
APIGatewayProxyEventV2,
APIGatewayProxyResultV2,
} from "aws-lambda";
import dotenv from "dotenv";
import { getAccessTokenFromUserCredentials } from "./utils/keycloak.js";
dotenv.config();
export const handler: Handler = async (
event: APIGatewayProxyEventV2,
context: Context
): Promise<APIGatewayProxyResultV2 | void> => {
try {
const { username, password } = JSON.parse(event.body ?? "");
console.log(username, password);
const tokens = await getAccessTokenFromUserCredentials(username, password);
// const response = {
// statusCode: 200,
// body: JSON.stringify({ tokens }),
// headers: {
// "Access-Control-Allow-Origin": "*",
// "Access-Control-Allow-Methods": "OPTIONS,POST,GET",
// "Access-Control-Allow-Headers": "Content-Type",
// "Access-Control-Allow-Credentials": true,
// },
// };
const response = {
statusCode: 301,
statusDescription: "Temporary Redirect",
headers: {
location: "https://jwt.io",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Credentials": true,
},
cookies: [],
};
return response;
} catch (err: any) {
console.log(err);
return {
statusCode: 401,
body: err.message,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Credentials": true,
},
};
}
};
我真的很感谢一些帮助,我只是想了解有关 aws 和 terraform 的更多信息。我不想使用边缘 lambda,但想使用普通 lambda。
经过大量研究,我发现尝试从 AJAX 调用进行重定向是一个很大的问题,这就是导致问题的原因。我已恢复返回重定向网址,然后从前端重定向到该网址。