【问题标题】:How to authenticate with certificates instead of passwords?如何使用证书而不是密码进行身份验证?
【发布时间】:2015-08-15 20:44:04
【问题描述】:

我正在构建一个小型 C# MVC5 应用程序,并准备向其中添加用户安全模块。 (之前我刚刚创建了一个用于测试角色的会话变量)但是,我的安全需求不适合我见过的任何预构建的安全模块,即 SimpleMembership 等。

以下是我的情况和需求的总结:

  • 没有密码 -- 用户名/密码验证不允许

  • 使用带有客户端证书的智能卡在 Web 服务器级别对用户进行身份验证 -- 我不管理服务器并且永远不会看到他们的卡

  • IIS 填充 Request.ClientCertificate 和其他一些,这是我可以访问的全部内容

  • 我的应用将拥有专用的用户帐户——不使用 Windows 身份验证等——因此更像是一个用户名/密码系统,无需输入用户名或密码

  • 服务器 正在通过使用某种形式的 Windows 身份验证和他们的智能卡证书对他们进行身份验证以访问 服务器,但我不能,我只是必须接受加载到请求集合中的证书

  • 用户表存储在 SQL Server 中(目前...)

  • 不喜欢使用 EntityFramework,因为所有应用程序数据都来自 Oracle 数据库,并试图获得批准以将身份验证/授权表移入其中并消除从两个数据库中工作,而 EF for Oracle 不适用我们的环境,所以我改用 OleDb :(

实施这样的计划的最佳方法是什么?我开始做的是构建三个表——用户、角色和用户角色——用户表包含(字符串)ClientCertificate 的副本。进入的用户将通过拉取 Request.ClientCertificate 并查找匹配的用户记录,然后从 UserRoles 获取角色列表来验证进入应用程序。用户对象将存储在包含用户和角色信息的会话中,并且属性将在控制器上用于通过要求某些角色来控制访问。

(我们有另一个应用程序使用这种基本方法,但它是 Linux 上的 J2EE,所以我不能只重用它的代码)

但是,我也开始阅读有关 IIdentity 和 IPrincipal 的信息,但我并不确定这是否是我可以使用的东西。显然,我更喜欢使用专家设计的安全模型。那么我应该使用继承自 IIdentity 和 IPrincipal 的自定义类来构建我的身份验证系统吗?还是我应该使用其他方法?

完全有可能定制像 SimpleMembership 这样的东西来满足我的需求,但如果是这样,我不知道。

感谢您的建议,非常感谢。

【问题讨论】:

    标签: c# asp.net asp.net-mvc authentication


    【解决方案1】:

    您也可以将Microsoft's Identity 用于此类高级场景。身份如此模块化,您可以将任何数据存储与您想要的任何模式一起使用。不需要身份验证密码,您可以实现自己的场景。考虑这个简单的例子:

    // imaging this action is called after user authorized by remote server
    public ActionResoult Login()
    {
        // imaging this method gets authorized certificate string 
        // from Request.ClientCertificate or even a remote server
        var userCer=_certificateManager.GetCertificateString();
    
        // you have own user manager which returns user by certificate string
        var user=_myUserManager.GetUserByCertificate(userCer);
    
        if(user!=null)         
        {
            // user is valid, going to authenticate user for my App
            var ident = new ClaimsIdentity(
                new[] 
                {
                    // since userCer is unique for each user we could easily
                    // use it as a claim. If not use user table ID 
                    new Claim("Certificate", userCer),
    
                    // adding following 2 claim just for supporting default antiforgery provider
                    new Claim(ClaimTypes.NameIdentifier, userCer),
                    new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
    
                    // an optional claim you could omit this 
                    new Claim(ClaimTypes.Name, user.Name),
    
                    // populate assigned user's role form your DB 
                    // and add each one as a claim  
                    new Claim(ClaimTypes.Role, user.Roles[0].Name),
                    new Claim(ClaimTypes.Role, user.Roles[1].Name),
                    // and so on
                },
                DefaultAuthenticationTypes.ApplicationCookie);
    
            // Identity is sign in user based on claim don't matter 
            // how you generated it Identity take care of it
            HttpContext.GetOwinContext().Authentication.SignIn(
                new AuthenticationProperties { IsPersistent = false }, ident);
    
            // auth is succeed, without needing any password just claim based 
            return RedirectToAction("MyAction"); 
        }
        // invalid certificate  
        ModelState.AddModelError("", "We could not authorize you :(");
        return View();
    }
    

    正如您所见,由于我们使用了自己的用户管理器,因此我们授权用户和填充角色不依赖于用户名、密码和任何数据存储。

    一些用法示例:

    [Authorize]
    public ActionResult Foo()
    {
    }
    
    // since we injected user roles to Identity we could do this as well
    [Authorize(Roles="admin")]
    public ActionResult Foo()
    {
        // since we injected our authentication mechanism to Identity pipeline 
        // we have access current user principal by calling also
        // HttpContext.User
    }
    

    这是一个简单的示例,您也可以实现自定义场景扩展 IIdenity。阅读我的其他类似答案,例如 thisthis 以获取更多示例,了解您如何通过 Claims 完成几乎所有事情。

    您也可以浏览和下载Token Based Authentication Sample repo 作为一个简单的工作示例。

    【讨论】:

    • 这非常有用,谢谢。我花了一个下午的时间阅读 IIdentity 和 IPrincipal 并开始自己动手,但还没有看到像这样真正的端到端示例可以满足我的需求。我将在接下来的几天里深入研究它,看看它是如何工作的,但这看起来是一个好的开始。
    • 顺便说一句,要使用 Authorize 属性,您必须在 web.config 中启用表单身份验证对吗?我假设未经授权的用户将自动重定向到 web.config 中指定的登录操作。另外,由于我不熟悉 .User 属性,它如何从一个页面浏览量持续到另一个页面浏览量?即,一旦我对用户进行身份验证并且他们单击链接以发起新的 HTTP 请求,授权如何保持?在会话中?它是无缝的还是我需要做其他未显示的事情?我想避免对每个页面进行身份验证的数据库命中。谢谢!
    • 您不需要使用 old from 身份验证。您只需要为您的应用程序实现身份框架。如果您不知道如何阅读this,但对于您的场景,您甚至不需要安装 EF。因此需要稍作修改。
    • 因为您将配置基于身份的 cookie。授权是持久的,你不需要做任何身份来照顾他们。在代码的每一部分,您都可以通过调用HttpContext.Current.User 访问当前用户
    • 谢谢。你的其他链接看起来也很棒。我今天抽出一点时间,创建了声明,并且 Authorize 属性检查正确。但是,它默认重定向到 /Account/Login。我在 Startup.Auth.cs 中发现了这一点,并将其更改为重定向到 Home 控制器上的 ActionResult NotAuthorized() ,该控制器仅显示一个通用的“未授权”视图,其中包含有关如何根据我们的策略请求访问的说明。有没有更好的处理方法?
    【解决方案2】:

    从它的声音来看,用户已经过身份验证,因此在您的控制器中,您可以简单地访问您的控制器继承的 Controller 基类上的 User 属性。这是属性返回一个IIPrincipal,它有一个名为Identity 的属性,它返回一个IIdentity

    例如,如果您想在控制器中检索当前登录用户的用户名,您可以访问User.Identity.Name,或者如果您想确保用户已登录,您可以检查User.Identity.IsAuthenticated

    您绝对不应该从客户端证书中提取任何内容,本质上服务器应该已经对用户进行了身份验证,您不在乎它是通过证书而不是用户名/密码完成的。

    【讨论】:

    • 好吧,服务器对用户进行了身份验证,但它是一个共享服务器,我的应用程序只是其中之一,并且用户尚未通过我的应用程序的身份验证。不确定这是否有意义。所以我没有我知道的可用控制器属性。另外,我正在使用 VS2013 中的本地服务器进行开发,因此在我开发时它不会提取证书,因此我必须在一些证书字符串中填充一些我可以用来查找各种用户的证书字符串。即为开发目的插入一些“CN = ...”字符串,但从产品中的 Request.ClientCertificate 中提取“CN = ...”。有意义吗?
    • 无论用户是否经过身份验证,控制器属性都在那里。如果用户在“服务器上”通过身份验证,那么他们应该对您的应用程序进行身份验证,我相信,您需要测试并确定。这种身份验证(基本上是 Windows 身份验证)完全发生在您的应用程序之外。您需要在您的应用程序中做的是授权,即应用角色并确定用户可以做什么和不能做什么。你绝对不应该对证书做任何事情。
    猜你喜欢
    • 1970-01-01
    • 2023-03-29
    • 2021-08-09
    • 2020-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-28
    相关资源
    最近更新 更多