Итак, я пытаюсь разместить полнофункциональное веб-приложение в AWS. У меня есть внешний интерфейс angular, который находится в корзине s3 за облачным фронтом и использует домен от route53. На бэкэнде у меня есть экспресс-проект typescript, который находится за API-шлюзом. Я использую auth0 для аутентификации и примеры приложений для внешнего/внутреннего интерфейса, которые они предоставляют на своих веб-сайтах. Я свяжу их здесь: https://github.com/auth0-developer-hub/spa_angular_typescript_hello-worldhttps://github.com/auth0-developer-hub/api_express_typescript_hello-world
Когда я делаю запросы, не затронутые перехватчиком, они будут работать, но когда я пытаюсь выполнить запросы, которые изменяются перехватчиком (когда я вхожу в систему с использованием auth0), я получаю следующую ошибку: «Доступ к XMLHttpRequest по адресу https://api-gateway.com/dev/api/messages/protected» из источника «https://website.link» заблокирован политикой CORS: ответ на предварительный запрос не проходит проверка управления доступом: в запрошенном ресурсе отсутствует заголовок «Access-Control-Allow-Origin».
Код перехватчика в модуле auth.module ниже:
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AuthModule, AuthHttpInterceptor } from '@auth0/auth0-angular';
import { environment as env } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SharedModule } from './shared';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
AppRoutingModule,
SharedModule,
HttpClientModule,
AuthModule.forRoot({
...env.auth0,
httpInterceptor: {
allowedList: [`${env.api.serverUrl}/api/messages/admin`, `${env.api.serverUrl}/api/messages/protected`],
//allowedList: [`${env.api.serverUrl}/api/messages/admin`],
},
}),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthHttpInterceptor,
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
Ниже приведен мой index.ts из экспресс-бэкэнда, где я явно разрешаю заголовок «Авторизация», который прикрепляет перехватчик. Я подтвердил, что передаваемые токены также действительны.
import cors from "cors";
import * as dotenv from "dotenv";
import * as awsServerlessExpress from 'aws-serverless-express';
import express from "express";
import helmet from "helmet";
import nocache from "nocache";
import { messagesRouter } from "./messages/messages.router";
import { errorHandler } from "./middleware/error.middleware";
import { notFoundHandler } from "./middleware/not-found.middleware";
dotenv.config();
if (!(process.env.PORT && process.env.CLIENT_ORIGIN_URL)) {
throw new Error(
"Missing required environment variables. Check docs for more info."
);
}
const PORT = parseInt(process.env.PORT, 10);
const CLIENT_ORIGIN_URL = process.env.CLIENT_ORIGIN_URL;
const app = express();
const apiRouter = express.Router();
app.use(express.json());
app.set("json spaces", 2);
app.use(
helmet({
hsts: {
maxAge: 31536000,
},
contentSecurityPolicy: {
useDefaults: false,
directives: {
"default-src": ["'none'"],
"frame-ancestors": ["'none'"],
},
},
frameguard: {
action: "deny",
},
})
);
app.use((req, res, next) => {
res.contentType("application/json; charset=utf-8");
next();
});
app.use(nocache());
app.use(
cors({
origin: CLIENT_ORIGIN_URL,
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Authorization", "Content-Type"],
maxAge: 86400,
})
);
app.use("/api", apiRouter);
apiRouter.use("/messages", messagesRouter);
app.use(errorHandler);
app.use(notFoundHandler);
// create serverless express
const server = awsServerlessExpress.createServer(app);
// export the handler function for AWS Lambda
export const handler = (event: any, context: any) => awsServerlessExpress.proxy(server, event, context);
Я пробовал различные вещи, такие как установка заголовка «Access-Control-AllowOrigin» на маршрутах, и он все еще не работал, например, ниже:
messagesRouter.get("/protected", validateAccessToken, (req, res) => {
res.set("Access-Control-Allow-Origin", CLIENT_ORIGIN_URL);
try {
logger.info(JSON.stringify(req.auth));
logger.info(`Token: ${JSON.stringify(req.auth?.token)}`)
//console.info(req.auth?.token)
const message = getProtectedMessage();
res.status(200).json(message);
}
catch (err){
console.info(err);
res.status(500).json('Error')
}
});
Я также пробовал различные конфигурации CORS в index.ts, такие как ниже:
app.use(cors());
app.use(cors({
origin: CLIENT_ORIGIN_URL,
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Authorization", "Content-Type"],
credentials: true,
}));
Это привело к той же ошибке
Обновлено: включая мою конфигурацию облачного фронта из terraform ниже:
resource "aws_cloudfront_distribution" "website_distribution" {
origin {
domain_name = aws_s3_bucket.frontend_bucket.bucket_regional_domain_name
origin_id = aws_s3_bucket.frontend_bucket.id
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
}
}
origin {
domain_name = replace(aws_api_gateway_deployment.example.invoke_url, "/^https?://([^/]*).*/", "$1")
origin_id = aws_api_gateway_deployment.example.id
origin_path = "/${terraform.workspace}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS", "POST", "DELETE", "PUT", "PATCH"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = aws_s3_bucket.frontend_bucket.id
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
ordered_cache_behavior {
path_pattern = "/api/*"
target_origin_id = aws_api_gateway_deployment.example.id
allowed_methods = ["GET", "HEAD", "OPTIONS", "POST", "DELETE", "PUT", "PATCH"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
forwarded_values {
query_string = true
cookies {
forward = "none"
}
headers = ["Authorization"]
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "none"
locations = []
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.cert.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2018"
cloudfront_default_certificate = false
}
custom_error_response {
error_code = 403
response_page_path = "/index.html"
response_code = "200"
error_caching_min_ttl = 300
}
enabled = true
is_ipv6_enabled = true
http_version = "http2"
price_class = "PriceClass_100"
default_root_object = "index.html"
aliases = [var.domain_name[terraform.workspace], "www.${var.domain_name[terraform.workspace]}"]
}
Да, это работает, когда я запускаю интерфейс/бэкенд локально. Также работает, когда я беру токен из инструментов разработчика и вызываю его из почтальона. Только не с доменом.
Извините, хотел спросить, работает ли это с локальным интерфейсом + бэкэндом на AWS. Существует множество конфигураций, в основном облачных, которые могут решить вашу проблему. Настроили ли вы облачные источники, которые работают так же, как Angular proxy.conf.json
?
Все хорошо. Я только что добавил свою конфигурацию облачного терраформа к своему сообщению, если это поможет. Это работает, когда я просто вызываю его из шлюза API. Я предполагаю, что вы спрашиваете, что произойдет, если я просто использую адрес корзины s3 напрямую? Когда я попробовал это, он действительно начал жаловаться на то, что хочет зайти с безопасного хоста или что-то в этом роде. Я могу попробовать еще раз, но мне придется изменить некоторые роли и настройки удостоверения доступа к источнику.
Спасибо за обновление с конфигурацией Terraform. Чтобы получить доступ к шлюзу API из Cloudfront, что происходит, когда вы выдаете https://cloudfront-host/api/endpoint
, вам необходимо определить другой источник, который является шлюзом API. Внутри нового определения источника вы должны использовать custom_origin_config вместо s3_origin_config
. Также читайте о ordered_cache_behavior
, возможно, вам придется его настроить. Вот почему вы можете добраться до него напрямую через почтальона!
Чтобы получить доступ к шлюзу API из Cloudfront, что происходит, когда вы выдаете https://cloudfront-host/api/endpoint
, вам необходимо определить другой источник, отличный от S3, который является шлюзом API.
Причина, по которой вы можете получить доступ напрямую через Postman или Api Gateway, заключается в том, что именно Cloudfront блокирует запрос. Вам также потребуется настроить ordered_cache_behavior
, чтобы Cloudfront мог передавать запросы через прокси в ваш собственный источник. Это очень похоже на proxy.conf.json
в Angular, где, если все ваши конечные точки API следуют шаблону /api/<endpoint>
, то ваше упорядоченное поведение кеша будет направлять запросы на /api/*
на шлюз API.
Внутри нового определения источника вы должны использовать custom_origin_config вместо s3_origin_config
. Также читайте о ordered_cache_behavior
, возможно, вам придется его настроить.
resource "aws_cloudfront_distribution" "website_distribution" {
origin {
domain_name = aws_s3_bucket.frontend_bucket.bucket_regional_domain_name
origin_id = aws_s3_bucket.frontend_bucket.id
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
}
}
origin {
domain_name = <>
origin_id = <some_id>
custom_origin_config {
...
}
}
ordered_cache_behavior {
path_pattern = "/api/*"
...
target_origin_id = <some_id>
...
}
...
Ненавижу это говорить, но я все еще сталкиваюсь с той же ошибкой: /. Я включил свою новую конфигурацию terraform, которая включает исходную конфигурацию шлюза API. Не уверен, что там что-то не так или нет.
Я исправил эту проблему, включив CORS для своего ресурса в API Gateway. Я также обязательно указал, что заголовок «Авторизация» разрешен.
Это работает локально, но не в AWS? А вы пробовали
allowedList: ['/api/messages/admin', '/api/messages/protected'],
? Без хостинга сайта. Если URL-адрес конечной точки на самом деле похож на/api/messages/proctected?<something>
или/api/messages/proctected/<something>
, вам следует использовать подстановочный знак./api/messages/*
разрешит как сообщения/защищенные, так и сообщения/администратора, но может быть не идеальным