MVC3 中如何覆盖 Windows 身份验证的 User.Identity?

13

我正在使用MVC3和MSSQL后端构建一个 内部网络 应用程序。 我已经通过自定义角色提供程序实现了身份验证和角色的功能。现在我想重写User.Identity,以允许像User.Identity.FirstName这样的项。但是我找不到任何代码可以显示如何在WindowsIdentity中完成此操作。

我尝试编写自定义提供程序:

public class CPrincipal : WindowsPrincipal
{
    UserDAL userDAL = new UserDAL();
    public CPrincipal(WindowsIdentity identity)
        : base(identity)
    {
        userInfo = userDAL.GetUserProfile(identity.Name.Split('\\')[1]);
        this.identity = identity;
    }
    public UserInfo userInfo { get; private set; }
    public WindowsIdentity identity { get; private set; }
}

并覆盖 WindowsAuthentication 以填充自定义 principal。

    void WindowsAuthentication_OnAuthenticate(object sender, WindowsAuthenticationEventArgs e)
    {
        if (e.Identity != null && e.Identity.IsAuthenticated)
        {
            CPrincipal cPrincipal = new CPrincipal(e.Identity);
            HttpContext.Current.User = cPrincipal;
        }
    }

我在身份验证函数中设置了断点,Principal(安全主体)已被填充;但是,当我在控制器中设置断点时,User只是普通的RolePrincipal,而不是我的自定义Principal。我做错了什么?

编辑:

我在global.asax中注释了上面的代码。 我使用C#覆盖了AuthorizeAttribute:

public class CAuthorize : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        bool authorized = base.AuthorizeCore(httpContext);
        if (!authorized)
        {
            return false;
        }


        IIdentity user = httpContext.User.Identity;
        CPrincipal cPrincipal = new CPrincipal(user);
        httpContext.User = cPrincipal;

        return true;
    } 

}

我将我的主要代码调整为以下内容:

public class CPrincipal : IPrincipal
{
    private UserDAL userDAL = new UserDAL();
    public CPrincipal(IIdentity identity)
    {
        userInfo = userDAL.GetUserProfile(identity.Name.Split('\\')[1]);
        this.Identity = identity;
    }
    public UserInfo userInfo { get; private set; }

    public IIdentity Identity { get; private set; }

    public bool IsInRole(string role)
    {
        throw new NotImplementedException();
    }
}
现在当我设置断点时,在观察窗口中显示了以下内容:

  • User
    • [CSupport.Model.CPrincipal]
    • Identity

“Identity”是可以访问的;然而,“CPrincipal”只能在观察窗口中访问,不能直接访问。

编辑: 感谢所有为此作出贡献的人。你们极大地扩展了我的理解。

我两种方法都试过了,所以我想分享一下。

选项1:在Global.asax中覆盖Authorize Request。

这是我选择的方法。

我没有使用Application_AuthenticateRequest,因为(根据HttpContext.Current.User is null even though Windows Authentication is on),在Windows身份验证过程中用户还没有被填充,因此我无法使用任何东西去获取用户信息。

Application_AuthorizeRequest是链中的下一个事件,发生在带入Windows身份验证之后。

    protected void Application_AuthorizeRequest(object sender, EventArgs e)
    {
        if (User.Identity.IsAuthenticated && Roles.Enabled)
        {
            Context.User = new FBPrincipal(HttpContext.Current.User.Identity);
        }
    }

这是Principal的覆盖

public class CPrincipal : IPrincipal
{
    private UserDAL userDAL = new UserDAL();
    public CPrincipal(IIdentity identity)
    {
        userInfo = userDAL.GetUserProfile(identity.Name.Split('\\')[1]);
        this.Identity = identity;
    }
    public UserInfo userInfo { get; private set; }

    public IIdentity Identity { get; private set; }

    public bool IsInRole(string role)
    {
        return userDAL.IsUserInRole(userInfo.UserName, role);
    }
}

这是您访问新创建的Principal中更新信息的方法。

    [Authorize(Roles = "super admin")]
    public ActionResult Dashboard()
    {
        string firstname = (User as CPrincipal).userInfo.FirstName; // <--
        DashboardModel dModel = reportDAL.GetChartData();
        return View(dModel);
    }

选项二:重写AuthorizeAttribute

这是已重写的Principal(与上述相同)

public class CPrincipal : IPrincipal
{
    private UserDAL userDAL = new UserDAL();
    public CPrincipal(IIdentity identity)
    {
        userInfo = userDAL.GetUserProfile(identity.Name.Split('\\')[1]);
        this.Identity = identity;
    }
    public UserInfo userInfo { get; private set; }

    public IIdentity Identity { get; private set; }

    public bool IsInRole(string role)
    {
        return userDAL.IsUserInRole(userInfo.UserName, role);
    }
}

这里是Authorize属性的覆盖

public class CAuthorize : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        bool authorized = base.AuthorizeCore(httpContext);
        if (!authorized)
        {
            return false;
        }


        IIdentity user = httpContext.User.Identity;
        CPrincipal cPrincipal = new CPrincipal(user);
        httpContext.User = cPrincipal;

        return true;
    } 

}

这是您更改要使用的AuthorizeAttribute并利用新信息的位置。

    [CAuthorize(Roles = "super admin")] // <--
    public ActionResult Dashboard()
    {
        string firstname = (User as CPrincipal).userInfo.FirstName; // <--
        DashboardModel dModel = reportDAL.GetChartData();
        return View(dModel);
    }

