我有一个奇怪的情况,我无法始终如一地复制。我有一个在 .NET Core 3.0 中开发的 MVC 网站,并使用 .NET Core Identity 授权用户。当我在本地开发环境中运行站点时,一切正常(经典的“在我的机器上运行!”)。当我将它部署到我的登台 Web 服务器时,我开始发现问题。用户可以成功登录,通过身份验证,并重定向到主页。注意:所有 Controller ,除了一个处理身份验证,都用 [Authorize]
修饰。属性和 [AutoValidateAntiforgeryToken]
属性。主页加载得很好。但是,有几个 ajax 调用在页面加载时运行,该回调到 Home Controller 以加载一些条件数据并检查是否已经设置了一些 session 级别的变量。 这些 ajax 调用返回 401 Unauthorized .问题是我无法始终如一地重复这种行为。我实际上有另一个用户同时登录(相同的应用程序,相同的服务器),并且对他们来说效果很好。我在 Chrome 中打开了开发者控制台,并将我认为的问题归结为一个常见(或不常见)的因素。工作的调用(例如加载主页,或其他用户成功的 ajax 调用)具有“.AspNetCore.Antiforgery”、“.AspNetCore.Identity.Application”和“.AspNetCore.Session”在请求 header 中设置的 cookie。不起作用的调用(我的 ajax 调用)只有“.AspNetCore.Session”cookie 集。另一件需要注意的是,这种行为会发生在网站上的每个 ajax 调用中。通过导航或表单发布对 Controller 操作的所有调用都可以正常工作。
不工作:
作品:
令我感到奇怪的是,另一个用户可以登录,甚至我也可以在新发布后偶尔登录,并且在正确设置 cookie 的情况下,这些 ajax 调用工作得很好。
下面是一些更具体的代码。不确定是不是我在 Identity 或 Session 配置中设置了错误。
启动文件
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Env { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<User, UserRole>(options =>
{
options.User.RequireUniqueEmail = true;
}).AddEntityFrameworkStores<QCAuthorizationContext>()
.AddDefaultTokenProviders(); ;
services.AddDbContext<QCAuthorizationContext>(cfg =>
{
cfg.UseSqlServer(Configuration.GetConnectionString("Authorization"));
});
services.AddSingleton<IConfiguration>(Configuration);
services.AddControllersWithViews();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromHours(4);
options.Cookie.HttpOnly = true;
// Make the session cookie essential
options.Cookie.IsEssential = true;
});
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
});
services.ConfigureApplicationCookie(options =>
{
//cookie settings
options.ExpireTimeSpan = TimeSpan.FromHours(4);
options.SlidingExpiration = true;
options.LoginPath = new Microsoft.AspNetCore.Http.PathString("/Account/Login");
});
services.AddHttpContextAccessor();
//services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
IMvcBuilder builder = services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "auth4",
pattern: "{controller=Account}/{action=Authenticate}/{id?}");
});
}
}
登录 Controller 操作
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel iViewModel)
{
ViewBag.Message = "";
try
{
var result = await signInManager.PasswordSignInAsync(iViewModel.Email, iViewModel.Password, false, false);
if (result.Succeeded)
{
var user = await userManager.FindByNameAsync(iViewModel.Email);
if (!user.FirstTimeSetupComplete)
{
return RedirectToAction("FirstLogin");
}
return RedirectToAction("Index", "Home");
}
else
{
ViewBag.Message = "Login Failed.";
}
}
catch (Exception ex)
{
ViewBag.Message = "Login Failed.";
}
return View(new LoginViewModel() { Email = iViewModel.Email });
}
家庭 Controller
public class HomeController : BaseController
{
private readonly ILogger<HomeController> _logger;
public HomeController(IConfiguration configuration, ILogger<HomeController> logger, UserManager<User> iUserManager) : base(configuration, iUserManager)
{
_logger = logger;
}
public async Task<IActionResult> Index()
{
HomeViewModel vm = HomeService.GetHomeViewModel();
vm.CurrentProject = HttpContext.Session.GetString("CurrentProject");
vm.CurrentInstallation = HttpContext.Session.GetString("CurrentInstallation");
if (!string.IsNullOrEmpty(vm.CurrentProject) && !string.IsNullOrEmpty(vm.CurrentInstallation))
{
vm.ProjectAndInstallationSet = true;
}
return View(vm);
}
public IActionResult CheckSessionVariablesSet()
{
var currentProject = HttpContext.Session.GetString("CurrentProject");
var currentInstallation = HttpContext.Session.GetString("CurrentInstallation");
return Json(!string.IsNullOrEmpty(currentProject) && !string.IsNullOrEmpty(currentInstallation));
}
public IActionResult CheckSidebar()
{
try
{
var sidebarHidden = bool.Parse(HttpContext.Session.GetString("SidebarHidden"));
return Json(new { Success = sidebarHidden });
}
catch (Exception ex)
{
return Json(new { Success = false });
}
}
}
基本 Controller
[AutoValidateAntiforgeryToken]
[Authorize]
public class BaseController : Controller
{
protected IConfiguration configurationManager;
protected SQLDBContext context;
protected UserManager<User> userManager;
public BaseController(IConfiguration configuration, UserManager<User> iUserManager)
{
userManager = iUserManager;
configurationManager = configuration;
}
public BaseController(IConfiguration configuration)
{
configurationManager = configuration;
}
protected void EnsureDBConnection(string iProject)
{
switch (iProject)
{
case "A":
DbContextOptionsBuilder<SQLDBContext> AOptionsBuilder = new DbContextOptionsBuilder<SQLDBContext>();
AOptionsBuilder.UseLazyLoadingProxies().UseSqlServer(configurationManager.GetConnectionString("A"));
context = new SQLDBContext(AOptionsBuilder.Options);
break;
case "B":
DbContextOptionsBuilder<SQLDBContext> BOptionsBuilder = new DbContextOptionsBuilder<SQLDBContext>();
BOptionsBuilder.UseLazyLoadingProxies().UseSqlServer(configurationManager.GetConnectionString("B"));
context = new SQLDBContext(BOptionsBuilder.Options);
break;
case "C":
DbContextOptionsBuilder<SQLDBContext> COptionsBuilder = new DbContextOptionsBuilder<SQLDBContext>();
COptionsBuilder.UseLazyLoadingProxies().UseSqlServer(configurationManager.GetConnectionString("C"));
context = new SQLDBContext(COptionsBuilder.Options);
break;
}
}
}
_Layout.cshtml Javascript(加载页面时运行上述ajax调用)
<script type="text/javascript">
var afvToken;
$(function () {
afvToken = $("input[name='__RequestVerificationToken']").val();
$.ajax({
url: VirtualDirectory + '/Home/CheckSidebar',
headers:
{
"RequestVerificationToken": afvToken
},
complete: function (data) {
console.log(data);
if (data.responseJSON.success) {
toggleSidebar();
}
}
});
$.ajax({
url: VirtualDirectory + '/Home/CheckSessionVariablesSet',
headers:
{
"RequestVerificationToken": afvToken
},
complete: function (data) {
console.log(data);
if (data.responseJSON) {
$('#sideBarContent').attr('style', '');
}
else {
$('#sideBarContent').attr('style', 'display:none;');
}
}
});
$.ajax({
url: VirtualDirectory + '/Account/UserRoles',
headers:
{
"RequestVerificationToken": afvToken
},
complete: function (data) {
if (data.responseJSON) {
var levels = data.responseJSON;
if (levels.includes('Admin')) {
$('.adminSection').attr('style', '');
}
else {
$('.adminSection').attr('style', 'display:none;');
}
}
}
});
});
</script>
编辑:
我发现的是带有“.AspNetCore.Antiforgery”、“.AspNetCore.Identity.Application”和“.AspNetCore.Session”属性的“Cookie” header ,在本地运行时在ajax请求中始终正确设置。部署时,它仅设置具有 session 属性的 cookie。我在 Startup.cs 中找到了一个设置,将 cookie 设置为
HttpOnly: options.Cookie.HttpOnly = true;
这可能会导致我的问题吗?将其设置为 false 会起作用吗?如果那不安全,那么我的方法有哪些变通方法/替代方法。我仍然需要实现用户身份验证的基本原则并且能够触发 ajax 请求。另一个编辑:
今天再次部署该站点后,我在 Firefox 和 Chrome 中同时运行该站点。 Firefox 在验证后发送了正确的 cookie,并且运行良好。但是,Chrome 仍然显示 401 行为。
最佳答案
在我看来,您的问题可能是由于 http 和 https 场景中 cookie 的不同行为!
在 https
中设置的安全 cookie回发到 http
时无法检索模式.
见 this了解更多信息。
我也在你的 Startup 中看到了这部分,这增加了我猜测的机会:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
在您的开发环境中,
http
一切正常.但是在部署环境https
进来,如果有一些请求,请转到 http
还有一些去https
,某些 cookie 不会返回,您可能会遇到此问题。
关于c# - 部署 .NET Core 站点时,Ajax 调用返回 401,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62194715/