使用 Angular 的 CORS 问题:8.2.14 和 .NET CORE 3.1 REST API 授权

Posted

技术标签:

【中文标题】使用 Angular 的 CORS 问题:8.2.14 和 .NET CORE 3.1 REST API 授权【英文标题】:CORS Problem using Angular: 8.2.14 and .NET CORE 3.1 REST API Authorization 【发布时间】:2020-09-10 13:12:59 【问题描述】:

我一直在开发一个简单的 Angular 项目,该项目连接到使用 .NET CORE 3.1 开发的 REST API

我遇到的问题是使用 Chrome 登录时出错。我尝试了许多不同的选项,并查看了许多 *** 问题和答案,但都没有奏效。我试图添加所有相关的代码和文件。如果还有其他需要,请告诉我。

这是我的 web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
  <httpProtocol>
      <customHeaders>
      <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
      <add name="Access-Control-Allow-Origin" value="*" />
      <add name="Access-Control-Allow-Headers" value="Content-Type" />
    </customHeaders>
  </httpProtocol>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="%LAUNCHER_PATH%" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" arguments="%LAUNCHER_ARGS%" hostingModel="InProcess">
        <environmentVariables>
          <environmentVariable name="FORTRESS_ENVIRONMENT" value="Development" />
          <environmentVariable name="FORTRESS_APPNAME" value="TEST APP" />
          <environmentVariable name="ASPNETCORE_HTTPS_PORT" value="3100" />
          <environmentVariable name="COMPLUS_ForceENC" value="1" />
        </environmentVariables>
      </aspNetCore>
      <security>
        <requestFiltering removeServerHeader="true" />
      </security>
    </system.webServer>
  </location>
</configuration>

这是我的 app.json 文件代码

    
      "AppName": "Contacts Service",
      "AllowedHosts": "*",
      "ConnectionString": "Server=FORTDSVM01;Initial Catalog=BankSCL;Integrated Security=true",
      "Kestrel": 
        "EndpointDefaults": 
          "Protocols": "Http1"
        
      ,
      "Serilog": 
        "MinimumLevel": 
          "Override": 
            "System": "Warning",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"

          
        ,
        "WriteTo:Async": 
          "Name": "Async",
          "Args": 
            "configure": [
              
                "Name": "File",
                "Args": 
                  "isJson": true,
                  "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact",
                  "path": "c:\\apps\\contacts-svc\\logs\\.log",
                  "rollingInterval": "Day",
                  "rollOnFileSizeLimit": true,
                  "fileSizeLimitBytes": "5000000",
                  "retainedFileCountLimit": null,
                  "shared": true
                
              
            ]
          
        ,
        "AppSettings": 
          "Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
        
      ,
      "Config": 
        "JWTKey": "12345678!@#$%^&*",
        "ValidIssuer": "https://localhost:3100/",
        "ValidAudience": "https://localhost:3100/",
        "SenderMailAddress": "test@gmail.com",
        "SenderMailPassword": "!123ABC!",
        "MailServerName": "smtp.gmail.com",
        "MailPortNumber": "465"
      
    

This is my Startup.cs

    using System;
    using System.Net.Http;
    using System.Security.Claims;
    using System.Text;
    using AutoMapper;
    using Fortress.Framework;
    using Fortress.Framework.DependencyInjection;
    using Fortress.Service.Api.Helpers;
    using Fortress.Service.Api.Mapper;
    using Fortress.Service.Api.Repository.Repository;
    using Fortress.Service.Api.Services;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.IdentityModel.Tokens;

    namespace Fortress.Service.Api
    
        public class Startup : ApiBaseStartup<Startup>
        
            static int Main(string[] args) => Run(args);
           // readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

            protected override void ConfigureApp(IApplicationBuilder app, IWebHostEnvironment env)
            

               // app.UseHttpsRedirection();

                app.UseRouting();

                app.UseCors("AllowAllHeaders");

                app.UseAuthorization();
                //app.UseAuthentication();

                app.UseEndpoints(endpoints =>
                
                    endpoints.MapControllers();
                );
            

            protected override void ConfigureAppServices(IServiceCollection services)
            
                services.AddCors(options => options.AddPolicy("AllowAllHeaders", build =>
                
                    build.WithOrigins("http://localhost:4200")
                         .AllowAnyMethod()
                         .AllowAnyHeader();
                ));

                services.AddAutoMapper(typeof(MapperProfile));
                services.AddSqlServer<ContactsContext>(Configuration);
                services.AddSqlServer<PoliciesContext>(Configuration);

                services.AddControllers();

                // configure strongly typed settings objects
                var appSettingsSection = Configuration.GetSection("AppSettings");
                services.Configure<AppSettings>(appSettingsSection);

                //// configure jwt authentication
                var appSettings = appSettingsSection.Get<AppSettings>();
                var key = Encoding.ASCII.GetBytes(Configuration.GetValue<string>("Config:JWTKey"));

                services.AddAuthentication(x =>
                
                    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                )
                .AddJwtBearer(x =>
                
                    x.RequireHttpsMetadata = false;
                    x.SaveToken = true;
                    x.TokenValidationParameters = new TokenValidationParameters
                    
                        ValidateIssuerSigningKey = true,
                        //Same Secret key will be used while creating the token
                        IssuerSigningKey = new SymmetricSecurityKey(key),
                        ValidateIssuer = true,
                        //Usually, this is your application base URL
                        ValidIssuer = Configuration.GetValue<string>("Config:ValidIssuer"),
                    ValidateAudience = true,
                        //Here, we are creating and using JWT within the same application.
                        //In this case, base URL is fine.
                        //If the JWT is created using a web service, then this would be the consumer URL.
                        ValidAudience = Configuration.GetValue<string>("Config:ValidAudience"),
                    RequireExpirationTime = true,
                        ValidateLifetime = true,
                        ClockSkew = TimeSpan.Zero
                    ;
                );

                // configure DI for application services
                services.AddScoped<IUserService, UserService>();


            
        
    

This is my launchSettings.json


  "iisSettings": 
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": 
      "applicationUrl": "http://localhost:3100", //64929
      "sslPort": 3100
    
  ,
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": 
    "IIS Express": 
      "commandName": "IISExpress",
      "launchUrl": "api/ping",
      "environmentVariables": 
        "FORTRESS_ENVIRONMENT": "Development"
      
    ,
    "Fortress.Service.Api": 
      "commandName": "Project",
      "launchUrl": "weatherforecast",
      "environmentVariables": 
        "FORTRESS_ENVIRONMENT": "Development"
      ,
      "applicationUrl": "https://localhost:3100;http://localhost:3000"
    ,
    "Docker": 
      "commandName": "Docker",
      "launchBrowser": true,
      "launchUrl": "Scheme://ServiceHost:ServicePort/api/ping",
      "environmentVariables": 
        "FORTRESS_URLS": "https://+:3100;http://+:80",
        "FORTRESS_HTTPS_PORT": "3100"
      ,
      "httpPort": 3000,
      "useSSL": true,
      "sslPort": 3100
    
  

这是我的用户控制器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Fortress.Framework;
using Fortress.Service.Api.Business.Contacts;
using Fortress.Service.Api.Models;
using Fortress.Service.Api.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
//using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
//using System.Web.Http.Cors;

namespace Fortress.Service.Api.Controllers

    //[Authorize(AuthenticationSchemes = "Bearer")]
    [AllowAnonymous]
    [FortressApi("Users")]
      public class UsersController : FortressController
    
        private IUserService _userService;

        public UsersController(IUserService userService)
        
            _userService = userService;
        

        // [AllowAnonymous]
        //[EnableCors("AllowAllHeaders")]
        [HttpPost("ValidateCredentials")]
        [Route("Login")]
        public async Task<UserCredentialOutput> ValidateCredentials(
            [FromServices] IGetCredentialService service,
            [FromBody] UserCredentialInput userCredentialInput, CancellationToken token)
        

            var response = await ExecuteServiceAsync(service, userCredentialInput, token);
            response.Password = userCredentialInput.Password;

            var user = _userService.Authenticate(response);
            return new UserCredentialOutput
            
                ContactID = user.ContactID,
                FirstName = user.FirstName,
                LastName = user.LastName,
                UserName = user.UserName,
                Roles = user.Roles,
                EmailID = user.EmailID,
                Token = user.Token
            ;
        
    

这是我调用 API 的 Angular 服务函数

validateCredentials(formData: FormData): Observable<UserCredentialOutput> 


        return this.api.post<UserCredentialOutput>('Users/Login', formData);


    

我完全忘了添加我的 Startup.cs 文件——在这里

