问题
So I've found bits and pieces that have enlightened me some on the [Authorize] tag, but nothing that solves my problem.
My scenario is that I have Web Api methods that I want to hit with integration tests using RestSharp. However RestSharp is getting my login page, instead of the results of the call.
[Authorize]
public Item GetItem([FromBody] int id)
{
return service.GetItem(id);
}
The product uses a custom login system, and what I would REALLY like would be a way to disable the [Authorize] badge only for integration tests. However I read that you can allow anonymous users and it would 'disable' the badge, so in the solution, I have an integration tests project, and in that project I have an App.config file. In that file I put:
<location>
<system.web>
<authorization>
<allow users="?"/>
</authorization>
</system.web>
</location>
But this doesn't appear to be working either. Any explanation as to what's going on, why it's not working and what can be done to get this working would be greatly appreciated.
I have attempted to set a Thread.CurrentPrincipal but that didn't work (maybe I did it wrong - can you set "anything" to be authorized in the code?). Authentication is handled in an httpmodule if that helps at all.
回答1:
Here is how you should set the Thread.CurrentPrincipal
. Add a message handler like this to your Web API project and add the handler in the Register
method of WebApiConfig.cs
like so: config.MessageHandlers.Add(new MyTestHandler());
.
public class MyTestHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var local = request.Properties["MS_IsLocal"] as Lazy<bool>;
bool isLocal = local != null && local.Value;
if (isLocal)
{
if (request.Headers.GetValues("X-Testing").First().Equals("true"))
{
var dummyPrincipal = new GenericPrincipal(
new GenericIdentity("dummy", "dummy"),
new[] { "myrole1" });
Thread.CurrentPrincipal = dummyPrincipal;
if (HttpContext.Current != null)
HttpContext.Current.User = dummyPrincipal;
}
}
return await base.SendAsync(request, cancellationToken);
}
}
This handler sets an authenticated principal to make all your [Authorize]
happy. There is an element of risk with this approach. Only for testing, you should plug this handler into the Web API pipeline. If you plug this handler in to the pipeline (intentional or otherwise) in your production code, it basically defeats your authentication mechanism. To mitigate the risk to some extent (hoping API is not accessed locally), I check to ensure the access is local and that there is a header X-Testing
with a value of true
.
From RestSharp, add the custom header.
var request = new RestRequest(...);
request.AddHeader("X-Testing", "true");
BTW, for integration testing, I'd much rather use in-memory hosting, instead of web-hosting. That way, Web API runs in the same testing project and you can do whatever you want with it, without the fear of breaking something in production. For more info on in-memory hosting, see this and this.
回答2:
I realise that this question is about firing 'real' requests from RestSharp at the webapi endpoints so this suggestion is not immediately applicable to the OPs scenario.. BUT:
I'm using in-memory Web Api tests using HttpConfiguration
, HttpServer
and HttpMessageInvoker
(much like Badri's suggestion I believe). In this way, I don't need listeners or ports open since I can test the full stack (end to end test) in memory - really handy on a build server, Heroku instance, etc.
Using in-memory tests, here is how you could set the Thread.CurrentPrincipal
.. I have a helper on my test base class like this:
protected void AuthentateRequest()
{
Thread.CurrentPrincipal = new AuthenticatedPrincipal(Thread.CurrentPrincipal);
}
Which uses this:
public class AuthenticatedPrincipal : IPrincipal
{
private readonly IPrincipal _principalToWrap;
private readonly IIdentity _identityToWrap;
public AuthenticatedPrincipal(IPrincipal principalToWrap)
{
_principalToWrap = principalToWrap;
_identityToWrap = new AuthenticatedIdentity(principalToWrap.Identity);
}
public bool IsInRole(string role)
{ return _principalToWrap.IsInRole(role); }
public IIdentity Identity
{
get { return _identityToWrap; }
private set { throw new NotSupportedException(); }
}
}
public class AuthenticatedIdentity : IIdentity
{
private readonly IIdentity _identityToWrap;
public AuthenticatedIdentity(IIdentity identityToWrap)
{
_identityToWrap = identityToWrap;
}
public string Name
{
get { return _identityToWrap.Name; }
private set { throw new NotSupportedException(); }
}
public string AuthenticationType
{
get { return _identityToWrap.AuthenticationType; }
private set { throw new NotSupportedException(); }
}
public bool IsAuthenticated
{
get { return true; }
private set { throw new NotSupportedException(); }
}
}
It may seem like overkill to stub the IPrincipal
manually but I tried with my mocking framework and it blew up in some of my test runners (Resharper and TeamCity, but not NCrunch - something about serialising over AppDomains I think).
This will set Thread.CurrentPrincipal
inside the ApiController
action method and therefore fool the AuthorizeAttribute
into believing you are authenticated.
回答3:
Set the authenticator for your RestClient
:
RestClient.Authenticator = new HttpBasicAuthenticator(username, password);
Using the authenticator that your custom login system actually accepts ... Basic, NTLM, OAuth, Simple ...
It is kind of documented in the second line of the example at http://restsharp.org/
来源:https://stackoverflow.com/questions/18048158/integration-test-web-api-with-authorize