asp.net MVC authentication with Shibboleth

2019-02-02 18:31发布

问题:

Shibboleth is a SSO Authentication that is added to IIS as a "plugin". After a user has done a Login there are Headers showing the Shibboleth Session: ShibSessionID ShibIdentityProvider eppn affiliation entitlement unscopedaffiliation ...more

So i can extract username and roles from the Headers. so far so fine.

Question: How can I implement a handler that does read the headers and set the status that a user is authorized? Idea is to use the [Authorize] Attribute and the Method Roles.IsUserInRole. All from the Headers, no Database, no User Management.

Update

Implementation According to the Answer from @Pharylon

In this Update there is nothing new, just a help for the copy&past friends. Of course you have to adjust the properties and Header fieldnames according to your Shibboleth Setup.

File: ShibbolethPrincipal.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal; //GenericPrincipal

namespace Shibboleth
{
    public class ShibbolethPrincipal : GenericPrincipal
    {
        public string username
        {
            get { return this.Identity.Name.Replace("@ksz.ch", ""); }
        }

        public string firstname
        {
            get { return HttpContext.Current.Request.Headers["givenName"]; }
        }

        public string lastname
        {
            get { return HttpContext.Current.Request.Headers["surname"]; }
        }

        public string phone
        {
            get { return HttpContext.Current.Request.Headers["telephoneNumber"]; }
        }

        public string mobile
        {
            get { return HttpContext.Current.Request.Headers["mobile"]; }
        }

        public string entitlement
        {
            get { return HttpContext.Current.Request.Headers["eduzgEntitlement"]; }            
        }

        public string homeOrganization
        {
            get { return HttpContext.Current.Request.Headers["homeOrganization"]; }            
        }

        public DateTime birthday
        {
            get
            {
                DateTime dtHappy = DateTime.MinValue;
                try
                {
                    dtHappy = DateTime.Parse(HttpContext.Current.Request.Headers["dateOfBirth"]);
                }
                finally
                {                    

                }

                return dtHappy;
            }
            set {}
        }

        public ShibbolethPrincipal()
            : base(new GenericIdentity(GetUserIdentityFromHeaders()), GetRolesFromHeader())
        {
        }

        public static string GetUserIdentityFromHeaders()
        {            
            //return HttpContext.Current.Request.Headers["eppn"];            
            return HttpContext.Current.Request.Headers["principalName"];                        
        }

        public static string[] GetRolesFromHeader()
        {
            string[] roles = null;
            //string rolesheader = HttpContext.Current.Request.Headers["affiliation"];
            string rolesheader = HttpContext.Current.Request.Headers["eduzgEntitlement"];
            if (rolesheader != null)
            {
                roles = rolesheader.Split(';');
            }
            return roles; 
        }
    }
}

File: ShibbolethController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Shibboleth
{
    public class ShibbolethController : Controller
    {
        protected new ShibbolethPrincipal User
        {
            get
            {
                return (base.User as ShibbolethPrincipal) ?? null; //CustomPrincipal.GetUnauthorizedPrincipal();
            }
        }
    }
}

File: Global.asax

void Application_PostAuthenticateRequest(object sender, EventArgs e)
        {
            var ctx = HttpContext.Current;

            var principal = new ShibbolethPrincipal();
            HttpContext.Current.User = principal;            
        }

Using examples:

 namespace itservices.Controllers
    {
        [Authorize] //examples : [Authorize(Roles="Administrators")], [Authorize(Users="Alice,Bob")]
        public class PasswordMailController : ShibbolethController
        {

    if(User.IsInRole("staff"))
    {

回答1:

You'll want to create a method in Global.asax.cs that has the following signature

protected void Application_PostAuthenticateRequest()
{
    //Your code here.
}

This will be called automatically before almost anything else is done (MVC will call this method if it exists, you don't have to "turn it on" anywhere), and this is where you need to set the Principal. For instance, let's assume you have a header called RolesHeader that has a comma separated value of roles and another header called UserId that has (duh) the user ID.

Your code, without any error handling, might look something like:

protected void Application_PostAuthenticateRequest()
{
    var rolesheader = Context.Request.Headers["RolesHeader"];
    var userId = Context.Request.Headers["UserId"];
    var roles = rolesheader.Split(',');
    var principal = new GenericPrincipal(new GenericIdentity(userId), roles);
    Context.User = principal;
}

It's the Principal/Identity that the [Authorize] attribute uses, so setting it here at the beginning of the request lifecycle means the [Authorize] attribute will work correctly.

The rest of this is optional, but I recommend it:

I like to create my own custom classes that implement IPrincipal and IIdentity instead of using the GenericPrincipal and GenericIdentity, so I can stuff more user information in it. My custom Principal and Identity objects then have much more rich information, such as branch numbers or email addresses or whatever.

Then, I create a Controller called BaseController that has the following

protected new CustomPrincipal User
{
    get
    {
        return (base.User as CustomPrincipal) ?? CustomPrincipal.GetUnauthorizedPrincipal();
    }
}

This allows me to access all my rich, custom Principal data instead of just what's defined in IPrincipal. All of my real controllers then inherit from BaseController instead of directly from Controller.

Obviously, when using a custom Principal like this, in the Application_PostAuthenticateRequest() method, you'd set the Context.User to be your CustomPrincipal instead of a GenericPrincipal.