How to authenticate with certificates instead of p

2020-06-04 05:44发布

问题:

I have a small C# MVC5 app that I'm building and am ready to add the user security module to it. (previously I just created a session variable for testing roles) However, my security needs do not fit any of the prebuilt security modules that I've seen, i.e. SimpleMembership etc.

Here's a summary of my situation and needs:

  • No passwords -- username/password auth is not allowed

  • Users are authenticated at the web server level using a smartcard with client certificate -- I do not manage the server and will never see their card

  • IIS populates the Request.ClientCertificate and a few others, this is all I have access to

  • My app will have dedicated user accounts -- not using Windows auth etc -- so more like a username/password system without ever entering a username or password

  • The server is authenticating them to access the server by using some form of Windows auth with their smart card certificate, but I can't, I just have to accept the certificate loaded into the Request collection

  • User tables are stored in SQL Server (for now...)

  • Prefer not to use EntityFramework, since all app data comes from an Oracle DB and trying to get approval to move authentication/authorization tables into it and eliminate working from two DBs, and EF for Oracle doesn't work in our environment, so I'm using OleDb instead :(

What is the best way to go about implementing a scheme like this? What I've started doing is building three tables -- Users, Roles, and UserRoles -- and the Users table holds a copy of the (string) ClientCertificate. Users who come in would be authenticated into the app by pulling the Request.ClientCertificate and looking for a matching Users record, then getting the list of roles from UserRoles. A user object would be stored in the session containing the user and role info, and attributes would be used on controllers to control access by requiring certain roles.

(we have another app that uses this basic approach, but it is J2EE on Linux so I can't just reuse its code)

However, I've also started reading about IIdentity and IPrincipal but I'm not 100% clear on whether or not that is something I can use. Clearly I'd prefer to use a security model designed by experts. So should I build my authentication system using custom classes that inherit from IIdentity and IPrincipal? Or is there some other approach I should use?

It is entirely possible that something like SimpleMembership can be customized to meet my needs, but if so I'm not aware of it.

Thanks for the advice, much appreciated.

回答1:

You could use Microsoft's Identity for this kind of advanced scenarios as well. Identity so modular you could use any data storage with any schema you want. Password for authentication in Identity is not necessary you could implement your own scenario. consider this simple example:

// 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();
}

As you can see we authorized user and populated roles without any dependency to username, password and any data storage since we used our own user manager.

Some usage example:

[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
}

This is a simple example you could implement your custom scenario extend IIdenity as well. Read my other similar answers like this and this for more examples to see how you can do almost everything by Claims.

Also you could browse and download Token Based Authentication Sample repo as a simple working example.



回答2:

From the sounds of it the user is already authenticated, as such in your controller you can simply access the User property on Controller base class that your controllers inherit. This is property returns an IIPrincipal which has a property called Identity which returns an IIdentity.

So for example if you wanted to retrieve the currently logged in user's username in your controller you would access User.Identity.Name or if you wanted to ensure that the user was logged in you could check User.Identity.IsAuthenticated.

You absolutely should not be pulling anything out of the client certificates, essentially the server should have already authenticated the user, you don't care that it was done via a certificate rather than a username/password.