ASP.NET MVC 表单身份验证 + 授权属性 + 简单角色

Posted

技术标签:

【中文标题】ASP.NET MVC 表单身份验证 + 授权属性 + 简单角色【英文标题】:ASP.NET MVC Forms Authentication + Authorize Attribute + Simple Roles 【发布时间】:2010-11-25 23:35:22 【问题描述】:

我正在尝试向 ASP.NET MVC 应用程序添加简单身份验证和授权。

我只是想在基本表单身份验证中添加一些附加功能(由于简单性和自定义数据库结构)

假设这是我的数据库结构: 用户: 用户名 密码 角色(理想情况下是一些枚举。如果需要,字符串。目前,用户只有一个角色,但这可能会改变)

高级问题: 鉴于上述数据库结构,我希望能够做到以下几点:

使用表单身份验证的简单登录 用以下方式装饰我的行为: [授权(Roles= MyRoles.Admin, MyRoles.Member)] 在我的视图中使用角色(以确定要在某些部分中显示的链接)

目前,我真正确定的是如何进行身份验证。在那之后我迷路了。我不确定在什么时候获取用户角色(登录,每次授权?)。由于我的角色可能不是字符串,我不确定它们将如何适应 User.IsInRole()。

现在,我在这里问是因为我还没有找到一个“简单”的方法来完成我所需要的。我见过很多例子。

用于身份验证:

我们有简单的用户验证来检查数据库和“SetAuthCookie” 或者我们覆盖成员资格提供程序并在 ValidateUser 内部执行此操作 在其中任何一个中,我都不确定如何使用我的简单用户角色,以便它们与以下内容一起使用: HttpContext.Current.User.IsInRole("管理员") 此外,我不确定如何修改它以使用我的枚举值。

对于授权,我见过:

派生 AuthorizeAttribute 并实施 AuthorizeCore OR OnAuthorization 来处理角色? 实施 IPrincipal?

任何帮助将不胜感激。但是,我担心我可能需要很多细节,因为我在 Google 上搜索的内容似乎都不适合我需要做的事情。

【问题讨论】:

【参考方案1】:

我想我已经实现了类似的东西。 我的解决方案基于NerdDinnertutorial,如下所示。

当您登录用户时,添加如下代码:

var authTicket = new FormsAuthenticationTicket(
    1,                             // version
    userName,                      // user name
    DateTime.Now,                  // created
    DateTime.Now.AddMinutes(20),   // expires
    rememberMe,                    // persistent?
    "Moderator;Admin"                        // can be used to store roles
    );

string encryptedTicket = FormsAuthentication.Encrypt(authTicket);

var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);

将以下代码添加到Global.asax.cs

protected void Application_AuthenticateRequest(Object sender, EventArgs e)

    HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (authCookie == null || authCookie.Value == "")
        return;

    FormsAuthenticationTicket authTicket;
    try
    
        authTicket = FormsAuthentication.Decrypt(authCookie.Value);
    
    catch
    
        return;
    

    // retrieve roles from UserData
    string[] roles = authTicket.UserData.Split(';');

    if (Context.User != null)
        Context.User = new GenericPrincipal(Context.User.Identity, roles);

完成此操作后,您可以在控制器操作代码中使用[Authorize] 属性:

[Authorize(Roles="Admin")]
public ActionResult AdminIndex ()

如果您还有其他问题,请告诉我。

【讨论】:

如果它不适合你,只需在 Web.config 中添加这个<appSettings> <add key="enableSimpleMembership" value="false" /> </appSettings> 正是我想要的!谢谢 我试过了,但 Context.User 对我来说总是空的。也许我需要更改 web.config 中的某些内容。但是,我通过删除 'if (Context.User != null)' 并将 Application_AuthenticateRequest 中的最后一行更改为 'Context.User = new GenericPrincipal(new GenericIdentity(authTicket.Name), roles);' 来使其工作。 正是我需要的!无法想象像这样简单的事情需要深入研究这么多博客、问题和书籍! 嗯,User.IsAuthenticated 总是返回 false。我也可以检查User.IsInRole吗(在我解决我的问题之后)?【参考方案2】:

