I'm writing my custom authentication and authorization system, in my Web API 2 project.

There's a TokenController which has a POST method to receive user credentials, validate and generate an authentication/authorization token.

Later, if the user wants to consume any of my controllers, they simply pass the token in query string, like: ?token=xcd.

I've set my custom authorization attribute on my controllers which retrieve this token from url and test it if the same in my database if so, he has the access to the controller.

However, setting the principal/identity inside TokenController does not make it ready to be used with Thread.CurrentPrinciple inside another controllers.

I've tried all these:

Thread.CurrentPrincipal = principal;
this.RequestContext.Principal = principal;
HttpContext.Current.User = principal;

When I try to retrieve the current principal/identity at the stage of authorization, there's no user anymore (User.Identity.Name is empty).

Where do I have to set the principal to make it available during the request life-time?

有帮助吗?

解决方案

The problem here is that:

  1. You're not aware of the controllers lifecycle. YES, they DO have one, and it bound to the request.
  2. You're using a custom authorization/authentication implementation

For the request (and controller) lifecycle, take a look into this sample:

Request -> new Controller() -> controller.Action() -> Response -> Dispose()

This being said, it's no wonder that setting the Thread.CurrentPrincipal or even the HttpContext.Current.User inside your TokenController won't work: the request already ended and the thread is now disposed. When you request another action/method, it's another entire context, with new threads and a new User/Identity/Principal object!

Imagine if you have 10 users. Which principal/identity should be returned by User.Identity if it was not bound to the current request's context? That's why...

So, you're better shot here would be to implement an ActionFilter or an AuthenticationFilter to be called BEFORE your controller, within the same request context, to correctly set the identity/principal so it's available for any controller, anytime.

We've created a custom auth framework here once. It's not that hard. Here follows some samples:

public MyCustomAuthenticationFilter : FilterAttribute, IActionFilter
{
   public void OnActionExecuted(ActionExecutedContext filterContext)
   {
      // You may set the user here.
   }

   public void OnActionExecuting(ActionExecutingContext filterContext)
   {
      // This will be called after the action has executed. Not that useful right now...
   }

   public class Override : FilterAttribute, System.Web.Mvc.Filters.IOverrideFilter
   { 
      public Type FiltersToOverride { get { return typeof(MyCustomAuthenticationFilter); } }
   }
}

Than, you may simply register it as a global filter:

GlobalFilters.Filters.Add(new MyCustomAuthenticationFilter());

Wherever you DON'T want it to execute, you may use a filter override:

[MyCustomAuthenticationFilter.Override]
public ActionResult Something()

But you'll need your custom filter provider, which can resolve all these (and other) overrides, similar to:

public class MyCustomFilterProvider : System.Web.Mvc.IFilterProvider
{
    public IEnumerable<System.Web.Mvc.Filter> GetFilters(System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ActionDescriptor actionDescriptor)
    {
        var _globalFilters = System.Web.Mvc.GlobalFilters.Filters;
        var _controllerFilters = controllerContext.Controller.GetType().GetCustomAttributes(true).OfType<FilterAttribute>();
        var _actionFilters = actionDescriptor.GetCustomAttributes(true).OfType<FilterAttribute>();

        var _all = _globalFilters.Union(_controllerFilters).Union(_actionFilters).ToList();
        var _overrides = _all.Where(_f => _f.Instance.GetType().GetInterfaces().Any(_i => _i.Equals(typeof(System.Web.Mvc.Filters.IOverrideFilter)))).ToArray();

        foreach(var _override in _overrides)
        {
            var _found = _all.Where(_f => _f.Instance.GetType().Equals(((System.Web.Mvc.Filters.IOverrideFilter)_override.Instance).FiltersToOverride)).ToArray();
            foreach (var _item in _found) _all.Remove(_item);
        }

        return _all.Except(_overrides);
    }
}

This provider should be registered to work (of course), so:

FilterProviders.Providers.Add(new MyCustomFilterProvider());

And I think that's all. Should not get in your way anymore.

You may, however, avoid ALL this and simply use the new AspNet.Identity/Katana/Owin (you don't have to use EF if that's the issue) and store everything you need as claims, making it really useful and easy. It works with bearer/token authentication/authorization too!

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top