You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用serverless-single-page-app-plugin部署Angular应用遇权限拒绝求助

问题描述

使用serverless-single-page-app-plugin将Angular应用部署到AWS CloudFront时,持续遭遇权限拒绝错误。当前使用的客户账号已关联管理员权限策略:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

但在个人AWS账号中使用完全相同的配置可正常部署,无权限问题。错误核心为CloudFront访问S3存储桶时触发AccessDenied,无法获取存储桶内对象。

附serverless.yaml配置:

service: serviceName

plugins:
  - serverless-single-page-app-plugin

custom:
  s3Bucket: s3Bucket
  s3LocalPath: app/
  region: ap-northeast-1

provider:
  name: aws
  iam:
    role:
      statements:
        - Effect: "Allow"
          Action:
            - s3:CreateBucket
            - s3:PutBucketAcl
            - s3:GetBucketAcl
            - s3:PutBucketPolicy
          Resource: arn:aws:s3::*
  runtime: nodejs14.x
  stage: dev
  region: ap-northeast-1
  profile: default
  stackName: StackName
  tags:
    owner: me
    project: serverless-learning
    env: dev
resources:
  Resources:
    cloudfrontoriginaccessidentity:
      Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
      Properties:
        CloudFrontOriginAccessIdentityConfig:
          Comment: "Test"
    ## Specifying the S3 Bucket
    WebAppS3Bucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.s3Bucket}
        AccessControl: PublicRead
        WebsiteConfiguration:
          IndexDocument: index.html
          ErrorDocument: index.html
    ## Specifying the CloudFront Distribution to server your Web Application
    WebAppCloudFrontDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Origins:
            - DomainName: ${self:custom.s3Bucket}.s3.amazonaws.com
              ## An identifier for the origin which must be unique within the distribution
              Id: WebApp
              # CustomOriginConfig:
              #   HTTPPort: 80
              #   HTTPSPort: 443
              #   OriginProtocolPolicy: https-only
              ## In case you want to restrict the bucket access use S3OriginConfig and remove CustomOriginConfig
              S3OriginConfig:
                OriginAccessIdentity:
                  Fn::Join:
                    - ''
                    -
                      - 'origin-access-identity/cloudfront/'
                      - Ref: cloudfrontoriginaccessidentity
          Enabled: 'true'
          ## Uncomment the following section in case you are using a custom domain
          # Aliases:
          # - mysite.example.com
          DefaultRootObject: index.html
          ## Since the Single Page App is taking care of the routing we need to make sure ever path is served with index.html
          ## The only exception are files that actually exist e.h. app.js, reset.css
          CustomErrorResponses:
            - ErrorCode: 404
              ResponseCode: 200
              ResponsePagePath: /index.html
          DefaultCacheBehavior:
            AllowedMethods:
              - DELETE
              - GET
              - HEAD
              - OPTIONS
              - PATCH
              - POST
              - PUT
            ## The origin id defined above
            TargetOriginId: WebApp
            ## Defining if and how the QueryString and Cookies are forwarded to the origin which in this case is S3
            ForwardedValues:
              QueryString: 'false'
              Cookies:
                Forward: none
            ## The protocol that users can use to access the files in the origin. To allow HTTP use `allow-all`
            ViewerProtocolPolicy: redirect-to-https
          ## The certificate to use when viewers use HTTPS to request objects.
          ViewerCertificate:
            CloudFrontDefaultCertificate: 'true'
          ## Uncomment the following section in case you want to enable logging for CloudFront requests
          # Logging:
          #   IncludeCookies: 'false'
          #   Bucket: mylogs.s3.amazonaws.com
          #   Prefix: myprefix
    ## Specifying the policies to make sure all files inside the Bucket are avaialble to CloudFront
    WebAppS3BucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket:
          Ref: WebAppS3Bucket
        PolicyDocument:
          Statement:
              Effect: "Allow"
              Principal: "*"
              Action:
                - s3:GetObject
              Resource: arn:aws:s3:::${self:custom.s3Bucket}/*



  # In order to print out the hosted domain via `serverless info` we need to define the DomainName output for CloudFormation
  Outputs:
    WebAppS3BucketOutput:
      Value:
        'Ref': WebAppS3Bucket
    WebAppCloudFrontDistributionOutput:
      Value:
        'Fn::GetAtt': [ WebAppCloudFrontDistribution, DomainName ]
解决方案

1. 修正S3存储桶策略的权限主体

当前配置同时启用了CloudFront Origin Access Identity(OAI)和公开可读策略,两者逻辑冲突。应仅允许OAI访问存储桶,而非公开所有人:

WebAppS3BucketPolicy:
  Type: AWS::S3::BucketPolicy
  Properties:
    Bucket:
      Ref: WebAppS3Bucket
    PolicyDocument:
      Statement:
          Effect: "Allow"
          Principal:
            AWS: !Sub "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${cloudfrontoriginaccessidentity}"
          Action:
            - s3:GetObject
          Resource: arn:aws:s3:::${self:custom.s3Bucket}/*

2. 移除存储桶的PublicRead访问控制

使用OAI后无需将存储桶设为公开,这会增加安全风险且干扰权限逻辑:

WebAppS3Bucket:
  Type: AWS::S3::Bucket
  Properties:
    BucketName: ${self:custom.s3Bucket}
    AccessControl: Private
    WebsiteConfiguration:
      IndexDocument: index.html
      ErrorDocument: index.html

3. 检查客户账号的权限边界或服务控制策略(SCP)

即使账号有管理员权限,客户账号可能被AWS组织的SCP或权限边界限制操作:

  • 确认账号所在AWS组织是否有SCP限制CloudFront/S3的关键操作
  • 检查账号是否被施加权限边界,禁止cloudfront:CreateCloudFrontOriginAccessIdentity、s3:PutBucketPolicy等操作

4. 修正CloudFront Origin的域名格式

使用S3静态网站托管时,CloudFront Origin需用静态网站域名(格式为bucket-name.s3-website-region.amazonaws.com),并切换为自定义源配置:

Origins:
  - DomainName: ${self:custom.s3Bucket}.s3-website-${self:provider.region}.amazonaws.com
    Id: WebApp
    CustomOriginConfig:
      HTTPPort: 80
      HTTPSPort: 443
      OriginProtocolPolicy: https-only

5. 清理资源后重新部署

若之前部署留下权限混乱的资源,按以下步骤操作:

  • 执行serverless remove删除现有栈
  • 手动清理残留的S3存储桶和CloudFront分发
  • 重新运行serverless deploy

内容的提问来源于stack exchange,提问作者LastOne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:20:19