如何使用证书进行身份验证而不是密码?

9
我正在构建一个小的C# MVC5应用程序,准备为其添加用户安全模块(先前我只创建了一个会话变量来测试角色)。然而,我的安全需求不符合我所见过的任何预建安全模块,例如SimpleMembership等。
以下是我的情况和需求摘要:
- 不需要密码--不允许使用用户名/密码身份验证 - 用户在Web服务器级别上使用带有客户端证书的智能卡进行身份验证--我不管理服务器,也永远看不到他们的卡 - IIS填充Request.ClientCertificate和其他一些内容,这是我拥有的所有内容 - 我的应用程序将具有专用用户帐户--不使用Windows身份验证等--因此更像是一种用户名/密码系统,而不需要输入用户名或密码 - 服务器正在使用某种形式的Windows身份验证与他们的智能卡证书对其进行身份验证,但我不能,我必须接受加载到请求集合中的证书 - 用户表存储在SQL Server中(目前...) - 由于所有应用程序数据都来自Oracle DB,并尝试获得批准将身份验证/授权表移动到其中并消除从两个数据库工作,因此最好不要使用EntityFramework,而且我们的环境中的Oracle EF不起作用,所以我改用OleDb :(
如何实施这样的方案是最好的方法?我已经开始构建三个表--用户、角色和用户角色--用户表保存(字符串)ClientCertificate的副本。进来的用户将通过提取Request.ClientCertificate进行身份验证,并查找匹配的用户记录,然后从UserRoles获取角色列表来进入应用程序。一个用户对象将存储在会话中,包含用户和角色信息,并且控制访问的控制器将使用属性要求特定角色。
(我们有另一个使用这种基本方法的应用程序,但它是Linux上的J2EE,所以我不能只重用其代码)
然而,我也开始阅读关于IIdentity和IPrincipal的内容,但我不确定是否可以使用。显然,我更喜欢使用专家设计的安全模型。那么,我应该使用从IIdentity和IPrincipal继承的自定义类来构建我的身份验证系统吗?还是应该使用其他方法?
完全有可能像SimpleMembership这样的东西可以定制以满足我的需求,但如果是这样,我不知道。
感谢您的建议,非常感谢。
2个回答

5
您可以使用Microsoft的身份验证来处理这种高级场景。身份验证非常模块化,您可以使用任何数据存储和所需架构。在身份验证中,密码不是必需的,您可以实现自己的方案。考虑以下简单示例:
// 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仓库,作为一个简单的工作示例。

顺便问一下,使用Authorize属性需要在web.config中启用表单身份验证,对吗?我假设未经授权的用户会自动重定向到web.config中指定的登录操作。另外,由于我不熟悉.User属性,它是如何从一个页面视图持久化到另一个页面视图的呢?即,一旦我认证了用户并且他们点击一个链接发起一个新的HTTP请求,授权是如何持久化的?在会话中吗?它是无缝的还是我需要执行其他未显示的操作?我想避免为每个页面进行身份验证而产生数据库开销。谢谢! - DaveCan
实际上,我说得太早了:Authorize属性并没有允许用户通过,即使在我创建了如上所示的ClaimsIdentity并将其传递给SignIn()方法之后,它仍然拒绝访问。通过调试器进行步进,似乎控制器上的.User方法甚至在调用SignIn()之后立即看不到这些声明。不确定我做错了什么,但可能是我犯了一个常见的错误。我还没有访问数据库,只是进行了一个硬编码测试,几乎与您上面展示的完全相同。感谢任何帮助/建议,非常感谢。 - DaveCan
澄清一下,我在有或没有Startup.cs文件的情况下都会出现错误。Startup.cs文件只包含一个Configuration(IAppBuilder app)方法,其中只有一行代码ConfigureAuth(app)。不确定我错过了什么....谢谢。 - DaveCan
我想我搞定了!我查看了你的web.config文件,找到了owin:AppStartup键,并在我的项目中添加了一个,现在它可以工作了!我仍然需要解决应用程序内的实现细节,但它可以正确地创建身份、添加证书名称和角色的声明,并使用Authorize属性锁定控制器。所以我认为现在我已经有足够的信息了,但如果您不介意的话,我可能会稍后向您提出一两个问题。我接受这个答案,因为它非常出色,您做得很棒,谢谢! :) - DaveCan
我很高兴它能帮到你。随时提出任何问题。如果我能帮到你,我会非常高兴的。 ;) - Sam FarajpourGhamari
显示剩余7条评论

0
听起来用户已经通过身份验证,因此在您的控制器中,您可以简单地访问控制器基类上的User属性,而这些控制器继承了该属性。该属性返回一个IIPrincipal,其中有一个名为Identity的属性,它返回一个IIdentity。
例如,如果您想要在控制器中检索当前登录用户的用户名,您将访问User.Identity.Name,或者如果您想确保用户已登录,则可以检查User.Identity.IsAuthenticated。
您绝对不应该从客户端证书中提取任何内容,实际上服务器应该已经验证了用户,您不需要关心是通过证书还是用户名/密码进行验证。

服务器已验证用户身份,但这是一个共享服务器,我的应用程序只是其中之一,并且用户尚未被授权进入我的应用程序。不确定这是否有意义。因此,我没有可用的控制器属性。此外,我正在使用VS2013中的本地服务器进行开发,因此在开发时它不会拉取证书,因此我需要添加一些证书字符串来查找各种用户。即为开发目的插入一些 "CN=..." 字符串,但在生产中从 Request.ClientCertificate 中提取 "CN=..."。有意义吗? - DaveCan
无论用户是否经过身份验证,控制器属性都存在。如果用户在服务器上通过身份验证,则应该在您的应用程序中显示为已经过身份验证,我认为您需要测试并确定。这种身份验证(基本上是Windows身份验证)完全发生在您的应用程序之外。您需要在应用程序中进行授权,即应用角色并确定用户可以做什么和不能做什么。您绝对不应该对证书进行任何操作。 - Ben Robinson

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