选项1全局处理所有内容,选项2针对个别情况进行处理。


等一下...那个事件处理程序是什么?你不是在尝试使用ASP.NET登录控件吧?这个事件位于哪里,它与哪个事件相关联? - Erik Funkenbusch
我正在一个内部网站上使用Windows身份验证。这个事件处理程序在global.asax中。 - Toby Jones
如果我在 CPrincipal 中将 Identity 强制转换为 WindowsIdentity,则会出现以下错误:“'CSupport.Models.CPrincipal.Identity' 无法实现 'System.Security.Principal.IPrincipal.Identity',因为它没有匹配的返回类型 'System.Security.Principal.IIdentity'”。 如果我将 override 改为 WindowsPrincipal,则会出现此错误:“'System.Security.Principal.WindowsPrincipal' 不包含接受 0 个参数的构造函数”。 - Toby Jones
不,你需要在使用它时将其转换为WindowsIdentity,而不是在将其分配给上下文时进行转换。 - Erik Funkenbusch
感谢您在决定使用Application_AuthorizeRequest时的更新。我之前也在使用AuthenticateRequest,它让我抓狂了。 - Abdulsattar Mohammed
显示剩余2条评论
2个回答

6

不要按照这种方式,你应该在global.asax中重写Application_AuthenticateRequest方法,然后使用Current.User而不是HttpContext.Current.User(不确定为什么,但有区别)。

然后,在控制器中访问它的一种简单方法是创建一个扩展方法。像这样:

public static class IIdentityExtensions {
    public static IMyIdentity MyIdentity(this IIdentity identity) {
        return (IMyIdentity)identity;
    }
}

那么你只需要写成 User.Identity.IMyIdenty().FirstName。你也可以将其写成属性的形式。

这是我使用的代码:

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    FormsAuthenticationTicket authTicket = FormsAuthentication
       .Decrypt(authCookie.Value);
    var identity = new MyIdentity(authTicket.Name, "Forms", 
       FormsAuthenticationHelper.RetrieveAuthUserData(authTicket.UserData));
    Context.User = new GenericPrincipal(identity, 
       DependencyResolver.Current.GetService<ISecurityHandler>()
          .GetRoles(identity.Name).ToArray());
}

现在,忽略DependencyResolver和自定义auth ticket的内容,这很基本并且对我来说可以正常工作。

然后,在我的应用程序中,当我需要从我的自定义身份信息获取信息时,我只需要使用((IMyIdentity)User.Identity).FirstName或任何我需要的内容进行转换。它不是什么高深技术,而且它有效。


我尝试了只使用 authenticaterequest 部分,结果得到:HttpContext.Current.User 为空且 Current 不存在。 protected void Application_AuthenticateRequest(object sender, EventArgs e) { if (HttpContext.Current.User.Identity != null && HttpContext.Current.User.Identity.IsAuthenticated) { HttpContext.Current.User = new CPrincipal(HttpContext.Current.User.Identity); } } - Toby Jones
@TobyJones - 请查看我的编辑。你的职责是在AuthenticateRequest中进行身份验证。用户当然为空,因为尚未进行身份验证。 - Erik Funkenbusch

2

我做错了什么?

可能是 [Authorize] 属性覆盖了你的更改。所以,不要在 Global.asaxWindowsAuthentication_OnAuthenticate 方法中进行更改,而应该编写自定义的 Authorize 属性,如下所示:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var authorized = base.AuthorizeCore(httpContext);
        if (!authorized)
        {
            return false;
        }


        var user = httpContext.User as WindowsIdentity;
        CPrincipal cPrincipal = new CPrincipal(user);
        httpContext.User = cPrincipal;

        return true;
    }
}

然后使用您的自定义属性而不是默认属性:

[MyAuthorize]
public ActionResult SomeAction()
{
    // User.Identity will be your custom principal here
}

在 ASP.NET MVC 中,执行授权的标准方式是通过授权操作过滤器而不是通过 Global.asax 中的事件。

授权属性不会覆盖您的上下文。 - Erik Funkenbusch
1
无论如何,在ASP.NET MVC应用程序中使用Global.asax中的事件来执行授权都是不好的实践。您应该使用自定义授权过滤器。 - Darin Dimitrov
1
他的代码中没有进行授权,他只是应用了自定义主体,据我所知这仍然应该在global.asax中完成。 - Erik Funkenbusch
不,我认为在Global.asax中应用自定义Principal不是一个好主意。这段代码完全无法在隔离环境中进行测试,并且很难仅在某些控制器操作上有条件地应用它。如果您只想在某些区域上应用授权怎么办?在ASp.NET MVC应用程序中执行授权的正确方法不是通过Global.asax。当然,这是我的观点。我尊重你的观点,你可以在任何地方进行授权。 - Darin Dimitrov
1
好的,我从未遇到过一个应用程序在不同的部分使用不同种类的原则,但如果这是你需要的,那当然可以。然而,我认为自定义原则应该尽早地在事件链中设置,以防止出现一个事件看到一个原则,而另一个事件看到另一个原则的情况。相对容易创建一个可测试的包装器来处理这个问题,但这也是接口的目的所在。但是,我想我们对此有不同的看法。 - Erik Funkenbusch
很好,结果是什么? - Darin Dimitrov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接