构建一个自定义AuthorizeAttribute,它可以使用您的枚举而不是字符串。当您需要授权时,通过附加枚举类型名称 + 枚举值将枚举转换为字符串,并从那里使用 IsInRole

要将角色添加到授权用户,您需要附加到 HttpApplication AuthenticateRequest 事件,类似于 http://www.eggheadcafe.com/articles/20020906.asp 中的第一个代码(但将大量嵌套的 if 语句反转为保护子句!)。

您可以在表单身份验证 cookie 中往返用户角色或每次从数据库中获取它们。

【讨论】:

实际上,这正是我最终所做的。我终于意识到,如果你使用 IsInRole,你就无法绕过 String 的事情。所以我可以在我的控制器中使用我的枚举,但是如果我需要在视图中检查角色,我会被 IsInRole 卡住...... ThanX 分解枚举 -> 从属性到帮助器的字符串转换,使用属性中的帮助器并创建一个 html 帮助器扩展方法 IsUserInRole,它也使用帮助器,但可以从视图中轻松访问。 还有一个选项是使用具有字符串属性的类作为角色。 public static class MyRoles public const string Viewer = "Viewer"; ..etc.. 。然后您可以添加 GetAll()、GetDefault() 等方法并调用[Authorize(Roles=MyRoles.Viewer)] 等角色。【参考方案3】:

我做了这样的事情:

使用 Global.asax.cs 加载要在会话、缓存或应用程序状态中比较的角色,或在 ValidateUser 控制器上动态加载它们

将 [Authorize] 属性分配给您的控制器,您需要授权

 [Authorize(Roles = "Admin,Tech")]

或允许访问,例如 Login 和 ValidateUser 控制器使用以下属性

 [AllowAnonymous] 

我的登录表单

<form id="formLogin" name="formLogin" method="post" action="ValidateUser">
<table>
  <tr>
    <td>
       <label for="txtUserName">Username: (AD username) </label>
    </td>
    <td>
       <input id="txtUserName" name="txtUserName" role="textbox" type="text" />
    </td>
  </tr>
  <tr>
     <td>
         <label for="txtPassword">Password: </label>
     </td>
     <td>
         <input id="txtPassword" name="txtPassword" role="textbox" type="password" />
     </td>
  </tr>
  <tr>
      <td>
         <p>
           <input id="btnLogin" type="submit" value="LogIn" class="formbutton" />
        </p>
      </td>
  </tr>
</table>
       @Html.Raw("<span id='lblLoginError'>" + @errMessage + "</span>")
</form>

从表单帖子调用的登录控制器和验证用户控制器

验证用户是通过 WCF 服务验证服务本地的 Windows AD 上下文的身份验证,但您可以将其更改为您自己的身份验证机制

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Security.Principal;
using MyMVCProject.Extensions;
namespace MyMVCProject.Controllers

