asp.net-mvc-5 - 为什么 ASP.NET MVC 应用程序在修改后无法立即识别用户角色更改?

标签 asp.net-mvc-5 entity-framework-6 asp.net-apicontroller role-base-authorization

我有一个 ASP.NET MVC 混合应用程序,除了 MVC Controller 之外,它还有一个 ApiController。我在 MVC Controller 和 ApiController 中在 Controller 级别(有时在方法级别)使用基于角色的授权属性。我使用 Entity Framework 6 进行基于模型的设计。

Controller级别授权:

[Authorize(Roles = "Administrator,RegularUser")]
public class EngineController : ApiController
{

[System.Web.Mvc.Authorize(Roles = "Administrator,RegularUser")]
public class ProjectsController : Controller
{

当我从 Controller 级别授权转移时,因为它可供非登录用户访问:

    [AllowAnonymous]
    [HttpPost]
    public async Task<CheckCouponReturnValueModel> CheckCoupon([FromBody] CouponCodeRequestModel requestModel)

或者因为我软化了授权(“User”的权限低于“RegularUser”):

    [OverrideAuthorization()]
    [Authorize(Roles = "User")]
    [HttpPost]
    public TopicReturnValueModel GetTopic([FromBody]TopicReferenceModel requestModel)

用户注册后,通常会获得“User”和“RegularUser”角色。我可以通过查询数据库的 AspNetUserRoles 表来确认这一点,或者我什至有一个管理 View 供管理员控制,它甚至可以通过相同的 ASP.NET MVC 应用程序显示角色。然而,当新创建的用户尝试访问 MVC Controller 上的端点或 View 时,它会受到框架授权规则的拒绝,并收到 401 Unauthorized。就像一些内部部分(我不知道它是否使用 RoleManager 或者幕后的内容)“没有收到用户已经在角色中的消息”。

奇怪的是,ApiController 端点可以工作并识别用户的角色。 MVC Controller 抛出 401 后,用户将被重定向到登录页面(带有重定向提示)。在用户登录的同时,菜单栏会反射(reflect)这一点(即使重定向到登录页面 - 这很令人困惑)。一旦用户服从并重新登录,精神 split 的行为就会突然消失,MVC Controller 端点也开始识别用户的角色。不用说,这是 Not Acceptable 。

我的包裹:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="animate.css" version="3.3.0.0" targetFramework="net461" />
  <package id="Antlr" version="3.5.0.2" targetFramework="net45" />
  <package id="bootstrap" version="3.3.7" targetFramework="net461" />
  <package id="Bootstrap.Datepicker" version="1.6.4" targetFramework="net461" />
  <package id="EntityFramework" version="6.1.3" targetFramework="net461" />
  <package id="FontAwesome" version="4.7.0" targetFramework="net461" />
  <package id="free-jqGrid" version="4.14.0" targetFramework="net461" />
  <package id="jQuery" version="2.2.4" allowedVersions="[2,3)" targetFramework="net461" />
  <package id="jquery.datatables" version="1.10.12" targetFramework="net461" />
  <package id="jQuery.InputMask" version="3.3.4" targetFramework="net461" />
  <package id="jquery.noty" version="2.3.5" targetFramework="net461" />
  <package id="jQuery.UI.Combined" version="1.12.1" targetFramework="net461" />
  <package id="jQuery.Validation" version="1.16.0" targetFramework="net461" />
  <package id="JSZip" version="3.1.3" targetFramework="net461" />
  <package id="knockoutjs" version="3.4.2" targetFramework="net461" />
  <package id="KnockoutJS.Validation" version="3.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Cors" version="5.2.3" targetFramework="net461" />
  <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net461" />
  <package id="Microsoft.AspNet.Identity.EntityFramework" version="2.2.1" targetFramework="net461" />
  <package id="Microsoft.AspNet.Identity.Owin" version="2.2.1" targetFramework="net461" />
  <package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.Web.Optimization" version="1.1.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Cors" version="5.2.3" targetFramework="net461" />
  <package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net45" />
  <package id="Microsoft.jQuery.Unobtrusive.Validation" version="3.2.3" targetFramework="net45" />
  <package id="Microsoft.Owin" version="3.1.0" targetFramework="net461" />
  <package id="Microsoft.Owin.Host.SystemWeb" version="3.1.0" targetFramework="net461" />
  <package id="Microsoft.Owin.Security" version="3.1.0" targetFramework="net461" />
  <package id="Microsoft.Owin.Security.Cookies" version="3.1.0" targetFramework="net461" />
  <package id="Microsoft.Owin.Security.Facebook" version="3.1.0" targetFramework="net461" />
  <package id="Microsoft.Owin.Security.Google" version="3.1.0" targetFramework="net461" />
  <package id="Microsoft.Owin.Security.MicrosoftAccount" version="3.1.0" targetFramework="net461" />
  <package id="Microsoft.Owin.Security.OAuth" version="3.1.0" targetFramework="net461" />
  <package id="Microsoft.Owin.Security.Twitter" version="3.1.0" targetFramework="net461" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
  <package id="MimeTypeMap.List" version="1.1.0" targetFramework="net461" />
  <package id="Modernizr" version="2.8.3" targetFramework="net45" />
  <package id="Moment.js" version="2.18.1" targetFramework="net461" />
  <package id="morelinq" version="2.3.0" targetFramework="net461" />
  <package id="mousetrap" version="1.3" targetFramework="net461" />
  <package id="Mvc.JQuery.DataTables" version="1.5.31" targetFramework="net461" />
  <package id="Mvc.JQuery.DataTables.Common" version="1.5.31" targetFramework="net461" />
  <package id="Mvc.JQuery.Datatables.Templates" version="1.5.31" targetFramework="net461" />
  <package id="MvcSiteMapProvider.MVC5" version="4.6.22" targetFramework="net45" />
  <package id="MvcSiteMapProvider.MVC5.Core" version="4.6.22" targetFramework="net45" />
  <package id="MvcSiteMapProvider.Web" version="4.6.22" targetFramework="net45" />
  <package id="Nager.Date" version="1.6.0" targetFramework="net461" />
  <package id="Newtonsoft.Json" version="10.0.2" targetFramework="net461" />
  <package id="Owin" version="1.0" targetFramework="net45" />
  <package id="pdfmake" version="0.1.18" targetFramework="net461" />
  <package id="PDFsharp" version="1.32.3057.0" targetFramework="net461" />
  <package id="QueryInterceptor" version="0.2" targetFramework="net45" />
  <package id="ReCaptcha-AspNet" version="1.4.0" targetFramework="net461" />
  <package id="Respond" version="1.4.2" targetFramework="net461" />
  <package id="Sendgrid" version="9.1.1" targetFramework="net461" />
  <package id="SendGrid.CSharp.HTTP.Client" version="3.3.0" targetFramework="net461" />
  <package id="Spin.js" version="2.3.2.1" targetFramework="net461" />
  <package id="Stripe.net" version="8.2.0" targetFramework="net461" />
  <package id="System.Linq.Dynamic.Core" version="1.0.6.13" targetFramework="net461" />
  <package id="System.Net.Http" version="4.0.0" targetFramework="net461" allowedVersions="[4,4.0.0]" />
  <package id="WebActivatorEx" version="2.2.0" targetFramework="net461" />
  <package id="WebGrease" version="1.6.0" targetFramework="net45" />
</packages>

ApiController[Authorize(Roles="...")] 属性正在使用 System.Web.Http.AuthorizeAttribute当我的 MVC Controller 使用 System.Web.Mvc.AuthorizeAttribute 时。我认为 ApiController 的角色是正确的,但显然我将 MVC Controller 中的所有授权声明替换为 System.Web.Http.AuthorizeAttribute ,但这也没有解决问题。 p>


@solidau 询问的 Startup.Auth:

    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context, user manager and role manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            ExpireTimeSpan = new System.TimeSpan(8, 0, 0),    // Uncomment this to enable 8 hour inactivity/idle expiration
            SlidingExpiration = true,
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),

