首页 > 解决方案 > 是否可以将 ASP.NET 成员资格与在 ASP.NET Identity 2.0 中创建的表一起使用以进行身份​​验证?

问题描述

我有一个基于 MVC4 的门户网站,它只使用成员身份验证用户(不创建新用户,不验证密码)。

以下是用于对用户进行身份验证的表的屏幕截图:

在此处输入图像描述

这是用户尝试进行身份验证时触发的操作方法:

    [HttpPost]
    public ActionResult Login(string username, string password, string[] rememberMe, string returnUrl)
    {
        if(Membership.ValidateUser(username, password))
        {
            if (rememberMe != null)
            {
                FormsAuthentication.SetAuthCookie(username, true);
            }
            Response.Redirect(returnUrl);
        }
        ViewBag.ErrorMessage = "wrong credentials";
        return View();
    }
    

这是网络配置:

<membership defaultProvider="AspNetSqlMembershipProvider" userIsOnlineTimeWindow="15">
  <providers>
    <clear />
    <add name="AspNetSqlMembershipProvider" 
         connectionStringName="SiteSqlServer" 
         enablePasswordRetrieval="false" 
         enablePasswordReset="true" 
         requiresQuestionAndAnswer="false"
         applicationName="/" 
         requiresUniqueEmail="true" 
         passwordFormat="Hashed" 
         maxInvalidPasswordAttempts="8" 
         minRequiredPasswordLength="4" 
         minRequiredNonalphanumericCharacters="0" 
         passwordAttemptWindow="10" 
         passwordStrengthRegularExpression="" 
         type="System.Web.Security.SqlMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d43e4e" />
  </providers>
</membership>

我还有一个由 asp.net identity 2.0 创建的新数据库:

在此处输入图像描述

我的任务是对新表进行用户身份验证(即我需要用于身份验证的成员资格,由 asp.net identity 2.0 创建的表)。

所以,为了这个目的,我想我需要自定义成员来阅读新表。

我的问题是可以对成员资格进行更改,使其读取 asp.net identity 2.0 进行身份验证?

标签: c#asp.net.netasp.net-mvcasp.net-identity

解决方案