public class SecurityController : Controller

    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    
        Session["LoginReturnURL"] = returnUrl;
        Session["PageName"] = "Login";
        return View("Login");
    
    [AllowAnonymous]
    public ActionResult ValidateUser()
    
        Session["PageName"] = "Login";
        ViewResult retVal = null;
        string loginError = string.Empty;
        HttpContext.User = null;

        var adClient = HttpContext.Application.GetApplicationStateWCFServiceProxyBase.ServiceProxyBase<UserOperationsReference.IUserOperations>>("ADService").Channel;

        var username = Request.Form["txtUserName"];
        var password = Request.Form["txtPassword"];

        //check for ad domain name prefix
        if (username.Contains(@"\"))
          username = username.Split('\\')[1];

        //check for the existence of the account 
        var acctReq = new UserOperationsReference.DoesAccountExistRequest();
        acctReq.userName = username;
        //account existence result
        var accountExist = adClient.DoesAccountExist(acctReq);
        if (!accountExist.DoesAccountExistResult)
        
            //no account; inform the user
            return View("Login", new object[]  "NO_ACCOUNT", accountExist.errorMessage );
        
        //authenticate
        var authReq = new UserOperationsReference.AuthenticateRequest();
        authReq.userName = username;
        authReq.passWord = password;
        var authResponse = adClient.Authenticate(authReq);
        String verifiedRoles = string.Empty;
        //check to make sure the login was as success against the ad service endpoint
        if (authResponse.AuthenticateResult == UserOperationsReference.DirectoryServicesEnumsUserProperties.SUCCESS)
        
            Dictionary<string, string[]> siteRoles = null;

            //get the role types and roles
            if (HttpContext.Application["UISiteRoles"] != null)
                siteRoles = HttpContext.Application.GetApplicationState<Dictionary<string, string[]>>("UISiteRoles");

            string groupResponseError = string.Empty;
            if (siteRoles != null && siteRoles.Count > 0)
            
                //get the user roles from the AD service
                var groupsReq = new UserOperationsReference.GetUsersGroupsRequest();
                groupsReq.userName = username;
                //execute the service method for getting the roles/groups
                var groupsResponse = adClient.GetUsersGroups(groupsReq);
                //retrieve the results
                if (groupsResponse != null)
                
                    groupResponseError = groupsResponse.errorMessage;
                    var adRoles = groupsResponse.GetUsersGroupsResult;

                    if (adRoles != null)
                    
                        //loop through the roles returned from the server
                        foreach (var adRole in adRoles)
                        
                            //look for an admin role first
                            foreach (var roleName in siteRoles.Keys)
                            
                                var roles = siteRoles[roleName].ToList();
                                foreach (var role in roles)
                                
                                    if (adRole.Equals(role, StringComparison.InvariantCultureIgnoreCase))
                                    
                                        //we found a role, stop looking
                                        verifiedRoles += roleName + ";";
                                        break;
                                    
                                
                            
                        
                    
                
            
            if (String.IsNullOrEmpty(verifiedRoles))
            
                //no valid role we need to inform the user
                return View("Login", new object[]  "NO_ACCESS_ROLE", groupResponseError );
            

            if (verifiedRoles.EndsWith(";"))
                verifiedRoles = verifiedRoles.Remove(verifiedRoles.Length - 1, 1);

            //all is authenticated not build the auth ticket
            var authTicket = new FormsAuthenticationTicket(
            1,                             // version
            username,                      // user name
            DateTime.Now,                  // created
            DateTime.Now.AddMinutes(20),  // expires
            true,                    // persistent?
           verifiedRoles   // can be used to store roles
            );

            //encrypt the ticket before adding it to the http response
            string encryptedTicket = FormsAuthentication.Encrypt(authTicket);

            var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
            Response.Cookies.Add(authCookie);

            Session["UserRoles"] = verifiedRoles.Split(';');

            //redirect to calling page
            Response.Redirect(Session["LoginReturnURL"].ToString());
        
        else
        
            retVal = View("Login", new object[]  authResponse.AuthenticateResult.ToString(), authResponse.errorMessage );
        

        return retVal;
    

用户已通过身份验证,现在创建新身份

protected void FormsAuthentication_OnAuthenticate(Object sender,     FormsAuthenticationEventArgs e)
    
        if (FormsAuthentication.CookiesSupported == true)
        
            HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
            if (authCookie == null || authCookie.Value == "")
                return;

            FormsAuthenticationTicket authTicket = null;
            try
            
                authTicket = FormsAuthentication.Decrypt(authCookie.Value);
            
            catch
            
                return;
            

            // retrieve roles from UserData
            if (authTicket.UserData == null)
                return;

            //get username from ticket
            string username = authTicket.Name;

            Context.User = new GenericPrincipal(
                      new System.Security.Principal.GenericIdentity(username, "MyCustomAuthTypeName"), authTicket.UserData.Split(';'));
        
    

在我的 _Layout.cshtml 顶部的网站上,我有类似的内容

 
  bool authedUser = false;
  if (User != null && User.Identity.AuthenticationType == "MyCustomAuthTypeName" && User.Identity.IsAuthenticated)
   
      authedUser = true;
   
 

然后在体内

        @
         if (authedUser)
          
            <span id="loggedIn_userName">
                <label>User Logged In: </label>@User.Identity.Name.ToUpper()
            </span>
          
          else
          
            <span id="loggedIn_userName_none">

                <label>No User Logged In</label>
            </span>
          
        

【讨论】:

【参考方案4】:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using SISWEBBSI.Models.Model;
using SISWEBBSI.Models.Model.Entities;
using SISWEBBSI.Models.ViewModel;

namespace SISWEBBSI.Controllers.ActionFilter

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public sealed class RequerAutorizacao : ActionFilterAttribute
    
        public Grupo.Papeis[] Papeis =  ;
        public string ViewName  get; set; 
        public ViewDataDictionary ViewDataDictionary  get; set; 
        public AcessoNegadoViewModel AcessoNegadoViewModel  get; set; 

        public override void OnActionExecuting(ActionExecutingContext FilterContext)
        
            if (!FilterContext.HttpContext.User.Identity.IsAuthenticated)
            
                string UrlSucesso = FilterContext.HttpContext.Request.Url.AbsolutePath;
                string UrlRedirecionar = string.Format("?ReturnUrl=0", UrlSucesso);
                string UrlLogin = FormsAuthentication.LoginUrl + UrlRedirecionar;
                FilterContext.HttpContext.Response.Redirect(UrlLogin, true);
            
            else
            
                if (Papeis.Length > 0)
                
                    //Papel ADMINISTRADOR sempre terá acesso quando alguma restrição de papeis for colocada.
                    int NovoTamanho = Papeis.Count() + 1;
                    Array.Resize(ref Papeis, NovoTamanho);
                    Papeis[NovoTamanho - 1] = Grupo.Papeis.ADMINISTRADOR;
                    UsuarioModel Model = new UsuarioModel();
                    if (!Model.UsuarioExecutaPapel(FilterContext.HttpContext.User.Identity.Name, Papeis))
                    
                        ViewName = "AcessoNegado";
                        String Mensagem = "Você não possui privilégios suficientes para essa operação. Você deve estar nos grupos que possuem";
                        if(Papeis.Length == 1)
                        
                            Mensagem = Mensagem + " o papel: <BR/>";
                        
                        else if (Papeis.Length > 1)
                        
                            Mensagem = Mensagem + " os papéis: <BR/>";
                        

                        foreach (var papel in Papeis)
                        
                            Mensagem = Mensagem + papel.ToString() + "<br/>";
                        
                        AcessoNegadoViewModel = new AcessoNegadoViewModel();
                        AcessoNegadoViewModel.Mensagem = Mensagem;
                        ViewDataDictionary = new ViewDataDictionary(AcessoNegadoViewModel);
                        FilterContext.Result = new ViewResult  ViewName = ViewName, ViewData = ViewDataDictionary ;
                        return;
                    
                
            
        
    

【讨论】:

这篇文章需要解释为什么应该考虑它。【参考方案5】:

将您的用户添加到“角色中的用户”表中。在代码中使用存储过程“addusertorole”(类似的东西)添加到各种角色。您可以在“角色”表中非常简单地创建角色。

您要使用的表:User、UsersInRole、Roles

使用内置的存储过程来操作这些表。然后你所要做的就是添加属性。

例如,您可以在选择用户并将其添加到角色的视图上具有“管理员”属性。您可以使用存储过程将该用户添加到角色。

【讨论】:

我不关心 SQL 数据库。我可以自己处理。我只需要知道“在哪里”。 当你说“哪里”是指你把属性放在哪里?

以上是关于ASP.NET MVC 表单身份验证 + 授权属性 + 简单角色的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET MVC 表单身份验证域属性在 Firefox 中中断登录

使用成员资格提供程序的 ASP.NET MVC 4 Web API 身份验证

授权问题,FormsAuth 和 ASP.NET MVC

需要帮助了解 ASP .Net MVC 用户身份验证/授权

ASP.NET MVC 中的授权属性

ASP.NET MVC 真正使用表单身份验证注销