                // https://stackoverflow.com/questions/20149750/unauthorised-webapi-call-returning-login-page-rather-than-401
                // http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
                // http://brockallen.com/2013/10/27/host-authentication-and-web-api-with-owin-and-active-vs-passive-authentication-middleware/
                OnApplyRedirect = ctx =>
                {
                    if (!IsAjaxRequest(ctx.Request))
                    {
                        ctx.Response.Redirect(ctx.RedirectUri);
                    }
                }
            }
        });

        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
        app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

        // Enables the application to remember the second login verification factor such as phone or email.
        // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
        // This is similar to the RememberMe option when you log in.
        app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

    }

    private static bool IsAjaxRequest(IOwinRequest request)
    {
        IReadableStringCollection queryXML = request.Query;
        if ((queryXML != null) && (queryXML["X-Requested-With"] == "XMLHttpRequest"))
        {
            return true;
        }

        IReadableStringCollection queryJSON = request.Query;
        if ((queryJSON != null) && (queryJSON["Content-Type"] == "application/json"))
        {
            return true;
        }

        IHeaderDictionary headersXML = request.Headers;
        var isAjax = ((headersXML != null) && (headersXML["X-Requested-With"] == "XMLHttpRequest"));

        IHeaderDictionary headers = request.Headers;
        var isJson = ((headers != null) && (headers["Content-Type"] == "application/json"));

        return isAjax || isJson;
    }

