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.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
|
|
@ -74,5 +75,10 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// The <see cref="TimeSpan"/> after which security stamps are re-validated.
|
||||
/// </value>
|
||||
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);
|
||||
if (user != null)
|
||||
{
|
||||
// REVIEW: note we lost login authenticaiton method
|
||||
context.ReplacePrincipal(await _signInManager.CreateUserPrincipalAsync(user));
|
||||
var newPrincipal = 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;
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -139,6 +139,54 @@ namespace Microsoft.AspNetCore.Identity.InMemory
|
|||
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]
|
||||
public async Task TwoFactorRememberCookieVerification()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue