Add OnSecurityStampReplacingPrincipal event
This commit is contained in:
parent
a664d32c5c
commit
aba7cf96b9
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Builder
|
namespace Microsoft.AspNetCore.Builder
|
||||||
|
|
@ -74,5 +75,10 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
/// The <see cref="TimeSpan"/> after which security stamps are re-validated.
|
/// The <see cref="TimeSpan"/> after which security stamps are re-validated.
|
||||||
/// </value>
|
/// </value>
|
||||||
public TimeSpan SecurityStampValidationInterval { get; set; } = TimeSpan.FromMinutes(30);
|
public TimeSpan SecurityStampValidationInterval { get; set; } = TimeSpan.FromMinutes(30);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when the default security stamp validator replaces the user's ClaimsPrincipal in the cookie.
|
||||||
|
/// </summary>
|
||||||
|
public Func<SecurityStampRefreshingPrincipalContext, Task> OnSecurityStampRefreshingPrincipal { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Identity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to pass information during the SecurityStamp validation event.
|
||||||
|
/// </summary>
|
||||||
|
public class SecurityStampRefreshingPrincipalContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The principal contained in the current cookie.
|
||||||
|
/// </summary>
|
||||||
|
public ClaimsPrincipal CurrentPrincipal { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The new principal which should replace the current.
|
||||||
|
/// </summary>
|
||||||
|
public ClaimsPrincipal NewPrincipal { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -66,8 +66,23 @@ namespace Microsoft.AspNetCore.Identity
|
||||||
var user = await _signInManager.ValidateSecurityStampAsync(context.Principal);
|
var user = await _signInManager.ValidateSecurityStampAsync(context.Principal);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
// REVIEW: note we lost login authenticaiton method
|
var newPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
|
||||||
context.ReplacePrincipal(await _signInManager.CreateUserPrincipalAsync(user));
|
|
||||||
|
if (_options.OnSecurityStampRefreshingPrincipal != null)
|
||||||
|
{
|
||||||
|
var replaceContext = new SecurityStampRefreshingPrincipalContext
|
||||||
|
{
|
||||||
|
CurrentPrincipal = context.Principal,
|
||||||
|
NewPrincipal = newPrincipal
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: a null principal is allowed and results in a failed authentication.
|
||||||
|
await _options.OnSecurityStampRefreshingPrincipal(replaceContext);
|
||||||
|
newPrincipal = replaceContext.NewPrincipal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// REVIEW: note we lost login authentication method
|
||||||
|
context.ReplacePrincipal(newPrincipal);
|
||||||
context.ShouldRenew = true;
|
context.ShouldRenew = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,54 @@ namespace Microsoft.AspNetCore.Identity.InMemory
|
||||||
Assert.Equal("hao", FindClaimValue(transaction6, ClaimTypes.Name));
|
Assert.Equal("hao", FindClaimValue(transaction6, ClaimTypes.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanAccessOldPrincipalDuringSecurityStampReplacement()
|
||||||
|
{
|
||||||
|
var clock = new TestClock();
|
||||||
|
var server = CreateServer(services => services.Configure<IdentityOptions>(options =>
|
||||||
|
{
|
||||||
|
options.Cookies.ApplicationCookie.SystemClock = clock;
|
||||||
|
options.OnSecurityStampRefreshingPrincipal = c =>
|
||||||
|
{
|
||||||
|
var newId = new ClaimsIdentity();
|
||||||
|
newId.AddClaim(new Claim("PreviousName", c.CurrentPrincipal.Identity.Name));
|
||||||
|
c.NewPrincipal.AddIdentity(newId);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
var transaction1 = await SendAsync(server, "http://example.com/createMe");
|
||||||
|
Assert.Equal(HttpStatusCode.OK, transaction1.Response.StatusCode);
|
||||||
|
Assert.Null(transaction1.SetCookie);
|
||||||
|
|
||||||
|
var transaction2 = await SendAsync(server, "http://example.com/pwdLogin/false" );
|
||||||
|
Assert.Equal(HttpStatusCode.OK, transaction2.Response.StatusCode);
|
||||||
|
Assert.NotNull(transaction2.SetCookie);
|
||||||
|
Assert.DoesNotContain("; expires=", transaction2.SetCookie);
|
||||||
|
|
||||||
|
var transaction3 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue);
|
||||||
|
Assert.Equal("hao", FindClaimValue(transaction3, ClaimTypes.Name));
|
||||||
|
Assert.Null(transaction3.SetCookie);
|
||||||
|
|
||||||
|
// Make sure we don't get a new cookie yet
|
||||||
|
clock.Add(TimeSpan.FromMinutes(10));
|
||||||
|
var transaction4 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue);
|
||||||
|
Assert.Equal("hao", FindClaimValue(transaction4, ClaimTypes.Name));
|
||||||
|
Assert.Null(transaction4.SetCookie);
|
||||||
|
|
||||||
|
// Go past SecurityStampValidation interval and ensure we get a new cookie
|
||||||
|
clock.Add(TimeSpan.FromMinutes(21));
|
||||||
|
|
||||||
|
var transaction5 = await SendAsync(server, "http://example.com/me", transaction2.CookieNameValue);
|
||||||
|
Assert.NotNull(transaction5.SetCookie);
|
||||||
|
Assert.Equal("hao", FindClaimValue(transaction5, ClaimTypes.Name));
|
||||||
|
Assert.Equal("hao", FindClaimValue(transaction5, "PreviousName"));
|
||||||
|
|
||||||
|
// Make sure new cookie is valid
|
||||||
|
var transaction6 = await SendAsync(server, "http://example.com/me", transaction5.CookieNameValue);
|
||||||
|
Assert.Equal("hao", FindClaimValue(transaction6, ClaimTypes.Name));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TwoFactorRememberCookieVerification()
|
public async Task TwoFactorRememberCookieVerification()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue