如何在 .NetCore API 的不同应用程序上验证从一个应用程序颁发的 AntiForgeryToken?
How to validate AntiForgeryToken issued from one Application on different Application in .NetCore API?
我有两个应用程序,ASP.NET Core Web APP ( app 1 ) 和 .NET CORE API ( app 2 )。我在 ( app 1 ) 中发布了一个 AntiForgeryToken 并且 return 它以隐藏字段的形式出现(实际上 .netcore 自动生成它)。现在,当用户提交表单时,数据将转到 (app 2) 并使用 CORS,我能够将 (app 1) 颁发的 AntiForgeryToken cookie 发送到 (app 2),这样我就可以验证它并在 (app 2) 我添加了 validateAntiFOrgeryToken 属性,以便我可以对其进行验证。下面是我在 chrome 开发工具响应选项卡上收到的错误。
我尝试了所有方法并搜索了所有 SO 但这个特定错误找不到答案。
System.InvalidOperationException: No service for type 'Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.AspNetCore.Mvc.ValidateAntiForgeryTokenAttribute.CreateInstance(IServiceProvider serviceProvider)
at Microsoft.AspNetCore.Mvc.Filters.DefaultFilterProvider.ProvideFilter(FilterProviderContext context, FilterItem filterItem)
at Microsoft.AspNetCore.Mvc.Filters.DefaultFilterProvider.OnProvidersExecuting(FilterProviderContext context)
at Microsoft.AspNetCore.Mvc.Filters.FilterFactory.CreateUncachedFiltersCore(IFilterProvider[] filterProviders, ActionContext actionContext, List`1 filterItems)
at Microsoft.AspNetCore.Mvc.Filters.FilterFactory.GetAllFilters(IFilterProvider[] filterProviders, ActionContext actionContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvokerCache.GetCachedResult(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvokerProvider.OnProvidersExecuting(ActionInvokerProviderContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionInvokerFactory.CreateInvoker(ActionContext actionContext)
at Microsoft.AspNetCore.Mvc.Routing.ActionEndpointFactory.<>c__DisplayClass7_0.<CreateRequestDelegate>b__0(HttpContext context)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: application/json, text/javascript, */*; q=0.01
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: close
Content-Length: 98
Content-Type: application/json
Cookie: .AspNetCore.Antiforgery.P09gDl3q4JU=CfDJ8MTlw3i2dFxEnHbgYLq-NTBvWTMlXSM5JV9sH03i3b4ulUq0JlSns86jxwas797wsxz9mOS2JDlK6nhntJGc80bpNNwUyUnOQou-iTEwsykSBE7-yfc05pjknlLMNciWmrLzxHaJ-kpG8Tjnqo7jxbc
Host: localhost:44375
Pragma: no-cache
Referer: https://localhost:44389/account/signup
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
origin: https://localhost:44389
x-xsrf-token: CfDJ8MTlw3i2dFxEnHbgYLq-NTB8DUVF73lPTj3HAOL2bhD-sIDv2N4Fs0JowHt6W-WB5oQltt8ELtCH03XYq2QAxz96SYseDR-pZ6XMXAXRE8mpGUatGKtWp_yNohpbYt2ZQD25NzYqYdw-fCi3hsdQf1g
sec-fetch-site: same-site
sec-fetch-mode: cors
这是应用 1 的代码:
(查看)
<div class="form-container">
<div class="form-inputs">
<form id="signup-form" method="post">
<!-- Input and Submit elements -->
@Html.AntiForgeryToken()
<input type="email" name="email" data-send="true" placeholder="Email Address" />
<span class="error"></span>
<input type="password" name="password" data-send="true" placeholder="Password" data-validate="true" data-val-min="8" data-val-max="20" data-val-length-error="Password must be between 8 and 20 characters" data-val-empty-error="Password is required" />
<span class="error"></span>
<input type="password" name="confirmpassword" data-send="true" data-validate="true" data-val-compare="password" data-val-empty-error="Confirm Password is required" data-val-compare-error="Password doesn't match" placeholder="Confirm Password" />
<span class="error"></span>
<input type="text" name="username" placeholder="Username" data-send="true" data-validate="true" data-val-min="3" data-val-max="10" data-val-length-error="Username must be between 3 and 10 characters" data-val-empty-error="Username is required" />
<span class="error"></span>
<div class="button-holder">
<button id="signup" class="action"><span id="loader"><i class="fas fa-spinner"></i></span><span id="button-text">signup</span></button>
</div>
<div class="terms-holder">
<p>By Creating an account you agree to our <span><a href="#">Terms of Use</a></span> and <span><a href="#">Privacy Policy</a></span></p>
</div>
</form>
</div>
</div>
这里是应用程序 2 的代码:
(startup.cs)
using System.Text;
using Authentication.Models;
using Authentication.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
namespace API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
readonly string OnlyOrigin = "OnlyOrigin";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder => builder.WithOrigins("https://localhost:44389")
.AllowAnyMethod()
.AllowCredentials()
.WithHeaders("content-type","X-XSRF-TOKEN")
);
});
services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.HeaderName = "X-XSRF-TOKEN";
options.SuppressXFrameOptionsHeader = false;
});
// 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(appSettings.Secret);
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,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddControllers();
services.AddScoped<IAccount, AccountManager>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
(控制器):
using API.Models;
using Authentication.Models;
using Authentication.Services;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
[Route("api/authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly IAccount accountService;
private readonly HttpContext Context;
public AuthenticationController(IAccount accountService, IHttpContextAccessor contextAccessor)
{
this.accountService = accountService;
this.Context = contextAccessor.HttpContext;
}
[HttpPost]
[Consumes("application/json")]
[Produces("application/json")]
[Route("account/signup")]
//[EnableCors("Only Origin")]
[ValidateAntiForgeryToken]
public string Signup([FromBody] Account account)
{
if(!ModelState.IsValid)
{
return "not valid";
}
return accountService.CreateAccount(account);
}
[HttpPost]
[Consumes("application/json")]
[Produces("application/json")]
[Route("account/signin")]
[ValidateAntiForgeryToken]
public IActionResult Signin(LoginViewModel loginViewModel)
{
if (!ModelState.IsValid)
{
return BadRequest(new Result
{
Type = "Error",
Return = "not valid"
});
}
Result result = accountService.Login(loginViewModel.Email, loginViewModel.Password);
return Ok(result);
}
}
}
(JQuery):
this.send = function () {
alert(csrf);
$.ajax({
url: "https://localhost:44375/api/authentication/account/signup",
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': csrf
},
xhrFields: {
withCredentials: true
},
method: 'POST',
dataType: 'json',
data: getDataToSend(),
success: function (data) {
console.log('succes: ' + data);
}
});
}
(请求Headers):
:authority: localhost:44375
:method: POST
:path: /api/authentication/account/signup
:scheme: https
accept: application/json, text/javascript, */*; q=0.01
accept-encoding: gzip, deflate, br
accept-language: en-GB,en-US;q=0.9,en;q=0.8
cache-control: no-cache
content-length: 98
content-type: application/json
cookie: .AspNetCore.Antiforgery.P09gDl3q4JU=CfDJ8MTlw3i2dFxEnHbgYLq-NTBvWTMlXSM5JV9sH03i3b4ulUq0JlSns86jxwas797wsxz9mOS2JDlK6nhntJGc80bpNNwUyUnOQou-iTEwsykSBE7-yfc05pjknlLMNciWmrLzxHaJ-kpG8Tjnqo7jxbc
origin: https://localhost:44389
pragma: no-cache
referer: https://localhost:44389/account/signup
sec-fetch-mode: cors
sec-fetch-site: same-site
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
x-xsrf-token: CfDJ8MTlw3i2dFxEnHbgYLq-NTB8DUVF73lPTj3HAOL2bhD-sIDv2N4Fs0JowHt6W-WB5oQltt8ELtCH03XYq2QAxz96SYseDR-pZ6XMXAXRE8mpGUatGKtWp_yNohpbYt2ZQD25NzYqYdw-fCi3hsdQf1g
如果您不希望发生该异常,则必须使用
services.AddControllersWithViews()
而不是
services.AddControllers()
我有两个应用程序,ASP.NET Core Web APP ( app 1 ) 和 .NET CORE API ( app 2 )。我在 ( app 1 ) 中发布了一个 AntiForgeryToken 并且 return 它以隐藏字段的形式出现(实际上 .netcore 自动生成它)。现在,当用户提交表单时,数据将转到 (app 2) 并使用 CORS,我能够将 (app 1) 颁发的 AntiForgeryToken cookie 发送到 (app 2),这样我就可以验证它并在 (app 2) 我添加了 validateAntiFOrgeryToken 属性,以便我可以对其进行验证。下面是我在 chrome 开发工具响应选项卡上收到的错误。
我尝试了所有方法并搜索了所有 SO 但这个特定错误找不到答案。
System.InvalidOperationException: No service for type 'Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.AspNetCore.Mvc.ValidateAntiForgeryTokenAttribute.CreateInstance(IServiceProvider serviceProvider)
at Microsoft.AspNetCore.Mvc.Filters.DefaultFilterProvider.ProvideFilter(FilterProviderContext context, FilterItem filterItem)
at Microsoft.AspNetCore.Mvc.Filters.DefaultFilterProvider.OnProvidersExecuting(FilterProviderContext context)
at Microsoft.AspNetCore.Mvc.Filters.FilterFactory.CreateUncachedFiltersCore(IFilterProvider[] filterProviders, ActionContext actionContext, List`1 filterItems)
at Microsoft.AspNetCore.Mvc.Filters.FilterFactory.GetAllFilters(IFilterProvider[] filterProviders, ActionContext actionContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvokerCache.GetCachedResult(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvokerProvider.OnProvidersExecuting(ActionInvokerProviderContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionInvokerFactory.CreateInvoker(ActionContext actionContext)
at Microsoft.AspNetCore.Mvc.Routing.ActionEndpointFactory.<>c__DisplayClass7_0.<CreateRequestDelegate>b__0(HttpContext context)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: application/json, text/javascript, */*; q=0.01
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: close
Content-Length: 98
Content-Type: application/json
Cookie: .AspNetCore.Antiforgery.P09gDl3q4JU=CfDJ8MTlw3i2dFxEnHbgYLq-NTBvWTMlXSM5JV9sH03i3b4ulUq0JlSns86jxwas797wsxz9mOS2JDlK6nhntJGc80bpNNwUyUnOQou-iTEwsykSBE7-yfc05pjknlLMNciWmrLzxHaJ-kpG8Tjnqo7jxbc
Host: localhost:44375
Pragma: no-cache
Referer: https://localhost:44389/account/signup
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
origin: https://localhost:44389
x-xsrf-token: CfDJ8MTlw3i2dFxEnHbgYLq-NTB8DUVF73lPTj3HAOL2bhD-sIDv2N4Fs0JowHt6W-WB5oQltt8ELtCH03XYq2QAxz96SYseDR-pZ6XMXAXRE8mpGUatGKtWp_yNohpbYt2ZQD25NzYqYdw-fCi3hsdQf1g
sec-fetch-site: same-site
sec-fetch-mode: cors
这是应用 1 的代码:
(查看)
<div class="form-container">
<div class="form-inputs">
<form id="signup-form" method="post">
<!-- Input and Submit elements -->
@Html.AntiForgeryToken()
<input type="email" name="email" data-send="true" placeholder="Email Address" />
<span class="error"></span>
<input type="password" name="password" data-send="true" placeholder="Password" data-validate="true" data-val-min="8" data-val-max="20" data-val-length-error="Password must be between 8 and 20 characters" data-val-empty-error="Password is required" />
<span class="error"></span>
<input type="password" name="confirmpassword" data-send="true" data-validate="true" data-val-compare="password" data-val-empty-error="Confirm Password is required" data-val-compare-error="Password doesn't match" placeholder="Confirm Password" />
<span class="error"></span>
<input type="text" name="username" placeholder="Username" data-send="true" data-validate="true" data-val-min="3" data-val-max="10" data-val-length-error="Username must be between 3 and 10 characters" data-val-empty-error="Username is required" />
<span class="error"></span>
<div class="button-holder">
<button id="signup" class="action"><span id="loader"><i class="fas fa-spinner"></i></span><span id="button-text">signup</span></button>
</div>
<div class="terms-holder">
<p>By Creating an account you agree to our <span><a href="#">Terms of Use</a></span> and <span><a href="#">Privacy Policy</a></span></p>
</div>
</form>
</div>
</div>
这里是应用程序 2 的代码:
(startup.cs)
using System.Text;
using Authentication.Models;
using Authentication.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
namespace API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
readonly string OnlyOrigin = "OnlyOrigin";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder => builder.WithOrigins("https://localhost:44389")
.AllowAnyMethod()
.AllowCredentials()
.WithHeaders("content-type","X-XSRF-TOKEN")
);
});
services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties†.
options.HeaderName = "X-XSRF-TOKEN";
options.SuppressXFrameOptionsHeader = false;
});
// 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(appSettings.Secret);
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,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddControllers();
services.AddScoped<IAccount, AccountManager>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
(控制器):
using API.Models;
using Authentication.Models;
using Authentication.Services;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
[Route("api/authentication")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly IAccount accountService;
private readonly HttpContext Context;
public AuthenticationController(IAccount accountService, IHttpContextAccessor contextAccessor)
{
this.accountService = accountService;
this.Context = contextAccessor.HttpContext;
}
[HttpPost]
[Consumes("application/json")]
[Produces("application/json")]
[Route("account/signup")]
//[EnableCors("Only Origin")]
[ValidateAntiForgeryToken]
public string Signup([FromBody] Account account)
{
if(!ModelState.IsValid)
{
return "not valid";
}
return accountService.CreateAccount(account);
}
[HttpPost]
[Consumes("application/json")]
[Produces("application/json")]
[Route("account/signin")]
[ValidateAntiForgeryToken]
public IActionResult Signin(LoginViewModel loginViewModel)
{
if (!ModelState.IsValid)
{
return BadRequest(new Result
{
Type = "Error",
Return = "not valid"
});
}
Result result = accountService.Login(loginViewModel.Email, loginViewModel.Password);
return Ok(result);
}
}
}
(JQuery):
this.send = function () {
alert(csrf);
$.ajax({
url: "https://localhost:44375/api/authentication/account/signup",
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': csrf
},
xhrFields: {
withCredentials: true
},
method: 'POST',
dataType: 'json',
data: getDataToSend(),
success: function (data) {
console.log('succes: ' + data);
}
});
}
(请求Headers):
:authority: localhost:44375
:method: POST
:path: /api/authentication/account/signup
:scheme: https
accept: application/json, text/javascript, */*; q=0.01
accept-encoding: gzip, deflate, br
accept-language: en-GB,en-US;q=0.9,en;q=0.8
cache-control: no-cache
content-length: 98
content-type: application/json
cookie: .AspNetCore.Antiforgery.P09gDl3q4JU=CfDJ8MTlw3i2dFxEnHbgYLq-NTBvWTMlXSM5JV9sH03i3b4ulUq0JlSns86jxwas797wsxz9mOS2JDlK6nhntJGc80bpNNwUyUnOQou-iTEwsykSBE7-yfc05pjknlLMNciWmrLzxHaJ-kpG8Tjnqo7jxbc
origin: https://localhost:44389
pragma: no-cache
referer: https://localhost:44389/account/signup
sec-fetch-mode: cors
sec-fetch-site: same-site
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
x-xsrf-token: CfDJ8MTlw3i2dFxEnHbgYLq-NTB8DUVF73lPTj3HAOL2bhD-sIDv2N4Fs0JowHt6W-WB5oQltt8ELtCH03XYq2QAxz96SYseDR-pZ6XMXAXRE8mpGUatGKtWp_yNohpbYt2ZQD25NzYqYdw-fCi3hsdQf1g
如果您不希望发生该异常,则必须使用
services.AddControllersWithViews()
而不是
services.AddControllers()