我维护一个基于Identity Server 4和.NET Core Identity的身份提供程序。我的用户使用SPA,在必要时会提示他们使用隐式流登录(顺便说一句,我知道它不再是SPA的推荐流)。
最近,我添加了一项功能来跟踪为给定用户发布最新令牌的时间。添加一个ICustomAuthorizeRequestValidator
实例即可轻松完成此操作(简化版本请参见下文):
public class AuthRequestValidator : ICustomAuthorizeRequestValidator
{
private readonly UserManager<ApplicationUser> _userManager;
public AuthRequestValidator(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task ValidateAsync(CustomAuthorizeRequestValidationContext context)
{
if (context.Result.IsError)
{
return;
}
var userName = context.Result.ValidatedRequest?.Subject?.Identity?.Name;
var user = await _userManager.FindByNameAsync(userName);
user.LastTokenIssuedUtc = DateTimeOffset.UtcNow;
await _userManager.UpdateAsync(user);
}
}
现在,我正在尝试编写一个集成测试,以检查用户登录时或请求新令牌时是否更新了日期时间。理想情况下,该外观如下所示:
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);
在上面的示例中,我使用RequestTokenImplicitFlowAsync
方法及其参数来说明我的意图。不幸的是,这种方法实际上并不存在,我还无法弄清楚自己如何实现。可能吗在其他测试中,我使用的是IdentityModel库提供的扩展方法,它们支持不同的授权流程。该库中不存在该事实的事实强烈表明我当前的方法可能是错误的。
您对如何使用集成测试中的隐式流进行登录有任何建议吗?还是如果不可能,您能指出一种可以用来测试我的新功能的目标的不同方法吗?
答案 0 :(得分:0)
嗯,这很难,因为:
无论如何,这是一个经过测试可在IdentityServer4解决方案中使用的模板解决方案,该解决方案可通过带ASP.NET Core Identity支架的表单支持本地登录:
// 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", Seed.adminEmail) },
{ new KeyValuePair<string, string>("Input.Password", Seed.adminPassword) },
{ 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);
}
但是,如果您要进行许多模拟用户交互性的测试,那么使用Selenium之类的工具和真正的端到端/集成测试可能会更容易?再说一次...:-)