using System;
using System.Net.Http;
using System.Security.Claims;
using System.Text;
using AutoMapper;
using Fortress.Framework;
using Fortress.Framework.DependencyInjection;
using Fortress.Service.Api.Helpers;
using Fortress.Service.Api.Mapper;
using Fortress.Service.Api.Repository.Repository;
using Fortress.Service.Api.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Mvc.Cors;

namespace Fortress.Service.Api

    public class Startup : ApiBaseStartup<Startup>
    
        static int Main(string[] args) => Run(args);
       // readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

        protected override void ConfigureApp(IApplicationBuilder app, IWebHostEnvironment env)
        


            //app.UseOptions();
            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseCors("AllowAllHeaders");
            //app.UseMvc();
            app.UseAuthorization();
            app.UseAuthentication();

            app.UseEndpoints(endpoints =>
            
                endpoints.MapControllers();
            );
        

        protected override void ConfigureAppServices(IServiceCollection services)
        
            services.AddCors(options => options.AddPolicy("AllowAllHeaders", build =>
            
                build.WithOrigins("http://localhost:4200")
                     .AllowAnyMethod()
                     .AllowAnyHeader();
            ));

            services.AddMvc();

            //services.Configure<MvcOptions>(options =>
            //
            //    options.Filters.Add(new CorsAuthorizationFilterFactory("AllowMyOrigin"));
            //);

            services.AddAutoMapper(typeof(MapperProfile));
            services.AddSqlServer<ContactsContext>(Configuration);
            services.AddSqlServer<PoliciesContext>(Configuration);

            services.AddControllers();

            // configure strongly typed settings objects
            var appSettingsSection = Configuration.GetSection("AppSettings");
            services.Configure<AppSettings>(appSettingsSection);

            //// configure jwt authentication
            var appSettings = appSettingsSection.Get<AppSettings>();
            var key = Encoding.ASCII.GetBytes(Configuration.GetValue<string>("Config:JWTKey"));

            services.AddAuthentication(x =>
            
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            )
            .AddJwtBearer(x =>
            
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                
                    ValidateIssuerSigningKey = true,
                    //Same Secret key will be used while creating the token
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = true,
                    //Usually, this is your application base URL
                    ValidIssuer = Configuration.GetValue<string>("Config:ValidIssuer"),
                ValidateAudience = true,
                    //Here, we are creating and using JWT within the same application.
                    //In this case, base URL is fine.
                    //If the JWT is created using a web service, then this would be the consumer URL.
                    ValidAudience = Configuration.GetValue<string>("Config:ValidAudience"),
                RequireExpirationTime = true,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.Zero
                ;
            );

            // configure DI for application services
            services.AddScoped<IUserService, UserService>();


        
    

【问题讨论】:

【参考方案1】:

只需将您对 web.config 所做的所有更改恢复原状,并将以下代码添加到 Startup.cs 中的 Configure 方法:

app.UseCors(builder =>
        
            builder
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader();
        );

【讨论】:

web api 需要通过代码设置 cors,而不是 web.config 我忘了添加我的 Startup.cs 文件。现在在上面。我已将 和 customHeaders> 从 web.config 文件中取出。同样的问题。它在邮递员中工作得很好。就在我从我的角度项目中推送时,它每次都会出错。 它与邮递员一起工作,因为邮递员没有设置源头,但浏览器确实在请求中设置了这个头。这不是角度的问题。你只需要在你的 .net 核心代码中修复它,它就可以正常工作了。 从 startup.cs 中的 ConfigureAppServices 方法中删除 services.AddCors... 块,并将同一文件中 ConfigureApp 方法中的 app.UseCors 方法更改为此答案中提供的格式 好的,我已经从 ConfigureAppServices // services.AddCors(); 中删除了以下内容并将以下内容添加到 ConfigureApp app.UseCors(builder => builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); );没有喜悦。 :(

以上是关于使用 Angular 的 CORS 问题:8.2.14 和 .NET CORE 3.1 REST API 授权的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 Spring Boot 和 Angular 修复 CORS 问题 [重复]

使用 Angular 的 Stripe API 的 CORS 问题

使用 Angular 向 facebook oauth 发出 CORS 问题

使用 Django 和 Angular 的 CORS 预检请求

如何使用 Angular 消除 Spring Boot 中的 CORS 错误? [复制]

Angular CORS 请求被阻止