当前位置: 首页 > 知识库问答 >
问题:

部署 .NET 核心站点时,Ajax 调用返回 401

司马彦
2023-03-14

我有一个奇怪的情况,我不能一直重复。我有一个用.NET Core 3.0开发的MVC网站,它授权用户使用.NET Core Identity。当我在本地开发环境中运行站点时,一切都很好(经典的“在我的机器上工作!”)。当我将其部署到登台web服务器时,我开始发现问题。用户可以成功登录、进行身份验证并重定向到主页。注意:除处理身份验证的控制器外,所有控制器都使用[Authorize]属性和[AutoValidateAntiforryToken]属性进行装饰。主页加载正常。但是,在加载页面时会运行两个ajax调用,回调到Home控制器以加载一些条件数据,并检查是否已经设置了一些会话级变量。这些ajax调用返回401 Unauthorized。问题是我不能让这种行为一直重复。实际上,我有另一个用户同时登录(同一个应用程序,同一个服务器),对他们来说工作得很好。我在Chrome中打开了开发人员控制台,并将我认为的问题归结为一个常见(或不常见)因素。有效的调用(例如加载主页,或为其他用户成功的ajax调用)在请求标头中设置了“.AspNetCore.Antiforry”、“.AsbNetCore.Identity.Application”和“.AspNetCore.Session”cookie。无法工作的调用(我的ajax调用)只有“.AspNetCore.Session”cookie集。另一件需要注意的事情是,这种行为发生在站点上的每个ajax调用中。通过导航或表单发布对控制器操作进行的所有调用都可以正常工作。

让我感到奇怪的是,另一个用户可以登录,甚至我也可以在一次新的发布后偶尔登录,并让这些ajax调用在正确设置cookies的情况下正常工作。

这是一些更具体的代码。不确定这是不是我在身份或会话配置中设置错误的原因。

启动.cs

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?}");
        });
    }
}

登录控制器操作

[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 });
    }

主控制器

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 });
        }
    }
}

基本控制器

[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.cshtmlJavascript(加载页面时运行上述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”头在本地运行时总是在ajax请求中正确设置。部署时,它只设置带有会话属性的cookie。我在Startup.cs中找到了一个设置,它将cookie设置为< code>HttpOnly: options。Cookie . HttpOnly = true这会导致我的问题吗?将它设置为假工作吗?如果这不安全,我的方法有什么变通/替代方法。我仍然需要实现用户认证的基本原则,并能够触发ajax请求。

另一个编辑:

今天,在我再次部署该站点后,我在Firefox和Chrome中同时运行了该站点。Firefox在验证后发送了正确的cookie,并且运行正常。然而,Chrome仍然显示401行为。

共有3个答案

杨鸿畅
2023-03-14

这看起来像是一个会话管理问题,使用服务。添加分布式内存缓存()有时会带来会话问题,尤其是在共享宿主环境中。您可以尝试缓存到数据库吗?

例如

services.AddDistributedSqlServerCache(options =>
        {
            options.ConnectionString = connectionString;
            options.SchemaName = "dbo";
            options.TableName = "DistributedCache"; 
        });

确保您处理了欧盟数据保护法问题,这些问题会影响来自. net核心的会话cookie

e、 g.在您的应用程序中,作为可用选项之一,您可以将会话cookie设置为必需的,以便在用户接受cookie条款之前写入会话cookies,即。

services.AddSession(options => 
{
    options.Cookie.IsEssential = true; // make the session cookie Essential
});
仲孙经赋
2023-03-14

正如你所发现的,这是不同浏览器中ajax调用的不同之处。服务器端编程工作正常,除非它面对来自浏览器的不同请求,否则不能有随意的响应(这里是谷歌chome)。我相信在ajax调用中使用断言应该可以解决问题,比如使用的凭据:真实。如果问题仍然存在,请告诉我。

谯振国
2023-03-14

在我看来,您的问题可能是由于在http和https场景中cookie的不同行为!

当发布回超文本传输协议时,在https模式下设置的安全cookie无法检索。

更多信息见此。

我还在你的创业中看到了这一部分,这增加了我猜测的可能性:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

在您的开发环境中,所有东西都可以在http上正常工作。但在部署环境中,如果一些请求转到http,而一些请求转到了http,则一些cookie不会返回,您可能会面临此问题。

 类似资料:
  • .NET核心和ASP.NET核心到底有什么区别?

  • . NET Core gRPC-Web客户端调用ASP.NETCore gRPC-Web服务器http://localhost:5000正常工作。 与部署到具有虚拟应用程序的IIS服务器的客户端代码调用服务器相同(例如http://build.mycompany.ca/myapp)的结果 状态(StatusCode="未实现",Detail="错误的gRPC响应。HTTP状态代码:404") 我的

  • 问题内容: 例如我有一个功能: 我怎样才能返回AJAX后得到的? 问题答案: 因为请求是异步的,所以您无法返回ajax请求的结果(而同步ajax请求是一个 糟糕的 主意)。 最好的选择是将自己的回调传递给f1 然后,您将像这样致电:

  • 回到RC1,我会这样做: 在RC2中,不再有,并且没有任何东西可以让我返回500类型的IActionResult。 我现在问的方法完全不同了吗?我们是否不再尝试捕获代码?我们是否只是让框架将一个泛型500异常抛回API调用者?对于开发,如何查看确切的异常堆栈?

  • 我正在尝试为我的项目创建CI/CT管道。所有步骤都已成功完成,包括,但不幸的是,我在azure webapp部署期间出错 我在这一步中收到以下错误 错误:未找到具有指定模式的包:D:\a\1\a\Drop\xxx.xx.UI.API.zip 知道我做错了什么吗?

  • 下面的指引都基于以下几个假设: 你正在使用的是默认的构建输出路径(dist)。这个路径 可以使用 build.outDir 更改,在这种情况下,你可以从这篇指南中推断出所需的指令。 Vite 已经被安装为了一个你项目的本地开发依赖(dev dependency),并且你已经配置好了如下的 npm script: 你正在使用 npm,或者使用了 Yarn 或其他的包管理工具,可以运行下面的脚本指令: