如何在自定义WebAPI HttpMessageHandler中安全地设置用户主体?

时间:2022-01-30 00:54:28

For basic authentication I have implemented a custom HttpMessageHandler based on the example shown in Darin Dimitrov's answer here: https://*.com/a/11536349/270591

对于基本的身份验证,我已经实现了一个基于Darin Dimitrov的示例的自定义HttpMessageHandler: https://*.com/a/11536349/270591

The code creates an instance principal of type GenericPrincipal with user name and roles and then sets this principal to the current principal of the thread:

代码创建具有用户名和角色的GenericPrincipal类型的实例主体,然后将该主体设置为线程的当前主体:

Thread.CurrentPrincipal = principal;

Later in a ApiController method the principal can be read by accessing the controllers User property:

在ApiController方法中,可以通过访问controller用户属性来读取主体:

public class ValuesController : ApiController
{
    public void Post(TestModel model)
    {
        var user = User; // this should be the principal set in the handler
        //...
    }
}

This seemed to work fine until I recently added a custom MediaTypeFormatter that uses the Task library like so:

直到最近我添加了一个自定义的MediaTypeFormatter,它使用的任务库如下所示:

public override Task<object> ReadFromStreamAsync(Type type, Stream readStream,
    HttpContent content, IFormatterLogger formatterLogger)
{
    var task = Task.Factory.StartNew(() =>
    {
        // some formatting happens and finally a TestModel is returned,
        // simulated here by just an empty model
        return (object)new TestModel();
    });
    return task;
}

(I have this approach to start a task with Task.Factory.StartNew in ReadFromStreamAsync from some sample code. Is it wrong and maybe the only reason for the problem?)

(我用这种方法来启动task . factory。从一些示例代码开始,从ReadFromStreamAsync开始。这是错的吗?也许是问题的唯一原因?

Now, "sometimes" - and for me it appears to be random - the User principal in the controller method isn't the principal anymore I've set in the MessageHandler, i.e. user name, Authenticated flag and roles are all lost. The reason seems to be that the custom MediaTypeFormatter causes a change of the thread between MessageHandler and controller method. I've confirmed this by comparing the values of Thread.CurrentThread.ManagedThreadId in the MessageHandler and in the controller method. "Sometimes" they are different and then the principal is "lost".

现在,“有时”——对我来说似乎是随机的——controller方法中的用户主体不再是我在MessageHandler中设置的主体,即用户名、已验证标志和角色都将丢失。原因似乎是自定义MediaTypeFormatter导致MessageHandler和controller方法之间的线程更改。通过比较Thread.CurrentThread的值,我已经证实了这一点。ManagedThreadId在MessageHandler和controller方法中使用。“有时”他们是不同的,然后校长是“迷失”。

I've looked now for an alternative to setting Thread.CurrentPrincipal to somehow transfer the principal safely from the custom MessageHandler to the controller method and in this blog post request properties are used:

我现在寻找了一种替代设置线程的方法。CurrentPrincipal以某种方式将主体安全地从自定义MessageHandler转移到controller方法,在这个博客post请求属性中使用:

request.Properties.Add(HttpPropertyKeys.UserPrincipalKey,
    new GenericPrincipal(identity, new string[0]));

I wanted to test that but it seems that the HttpPropertyKeys class (which is in namespace System.Web.Http.Hosting) doesn't have a UserPrincipalKey property anymore in the recent WebApi versions (release candidate and final release from last week as well).

我想测试一下,但似乎HttpPropertyKeys类(在命名空间System.Web.Http.Hosting中)在最近的WebApi版本中没有UserPrincipalKey属性(发布候选版本和上周的最终版本)。

My question is: How can I change the last code snippet above so that is works with the current WebAPI version? Or generally: How can I set the user principal in a custom MessageHandler and access it reliably in a controller method?

我的问题是:如何更改上面的最后一个代码片段,以便与当前的WebAPI版本兼容?或者通常:如何在自定义MessageHandler中设置用户主体,并在controller方法中可靠地访问它?

Edit

编辑

It is mentioned here that "HttpPropertyKeys.UserPrincipalKey ... resolves to “MS_UserPrincipal”", so I tried to use:

这里提到“HttpPropertyKeys”。UserPrincipalKey……解析为“MS_UserPrincipal”,因此我尝试使用:

request.Properties.Add("MS_UserPrincipal",
    new GenericPrincipal(identity, new string[0]));

But it doesn't work as I expected: The ApiController.User property does not contain the principal added to the Properties collection above.

但它并没有像我预期的那样工作:ApiController。用户属性不包含添加到上述属性集合中的主体。

3 个解决方案

#1


73  

The problem of losing the principal on a new thread is mentioned here:

这里提到了在新线程上丢失主体的问题:

http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/

http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/

Important: Setting the Client Principal in ASP.NET Web API

重要:在ASP中设置客户端主体。净Web API

Due to some unfortunate mechanisms buried deep in ASP.NET, setting Thread.CurrentPrincipal in Web API web hosting is not enough.

由于一些不幸的机制深藏在ASP中。净,设置线程。Web API Web托管中的CurrentPrincipal不够。

When hosting in ASP.NET, Thread.CurrentPrincipal might get overridden with HttpContext.Current.User when creating new threads. This means you have to set the principal on both the thread and the HTTP context.

当主机在ASP。净,线程。CurrentPrincipal可能会被HttpContext.Current重写。用户在创建新线程时。这意味着您必须在线程和HTTP上下文中设置主体。

And here: http://aspnetwebstack.codeplex.com/workitem/264

在这里:http://aspnetwebstack.codeplex.com/workitem/264

Today, you will need to set both of the following for user principal if you use a custom message handler to perform authentication in the web hosted scenario.

现在,如果您使用自定义消息处理程序在web托管场景中执行身份验证,那么您将需要为用户主体设置以下两个参数。

IPrincipal principal = new GenericPrincipal(
    new GenericIdentity("myuser"), new string[] { "myrole" });
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;

I have added the last line HttpContext.Current.User = principal (needs using System.Web;) to the message handler and the User property in the ApiController does always have the correct principal now, even if the thread has changed due to the task in the MediaTypeFormatter.

我添加了最后一行HttpContext.Current。User = principal(需要使用System.Web;)到消息处理程序,ApiController中的User属性现在总是有正确的主体,即使由于MediaTypeFormatter中的任务而改变了线程。

Edit

编辑

Just to emphasize it: Setting the current user's principal of the HttpContext is only necessary when the WebApi is hosted in ASP.NET/IIS. For self-hosting it is not necessary (and not possible because HttpContext is an ASP.NET construct and doesn't exist when self hosted).

强调一下:只有当WebApi驻留在ASP.NET/IIS中时,才需要设置HttpContext的当前用户主体。对于自托管,它不是必需的(也不可能,因为HttpContext是一个ASP。NET构造,在自托管时不存在)。

#2


5  

To avoid the context switch try using a TaskCompletionSource<object> instead of manually starting another task in your custom MediaTypeFormatter:

为了避免上下文切换,请尝试使用TaskCompletionSource,而不是在自定义MediaTypeFormatter中手动启动另一个任务:

public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
    var tcs = new TaskCompletionSource<object>();

    // some formatting happens and finally a TestModel is returned,
    // simulated here by just an empty model
    var testModel = new TestModel();

    tcs.SetResult(testModel);
    return tcs.Task;
}

#3


4  

Using your custom MessageHandler you could add the MS_UserPrincipal property by calling the HttpRequestMessageExtensionMethods.SetUserPrincipal extension method defined in System.ServiceModel.Channels:

使用定制的MessageHandler,可以通过调用HttpRequestMessageExtensionMethods添加MS_UserPrincipal属性。SetUserPrincipal扩展方法,定义在System.ServiceModel.Channels:

protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    var user = new GenericPrincipal(new GenericIdentity("UserID"), null);
    request.SetUserPrincipal(user);
    return base.SendAsync(request, cancellationToken);
}

