Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/287.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/azure/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为隐式流身份验证编写集成测试 上下文_C#_Oauth 2.0_Integration Testing_Identityserver4_Openid Connect - Fatal编程技术网

C# 为隐式流身份验证编写集成测试 上下文

C# 为隐式流身份验证编写集成测试 上下文,c#,oauth-2.0,integration-testing,identityserver4,openid-connect,C#,Oauth 2.0,Integration Testing,Identityserver4,Openid Connect,我维护一个基于identity Server 4和.NET Core identity的身份提供者。我的用户使用SPA,在那里他们会在必要时被提示使用隐式流登录(顺便说一句,我知道这不再是SPA的推荐流) 最近,我添加了一个功能来跟踪为给定用户颁发最新令牌的时刻。通过添加ICustomAuthorizeRequestValidator的实例,可以轻松实现这一点(请参见下面的简化版本): 公共类AuthRequestValidator:ICustomAuthorizerRequestValidat

我维护一个基于identity Server 4和.NET Core identity的身份提供者。我的用户使用SPA,在那里他们会在必要时被提示使用隐式流登录(顺便说一句,我知道这不再是SPA的推荐流)

最近,我添加了一个功能来跟踪为给定用户颁发最新令牌的时刻。通过添加
ICustomAuthorizeRequestValidator
的实例,可以轻松实现这一点(请参见下面的简化版本):

