如何为由 OpenId Connect、cookie 身份验证和 NancyFx 和 Owin 托管的 WebApi 编写内存集成测试
How to write in memory integration test for a WebApi secured by OpenId Connect, cookie authentication and hosted by NancyFx and Owin
我有一个使用 Owin 和 NancyFX 的 WabApi 项目。 api 由 OpenId Connect 和 cookie 身份验证保护。
我必须使用 HttpClient 编写一些内存集成测试,只要您不尝试使用基于 OpenId Connect 和 cookie 的身份验证,这就非常容易。
有谁知道如何为 HttpClient 准备一个正确的身份验证 cookie 以使其作为经过身份验证的用户与 WebApi 连接?
目前我可以进行一些 http 调用以从 OpenId Connect 提供程序(由 IdentityServer v3 实现)获取正确的访问令牌、id 令牌等,但我不知道如何为 HttpClient 准备身份验证 cookie。
PS:我对 OpenId Connect 使用混合流程
您可以在下面找到我的一些文件。
服务器项目:
WebApi 的 AppStartup:
服务器应用程序同时托管 WebApi 和 OpenId Connect 提供程序 (IdentityServer v3),因此其应用程序启动如下所示:
public class ServerAppStartup
{
public static void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var factory = new IdentityServerServiceFactory {...};
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "server app",
SigningCertificate = ...,
RequireSsl = false,
Factory = factory,
AuthenticationOptions = new AuthenticationOptions {
RememberLastUsername = true
},
EnableWelcomePage = false
});
});
app.SetDefaultSignInAsAuthenticationType("ClientCookie");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = "ClientCookie",
CookieName = CookieAuthenticationDefaults.CookiePrefix + "ClientCookie",
ExpireTimeSpan = TimeSpan.FromMinutes(5)
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(),
Authority = options.BaseUrl+ "identity",
ClientId = options.ClientId,
RedirectUri = options.RedirectUri,
PostLogoutRedirectUri = options.PostLogoutRedirectUri,
ResponseType = "code id_token",
Scope = "openid profile offline_access",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
/* stuff to get ACCESS TOKEN from CODE TOKEN */
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
}
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseNancy();
app.UseStageMarker(PipelineStage.MapHandler);
}
nancy 模块示例(类似于 MVC 或 WebApi 中的控制器):
using System;
using Nancy.ModelBinding;
using Nancy.Security;
namespace Server.Modules
{
public class UsersModule : BaseModule
{
public UsersModule() : base("/users")
{
Get["/getall"] = parameters =>
{
this.RequiresMSOwinAuthentication();
...
return ...;
};
}
}
}
集成测试项目:
测试服务器让我运行内存中的WebApi:
public class TestServer: IDisposable
{
private Func<IDictionary<string, object>, Task> _appFunc;
public static CookieContainer CookieContainer;
public Uri BaseAddress { get; set; }
// I uses OwinHttpMessageHandler becaouse it can handle http redirections
public OwinHttpMessageHandler Handler { get; private set; }
public HttpClient HttpClient => new HttpClient(Handler) { BaseAddress = BaseAddress };
public static TestServer Create()
{
CookieContainer = new CookieContainer();
var result = new TestServer();
var appBuilder = new AppBuilder();
appBuilder.Properties["host.AppName"] = "WebApi server";
/* Use configuration of server app */
ServerAppStartup.Configuration(appBuilder);
result._appFunc = appBuilder.Build();
result.Handler = new OwinHttpMessageHandler(result._appFunc)
{
AllowAutoRedirect = true,
AutoRedirectLimit = 1000,
CookieContainer = CookieContainer,
UseCookies = true
};
return result;
}
public void Dispose()
{
Handler.Dispose();
GC.SuppressFinalize(this);
}
}
样本测试:
namespace ServerSpec.Specs.Users
{
public class GetAllUsersSpec
{
private TestServer _server;
public GetAllUsersSpec(){
server = TestServer.create();
}
[Fact]
public void should_return_all_users()
{
/* here I will get error because http client or rather its cookie handler has no authentication cookie */
var users = Get("/users/getall");
...
}
public TResponse Get<TResponse>(string urlFragment)
{
var client = server.HttpClient();
var httpResponse = client.GetAsync(urlFragment).Result;
httpResponse.EnsureSuccessStatusCode();
return httpResponse.Content.ReadAsAsync<TResponse>().Result;
}
}
}
查看此项目中的单元测试和集成测试:
https://github.com/IdentityModel/IdentityModel.Owin.PopAuthentication
它显示了在一个管道中使用 IdentityServer 进行内存中集成测试,并在另一个接受令牌的管道中使用(假)web api。
我有一个使用 Owin 和 NancyFX 的 WabApi 项目。 api 由 OpenId Connect 和 cookie 身份验证保护。
我必须使用 HttpClient 编写一些内存集成测试,只要您不尝试使用基于 OpenId Connect 和 cookie 的身份验证,这就非常容易。
有谁知道如何为 HttpClient 准备一个正确的身份验证 cookie 以使其作为经过身份验证的用户与 WebApi 连接?
目前我可以进行一些 http 调用以从 OpenId Connect 提供程序(由 IdentityServer v3 实现)获取正确的访问令牌、id 令牌等,但我不知道如何为 HttpClient 准备身份验证 cookie。
PS:我对 OpenId Connect 使用混合流程
您可以在下面找到我的一些文件。
服务器项目: WebApi 的 AppStartup:
服务器应用程序同时托管 WebApi 和 OpenId Connect 提供程序 (IdentityServer v3),因此其应用程序启动如下所示:
public class ServerAppStartup
{
public static void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var factory = new IdentityServerServiceFactory {...};
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "server app",
SigningCertificate = ...,
RequireSsl = false,
Factory = factory,
AuthenticationOptions = new AuthenticationOptions {
RememberLastUsername = true
},
EnableWelcomePage = false
});
});
app.SetDefaultSignInAsAuthenticationType("ClientCookie");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = "ClientCookie",
CookieName = CookieAuthenticationDefaults.CookiePrefix + "ClientCookie",
ExpireTimeSpan = TimeSpan.FromMinutes(5)
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(),
Authority = options.BaseUrl+ "identity",
ClientId = options.ClientId,
RedirectUri = options.RedirectUri,
PostLogoutRedirectUri = options.PostLogoutRedirectUri,
ResponseType = "code id_token",
Scope = "openid profile offline_access",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
/* stuff to get ACCESS TOKEN from CODE TOKEN */
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
}
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseNancy();
app.UseStageMarker(PipelineStage.MapHandler);
}
nancy 模块示例(类似于 MVC 或 WebApi 中的控制器):
using System;
using Nancy.ModelBinding;
using Nancy.Security;
namespace Server.Modules
{
public class UsersModule : BaseModule
{
public UsersModule() : base("/users")
{
Get["/getall"] = parameters =>
{
this.RequiresMSOwinAuthentication();
...
return ...;
};
}
}
}
集成测试项目:
测试服务器让我运行内存中的WebApi:
public class TestServer: IDisposable
{
private Func<IDictionary<string, object>, Task> _appFunc;
public static CookieContainer CookieContainer;
public Uri BaseAddress { get; set; }
// I uses OwinHttpMessageHandler becaouse it can handle http redirections
public OwinHttpMessageHandler Handler { get; private set; }
public HttpClient HttpClient => new HttpClient(Handler) { BaseAddress = BaseAddress };
public static TestServer Create()
{
CookieContainer = new CookieContainer();
var result = new TestServer();
var appBuilder = new AppBuilder();
appBuilder.Properties["host.AppName"] = "WebApi server";
/* Use configuration of server app */
ServerAppStartup.Configuration(appBuilder);
result._appFunc = appBuilder.Build();
result.Handler = new OwinHttpMessageHandler(result._appFunc)
{
AllowAutoRedirect = true,
AutoRedirectLimit = 1000,
CookieContainer = CookieContainer,
UseCookies = true
};
return result;
}
public void Dispose()
{
Handler.Dispose();
GC.SuppressFinalize(this);
}
}
样本测试:
namespace ServerSpec.Specs.Users
{
public class GetAllUsersSpec
{
private TestServer _server;
public GetAllUsersSpec(){
server = TestServer.create();
}
[Fact]
public void should_return_all_users()
{
/* here I will get error because http client or rather its cookie handler has no authentication cookie */
var users = Get("/users/getall");
...
}
public TResponse Get<TResponse>(string urlFragment)
{
var client = server.HttpClient();
var httpResponse = client.GetAsync(urlFragment).Result;
httpResponse.EnsureSuccessStatusCode();
return httpResponse.Content.ReadAsAsync<TResponse>().Result;
}
}
}
查看此项目中的单元测试和集成测试:
https://github.com/IdentityModel/IdentityModel.Owin.PopAuthentication
它显示了在一个管道中使用 IdentityServer 进行内存中集成测试,并在另一个接受令牌的管道中使用(假)web api。