Note that this only adds this property to the Request's Properties collection, it doesn't change the User attached to the ApiController.

注意,这只会将此属性添加到请求的属性集合中,不会更改附加到ApiController的用户。

#1


73  

The problem of losing the principal on a new thread is mentioned here:

这里提到了在新线程上丢失主体的问题:

http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/

http://leastprivilege.com/2012/06/25/important-setting-the-client-principal-in-asp-net-web-api/

Important: Setting the Client Principal in ASP.NET Web API

重要:在ASP中设置客户端主体。净Web API

Due to some unfortunate mechanisms buried deep in ASP.NET, setting Thread.CurrentPrincipal in Web API web hosting is not enough.

由于一些不幸的机制深藏在ASP中。净,设置线程。Web API Web托管中的CurrentPrincipal不够。

When hosting in ASP.NET, Thread.CurrentPrincipal might get overridden with HttpContext.Current.User when creating new threads. This means you have to set the principal on both the thread and the HTTP context.

当主机在ASP。净,线程。CurrentPrincipal可能会被HttpContext.Current重写。用户在创建新线程时。这意味着您必须在线程和HTTP上下文中设置主体。

And here: http://aspnetwebstack.codeplex.com/workitem/264

在这里:http://aspnetwebstack.codeplex.com/workitem/264

Today, you will need to set both of the following for user principal if you use a custom message handler to perform authentication in the web hosted scenario.

现在,如果您使用自定义消息处理程序在web托管场景中执行身份验证,那么您将需要为用户主体设置以下两个参数。

IPrincipal principal = new GenericPrincipal(
    new GenericIdentity("myuser"), new string[] { "myrole" });
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;

I have added the last line HttpContext.Current.User = principal (needs using System.Web;) to the message handler and the User property in the ApiController does always have the correct principal now, even if the thread has changed due to the task in the MediaTypeFormatter.

我添加了最后一行HttpContext.Current。User = principal(需要使用System.Web;)到消息处理程序,ApiController中的User属性现在总是有正确的主体,即使由于MediaTypeFormatter中的任务而改变了线程。

Edit

编辑

Just to emphasize it: Setting the current user's principal of the HttpContext is only necessary when the WebApi is hosted in ASP.NET/IIS. For self-hosting it is not necessary (and not possible because HttpContext is an ASP.NET construct and doesn't exist when self hosted).

强调一下:只有当WebApi驻留在ASP.NET/IIS中时,才需要设置HttpContext的当前用户主体。对于自托管,它不是必需的(也不可能,因为HttpContext是一个ASP。NET构造,在自托管时不存在)。

#2


5  

To avoid the context switch try using a TaskCompletionSource<object> instead of manually starting another task in your custom MediaTypeFormatter:

为了避免上下文切换,请尝试使用TaskCompletionSource,而不是在自定义MediaTypeFormatter中手动启动另一个任务:

public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
    var tcs = new TaskCompletionSource<object>();

    // some formatting happens and finally a TestModel is returned,
    // simulated here by just an empty model
    var testModel = new TestModel();

    tcs.SetResult(testModel);
    return tcs.Task;
}

#3


4  

Using your custom MessageHandler you could add the MS_UserPrincipal property by calling the HttpRequestMessageExtensionMethods.SetUserPrincipal extension method defined in System.ServiceModel.Channels:

使用定制的MessageHandler,可以通过调用HttpRequestMessageExtensionMethods添加MS_UserPrincipal属性。SetUserPrincipal扩展方法,定义在System.ServiceModel.Channels:

protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    var user = new GenericPrincipal(new GenericIdentity("UserID"), null);
    request.SetUserPrincipal(user);
    return base.SendAsync(request, cancellationToken);
}

Note that this only adds this property to the Request's Properties collection, it doesn't change the User attached to the ApiController.

注意,这只会将此属性添加到请求的属性集合中,不会更改附加到ApiController的用户。