公共类AuthRequestValidator:ICustomAuthorizerRequestValidator
{
私有只读UserManager库,支持不同的授权流。该库中不存在该库的事实强烈暗示我当前的方法可能是错误的


对于如何使用集成测试中的隐式流登录,您有什么建议吗?或者,如果不可能,您能否指出一种不同的方法,我可以使用它来实现测试新功能的目标?

嗯,这非常困难,因为:

  • 隐式流是交互式的:它需要浏览器和用户的交互,这两者都是很难模拟的
  • 它涉及多个重要的GET和POST请求,可能包括一个带有CSRF令牌的请求
  • 这取决于您特定的IdentityServer登录屏幕,每个人的登录屏幕都可能不同
无论如何,这里有一个模板解决方案,我已经在我的IdentityServer4解决方案中测试过,该解决方案通过ASP.NET核心标识搭建的表单支持本地登录:

// Prerequisites:
const string usernameSeededInDatabase = "johndoe@example.org";
const string passwordSeededInDatabase = "Super123Secret!";
const string implicitFlowClientId = "my-implicit-flow-client"; // IDS4 Client setting
const string spaClientUri = "http://localhost:4200/"; // IDS4 Client setting
const string spaClientRedirectUri = "http://localhost:4200/silent-refresh.html"; // IDS4 Client setting
private readonly WebApplicationFactory _factory; // Injected in Test Class

[Fact]
public async Task Can_run_through_implicit_flow()
{
    // Simulate Implicit flow with a client that retains cookies too:
    var httpClient = _factory.CreateClient();

    // Start by faking the "login" GET started from an SPA:
    var authorizeRequestUrl = AuthorizeEndpoint
        + "?response_type=id_token token"
        + "&client_id=" + clientId
        + "&state=teststate"
        + "&redirect_uri=" + spaClientUri
        + "&scope=openid profile" // plus an api scope, if you like
        + "&nonce=testnonce";
    var authorizeResponse = await httpClient.GetAsync(authorizeRequestUrl);
    var authorizeResponseBody = await authorizeResponse.Content.ReadAsStringAsync();

    // Our IDS will want you to POST to the same url you got redirected to previously (as it will also contain the returnUrl):
    var loginRequestUrl = authorizeResponse.RequestMessage.RequestUri.AbsoluteUri;

    // Extract CsrfToken from html:
    var regex = new Regex("name=\"__RequestVerificationToken\" type=\"hidden\" value=\"(?<CsrfToken>[^\"]+)\"");
    var match = regex.Match(authorizeResponseBody);
    var requestVerificationToken = match.Groups["CsrfToken"].Value;

    // Simulate the login form POST:
    var content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
    {
        { new KeyValuePair<string, string>("Input.Email", usernameSeededInDatabase) },
        { new KeyValuePair<string, string>("Input.Password", passwordSeededInDatabase) },
        { new KeyValuePair<string, string>("__RequestVerificationToken", requestVerificationToken) },
    });
    var loginResponse = await httpClient.PostAsync(loginRequestUrl, content);
    var loginResponseBody = await loginResponse.Content.ReadAsStringAsync();

    // Now we should have a cookie on the HttpClient that allows silent refreshes:
    var silentRefreshUrl = AuthorizeEndpoint
        + "?response_type=id_token token"
        + "&client_id=" + clientId
        + "&state=teststate"
        + "&redirect_uri=" + spaClientRedirectUri
        + "&scope=openid profile" // plus an api scope, if you like
        + "&nonce=testnonce"
        + "&prompt=none"; // Indicates silent refresh
    var silentRefreshResponse = await httpClient.GetAsync(silentRefreshUrl);

    // We should've been redirected to the silent-refresh.html page (response is probably a 404 since we're not serving the SPA):
    Assert.Matches("http://localhost:4200/silent-refresh.html", silentRefreshResponse.RequestMessage.RequestUri.AbsoluteUri);
}
//先决条件:
常量字符串UserNameseedInDatabase=”johndoe@example.org";
const string passwordseedInDatabase=“Super123Secret!”;
const string implicitFlowClientId=“我的隐式流客户端”//IDS4客户端设置
常量字符串spaClientUri=”http://localhost:4200/“;//IDS4客户端设置
常量字符串spaClientRedirectUri=”http://localhost:4200/silent-refresh.html“//IDS4客户端设置
私有只读WebApplicationFactory _factory;//注入测试类
[事实]
公共异步任务可以通过\u隐式\u流()运行
{
//使用保留cookie的客户端模拟隐式流:
var httpClient=_factory.CreateClient();
//从假装“登录”开始从SPA开始:
var authorizeRequestUrl=AuthorizeEndpoint
+“?响应\u类型=id\u令牌”
+“&client_id=“+clientId
+“&state=teststate”
+“&redirect_uri=“+spaClientUri”
+“&scope=openid概要文件”//加上一个api范围,如果您愿意的话
+“&nonce=testnonce”;
var authorizeResponse=await-httpClient.GetAsync(authorizeRequestUrl);
var authorizeResponseBy=await authorizeResponse.Content.ReadAsStringAsync();
//我们的ID将希望您发布到之前重定向到的相同url(因为它还将包含returnUrl):
var loginRequestUrl=authorizeResponse.RequestMessage.RequestUri.AbsoluteUri;
//从html中提取CsrfToken:
var regex=new regex(“name=\”\uu RequestVerificationToken\“type=\“hidden\”value=\”(?[^\“]+)\);
var match=regex.match(authorizeResponseBody);
var requestVerificationToken=match.Groups[“CsrfToken”].Value;
//模拟登录表单POST:
var content=newformurlencodedcontent(新列表
{
{new KeyValuePair(“Input.Email”,usernameededindatabase)},
{new KeyValuePair(“Input.Password”,passwordseedInDatabase)},
{新的KeyValuePair(“\uu RequestVerificationToken”,RequestVerificationToken)},
});
var loginResponse=等待httpClient.PostAsync(loginRequestUrl,内容);
var loginResponseBody=await loginResponse.Content.ReadAsStringAsync();
//现在,我们应该在HttpClient上有一个允许静默刷新的cookie:
var silentRefreshUrl=AuthorizeEndpoint
+“?响应\u类型=id\u令牌”
+“&client_id=“+clientId
+“&state=teststate”
+“&redirect_uri=“+spaclienterrirecturi
+“&scope=openid概要文件”//加上一个api范围,如果您愿意的话
+“&nonce=testnonce”
+“&prompt=none”;//表示静默刷新
var silentRefreshResponse=wait httpClient.GetAsync(silentRefreshUrl);
//我们应该被重定向到silent-refresh.html页面(响应可能是404,因为我们没有为SPA提供服务):
Assert.Matches(“http://localhost:4200/silent-refresh.html”,silentRefreshResponse.RequestMessage.RequestUri.AbsoluteUri);
}

然而,如果你打算依靠模拟用户交互来做很多这样的测试,那么使用Selenium和真正的e2e/集成测试可能会更容易一些?然后再说一遍…:-)