是的,这是可能的,但这是解决方案将取决于您当前的具体实施的问题之一。立即想到三个解决方案:

  1. 用对新系统的调用替换所有身份验证逻辑从
    长远来看,这更易于管理,因为您只需在一个地方维护身份验证逻辑。

    • 如果您有一个可以通过 HTTP 访问的已部署 API,这是最简单的解决方案,但它可以通过包含您的 dll 来工作,只要它们是针对兼容的 .Net 版本编译的。
    • 如果您有其他应用程序(桌面/Web/移动)也使用新 API,则此方法特别有效,此旧站点将成为新站点的客户端。

    这是最低代码解决方案,也是唯一一个我们不必担心正确散列密码以进行比较的解决方案。

    如果对方系统是 Online API,那么你可以使用 HttpClient 简单地调用对方站点进行身份验证,你如何做到这一点取决于 API 支持什么类型的身份验证协议

    使用 System.Web.Security;
    
    [HttpPost]
    public async Task<ActionResult> Login(string username, string password, string[] rememberMe, string returnUrl)
    {
        如果(等待验证用户(用户名,密码))
        {
            如果(记住我!= null)
            {
                FormsAuthentication.SetAuthCookie(用户名,真);
            }
            Response.Redirect(returnUrl);
        }
        ViewBag.ErrorMessage = "错误的凭据";
        返回视图();
    }
    
    公共异步任务<bool> 登录(字符串用户名,字符串密码)
    {
        var client = new System.Net.Http.HttpClient();
        client.DefaultRequestHeaders.Clear();
        client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/x-www-form-urlencoded");
        字典<string, string> data = new Dictionary<string, string>();
        data.Add("grant_type", "password");
        data.Add("用户名", 用户名);
        data.Add("密码", 密码);
    
        // 示例,将其替换为您自己的。
        string apiAuthUrl = "http://localhost:3000/token";
    
        var response = await client.PostAsync(apiAuthUrl, new System.Net.Http.FormUrlEncodedContent(data));
        返回响应。IsSuccessStatusCode;
    }
    

    如果其他系统不是作为 API 发布的,而是作为桌面风格的应用程序,那么您可以通过直接引用和调用 dll 中的 Auth 方法来执行类似的操作。

  2. 创建一个自定义成员资格提供程序
    这是一个基于代码的解决方案,类似于它在 2.0 中的工作方式,但涉及更多一点。

    • Scott Gu 已发布到源代码,该源代码曾经是这些类型活动的有用参考:内置 ASP.NET 2.0 提供程序的源代码现在可供下载

    • 但是链接似乎被破坏了!

    • MS Docs 仍然有一些阅读,但没有来源:实现成员资格提供程序

    • 以下文章来自第一种原则方法,您的表已经创建,因此只需专注于访问数据,而不是创建新表

      (MVC) 自定义成员资格提供程序

      不必实现提供程序的所有方面,只需实现您的站点代码将调用的那些方法。

      这个解决方案很好,因为您不必更改任何现有代码,只需更改新代码和更改 web.config

      兴趣点

      更改 web.config 引用以使用您的自定义提供程序,替换MyNamespace.Providers为您的实际命名空间!:

      <membership defaultProvider="CustomMembershipProvider" userIsOnlineTimeWindow="15">
        <providers>
          <clear/>
          <add name="CustomMembershipProvider" 
              type="MyNamespace.Providers.CustomMembershipProvider"
              connectionStringName="SiteSqlServer"
              enablePasswordRetrieval="false"
              enablePasswordReset="true"
              requiresQuestionAndAnswer="false"
              requiresUniqueEmail="true"
              maxInvalidPasswordAttempts="8"
              minRequiredPasswordLength="4"
              minRequiredNonalphanumericCharacters="0"
              passwordAttemptWindow="10"
              applicationName="DotNetNuke" />
        </providers>
      </membership>
      

      然后实现成员资格提供者类:

      使用 System.Web.Security;
      
      公共类 CustomMembershipProvider : MembershipProvider
      {
          公共覆盖 MembershipUser CreateUser(字符串用户名,字符串密码,
                 字符串电子邮件,字符串密码问题,字符串密码答案,
                 bool isApproved, object providerUserKey, out MembershipCreateStatus status)
          {
              /// 我们不会在这个站点中创建新用户!
              抛出新的 NotImplementedException();             
          }
      
          public override MembershipUser GetUser(string username, bool userIsOnline)
          {
              var user = /* 你的用户回购查询 */;
      
              如果(用户!= null)
              {
                  MembershipUser memUser = new MembershipUser("CustomMembershipProvider",
                                                 用户名,user.UserID,user.UserEmailAddress,
                                                 字符串.空,字符串.空,
                                                 真,假,日期时间.MinValue,
                                                 日期时间.MinValue,
                                                 日期时间.MinValue,
                                                 日期时间。现在,日期时间。现在);
                  返回内存用户;
              }
              返回空值;
          }
      
          public override bool ValidateUser(字符串用户名,字符串密码)
          {
              // 这很大程度上取决于新系统中密码的加密方式
              // 假设是一个简单的 MD5 Hash,我们只需要创建相同的 Hash              
              字符串 sha1Pswd = GetMD5Hash(密码);
      
              // TODO: 现在使用这个哈希来验证用户名和密码是否与你的 repo 中的用户匹配
              var user = /*查找与用户名和密码匹配的用户。*/;
              如果(用户!= null)
                  返回真;
              返回假;
          }
      
          公共覆盖 int MinRequiredPasswordLength
          {
              得到 { 返回 4; }
          }
      
          /// <summary> 简单的 MD5 哈希算法,您需要将其与其他系统中使用的进程进行匹配 </summary>
          公共静态字符串 GetMD5Hash(字符串值)
          {
              MD5 md5Hasher = MD5.Create();
              byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(value));
              StringBuilder sBuilder = new StringBuilder();
              for (int i = 0; i < data.Length; i++)
              {
                  sBuilder.Append(data[i].ToString("x2"));
              }
      
              返回 sBuilder.ToString();
          }
      }
      
      
      
  3. 自定义 AspNetSqlMembershipProvider 使用的存储过程
    取决于您的优势,这可能更简单,并且是一个选项,因为 MVC 站点已经配置了此提供程序,我们需要做的就是修改提供程序用于执行身份验证的 SQL 存储过程要求。

    • 以下是关于如何实现提供程序的好读物

      在 MVC5 中使用 AspNetSqlMembershipProvider
      配置 Web 应用程序以利用 ASP.NET 应用程序服务数据库

      • 正如在前一个选项中强调的那样,这只有在新站点使用相同的加密方法时才有效,它可能还需要使用在 web.config 中设置的相同机器密钥。

      您可以设置一个空白数据库并使用 aspnet_regsql 对其进行配置以查看存储过程(它们不会在您的新数据库中!)。

      • 这将变得有点或反复试验来识别将需要的过程,我怀疑你只需要GetUserByName,下面的屏幕截图显示了一些其他存储过程,命名约定使它很容易匹配会员API方法:

        ASPNet 会员存储过程

        /****** Object:  StoredProcedure [dbo].[aspnet_Membership_GetUserByName]    Script Date: 25/06/2020 11:19:04 AM ******/
        SET ANSI_NULLS ON
        GO
        SET QUOTED_IDENTIFIER OFF
        GO
        CREATE OR ALTER PROCEDURE [dbo].[aspnet_Membership_GetUserByName]
            @ApplicationName      nvarchar(256),
            @UserName             nvarchar(256),
            @CurrentTimeUtc       datetime,
            @UpdateLastActivity   bit = 0
        AS
        BEGIN
            DECLARE @UserId uniqueidentifier
      
            IF (@UpdateLastActivity = 1)
            BEGIN
                -- select user ID from AspnetUsers table
                -- Ignore ApplicationIDs here, assume this is a single Application schema
                SELECT TOP 1 @UserId = u.Id
                FROM    dbo.AspNetUsers u
                WHERE   LOWER(@UserName) = LOWER(u.UserName)
      
                IF (@@ROWCOUNT = 0) -- Username not found
                    RETURN -1
      
                -- We don't have 'Activity' per-se, instead we reset the AccessFailedCount to zero
                -- Your implementation might be different, so think it through :)
                UPDATE   dbo.AspNetUsers
                SET      AccessFailedCount = 0
                WHERE    Id = @UserId
      
                -- Your Schema might be different, here we just map back to the old Schema in the projected response
                -- Make sure the data types match in your response, here we are injected GetDate() for most dates by default
                -- NOTE: LockOutEnabled DOES NOT mean the user is locked out, only that lockout logic should be evaluated
                SELECT Email, '' as PasswordQuestion, '' as Comment, Cast(1 as BIT) as IsApproved,
                        CAST(null as datetime) as CreateDate, GetDate() as LastLoginDate, GetDate() as LastActivityDate, GetDate() as LastPasswordChangedDate,
                        Id as UserId, CASE WHEN LockoutEnabled = 1 AND LockoutEndDateUtc IS NOT NULL THEN 1 ELSE 0 END as IsLockedOut, LockoutEndDateUtc as LastLockoutDate
                FROM    dbo.AspNetUsers
                WHERE  Id = @UserId
            END
            ELSE
            BEGIN
                -- Your Schema might be different, here we just map back to the old Schema in the projected response
                -- Make sure the data types match in your response, here we are injected GetDate() for most dates by default
                -- NOTE: LockOutEnabled DOES NOT mean the user is locked out, only that lockout logic should be evaluated
                SELECT TOP 1 Email, '' as PasswordQuestion, '' as Comment, Cast(1 as BIT) as IsApproved,
                        CAST(null as datetime) as CreateDate, GetDate() as LastLoginDate, GetDate() as LastActivityDate, GetDate() as LastPasswordChangedDate,
                        Id as UserId, CASE WHEN LockoutEnabled = 1 AND LockoutEndDateUtc IS NOT NULL THEN 1 ELSE 0 END as IsLockedOut, LockoutEndDateUtc as LastLockoutDate
                FROM    dbo.AspNetUsers
                WHERE   LOWER(@UserName) = LOWER(UserName)
      
                IF (@@ROWCOUNT = 0) -- Username not found
                    RETURN -1
            END
      
            RETURN 0
        END
      

      您可能还需要实现部分或全部视图,但这在很大程度上取决于您的 MVC 站点实际使用的 Membership API 中的方法。


推荐阅读