是的,有一个技巧可以使 session 不仅可用于 MVC Controller ,而且还可用于 ApiController,因为我确实需要它。我猜想 auth 子系统具有与 MVC Controller 使用的常规实体上下文(在基类中实例化)不同的数据库上下文。

public abstract class WorkflowControllersBase : Controller
{
    protected Entities _context = new Entities();

并且每个 MVC Controller 都是该基类的后代。尽管我可能有不同的上下文,但我绝对确认我在数据库中添加了正确的角色,它们被持久化了。身份验证子系统上下文是否会与数据库状态不同步?如何同步?


@Ali,当前代码:

                IdentityResult result = await UserManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    await UserManager.AddToRoleAsync(user.Id, "User");
                    await UserManager.AddToRoleAsync(user.Id, model.AccountType);
                    await SignInAsync(user, isPersistent: true);
                    if (model.AccountType != "QuickDeal")
                    {
                        if (User.IsInRole("QuickDeal"))  // Remove from QuickDeal if the user upgraded
                            await UserManager.RemoveFromRoleAsync(user.Id, "QuickDeal");
                        await UserManager.AddToRoleAsync(user.Id, "RegularUser");
                    }

我尝试在 SignInAsync 之后执行角色添加/删除,但到目前为止没有帮助。实际的 SignInAsyncAccountController 的一个方法,由 ASP.NET MVC 模板提供:

    private async Task SignInAsync(ApplicationUser user, bool isPersistent)
    {
        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
        AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, await user.GenerateUserIdentityAsync(UserManager));
    }

<configSections>
  <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
</configSections>
<connectionStrings>
  <add name="DefaultConnection" connectionString="Server=tcp:xyx.database.windows.net,1433;Initial Catalog=XYZ;Persist Security Info=False;User ID=csaba;Password=*************;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" providerName="System.Data.SqlClient" />
  <add name="Entities" connectionString="metadata=res://*/Models.EntityModel.csdl|res://*/Models.EntityModel.ssdl|res://*/Models.EntityModel.msl;provider=System.Data.SqlClient;provider connection string='Server=tcp:zyx.database.windows.net,1433;Initial Catalog=XYZ;Persist Security Info=False;User ID=csaba;Password=***************;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'" providerName="System.Data.EntityClient" />
</connectionStrings>

请注意,Azure 提供的默认字符串未启用 MARS。但这样我就得到了一个错误,所以我设置了MultipleActiveResultSets=True。也许这就是解决方案的一个线索。

最佳答案

由于您正在使用 cookie,因此您需要确保在分配新角色后使用新角色重新创建 cookie(否则,过时的 cookie 会一直存在,直到过期)。授予新角色后,您可以使用身份验证管理器注销用户,然后再次登录,从而使用新添加的角色重新创建他们的 cookie。我已经包含了一个片段,但您必须根据您的代码进行自定义:

IAuthenticationManager authenticationManager = HttpContext.GetOwinContext().Authentication;
authenticationManager.SignOut("ApplicationCookie");
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, identity);

关于asp.net-mvc-5 - 为什么 ASP.NET MVC 应用程序在修改后无法立即识别用户角色更改?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43701607/

相关文章:

entity-framework - 是否可以将现有的 MVC4 Web 应用程序升级到 MVC5?

jquery - MVC 将数据传入和传出 Bootstrap Modal

c# - 我应该在 Model 还是 viewModel 中放置 "required"数据注释?

c# - 如何防止 Entity Framework 6在配置文件中创建绑定(bind)重定向

C# Web Api - IValidatableObject validationContext 服务提供者为空

asp.net - 从 SSIS 调用 Web API 页面?

javascript - HttpPostedFileBase 未绑定(bind)到模型 C# MVC 5

c# - 升级到 EF 6.1.1 使 [NotMapped] 的效果消失

c# - 如何将列表中间的项目放在首位?

.net - 如何在不使用 Authorize 属性的情况下获取 User.Identity 值?