种子从哪里来?正如Seed.adminEmailGood point中使用的那样,我在发布之前只重构了一半代码。在我这里的堆栈溢出示例中,它是将是常量,将更新我的帖子。
var user = GetUserFromDb("foo@bar.xyz");
var oldLatestToken = user.LastTokenIssuedUtc;

RequestTokenImplicitFlowAsync(new ImplicitFlowRequestParams
{
    UserName = "foo@bar.xyz",
    Password = "secret",
    Scope = "scope"
});

user = GetUserFromDb("foo@bar.xyz");
Assert.True(oldLatestToken < user.LastTokenIssuedUtc);
// Prerequisites:
const string usernameSeededInDatabase = "johndoe@example.org";
const string passwordSeededInDatabase = "Super123Secret!";
const string implicitFlowClientId = "my-implicit-flow-client"; // IDS4 Client setting
const string spaClientUri = "http://localhost:4200/"; // IDS4 Client setting
const string spaClientRedirectUri = "http://localhost:4200/silent-refresh.html"; // IDS4 Client setting
private readonly WebApplicationFactory _factory; // Injected in Test Class

[Fact]
public async Task Can_run_through_implicit_flow()
{
    // Simulate Implicit flow with a client that retains cookies too:
    var httpClient = _factory.CreateClient();

    // Start by faking the "login" GET started from an SPA:
    var authorizeRequestUrl = AuthorizeEndpoint
        + "?response_type=id_token token"
        + "&client_id=" + clientId
        + "&state=teststate"
        + "&redirect_uri=" + spaClientUri
        + "&scope=openid profile" // plus an api scope, if you like
        + "&nonce=testnonce";
    var authorizeResponse = await httpClient.GetAsync(authorizeRequestUrl);
    var authorizeResponseBody = await authorizeResponse.Content.ReadAsStringAsync();

    // Our IDS will want you to POST to the same url you got redirected to previously (as it will also contain the returnUrl):
    var loginRequestUrl = authorizeResponse.RequestMessage.RequestUri.AbsoluteUri;

    // Extract CsrfToken from html:
    var regex = new Regex("name=\"__RequestVerificationToken\" type=\"hidden\" value=\"(?<CsrfToken>[^\"]+)\"");
    var match = regex.Match(authorizeResponseBody);
    var requestVerificationToken = match.Groups["CsrfToken"].Value;

    // Simulate the login form POST:
    var content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
    {
        { new KeyValuePair<string, string>("Input.Email", usernameSeededInDatabase) },
        { new KeyValuePair<string, string>("Input.Password", passwordSeededInDatabase) },
        { new KeyValuePair<string, string>("__RequestVerificationToken", requestVerificationToken) },
    });
    var loginResponse = await httpClient.PostAsync(loginRequestUrl, content);
    var loginResponseBody = await loginResponse.Content.ReadAsStringAsync();

    // Now we should have a cookie on the HttpClient that allows silent refreshes:
    var silentRefreshUrl = AuthorizeEndpoint
        + "?response_type=id_token token"
        + "&client_id=" + clientId
        + "&state=teststate"
        + "&redirect_uri=" + spaClientRedirectUri
        + "&scope=openid profile" // plus an api scope, if you like
        + "&nonce=testnonce"
        + "&prompt=none"; // Indicates silent refresh
    var silentRefreshResponse = await httpClient.GetAsync(silentRefreshUrl);

    // We should've been redirected to the silent-refresh.html page (response is probably a 404 since we're not serving the SPA):
    Assert.Matches("http://localhost:4200/silent-refresh.html", silentRefreshResponse.RequestMessage.RequestUri.AbsoluteUri);
}