зеркало из https://github.com/aspnet/Security.git
#32 - Port cookie auth session store from Katana. Add MemoryCache sample.
This commit is contained in:
Родитель
f6d6f31414
Коммит
d6b82b8799
13
Security.sln
13
Security.sln
|
@ -34,6 +34,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.M
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Security.OAuth", "src\Microsoft.AspNet.Security.OAuth\Microsoft.AspNet.Security.OAuth.kproj", "{4A636011-68EE-4CE5-836D-EA8E13CF71E4}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CookieSessionSample", "samples\CookieSessionSample\CookieSessionSample.kproj", "{19711880-46DA-4A26-9E0F-9B2E41D27651}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -144,6 +146,16 @@ Global
|
|||
{4A636011-68EE-4CE5-836D-EA8E13CF71E4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{4A636011-68EE-4CE5-836D-EA8E13CF71E4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{4A636011-68EE-4CE5-836D-EA8E13CF71E4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -159,5 +171,6 @@ Global
|
|||
{C96B77EA-4078-4C31-BDB2-878F11C5E061} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{1FCF26C2-A3C7-4308-B698-4AFC3560BC0C} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{4A636011-68EE-4CE5-836D-EA8E13CF71E4} = {4D2B6A51-2F9F-44F5-8131-EA5CAC053652}
|
||||
{19711880-46DA-4A26-9E0F-9B2E41D27651} = {F8C0AA27-F3FB-4286-8E4C-47EF86B539FF}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
"Microsoft.AspNet.HttpFeature": "1.0.0-*",
|
||||
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
|
||||
"Microsoft.AspNet.RequestContainer": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security": "",
|
||||
"Microsoft.AspNet.Security.Cookies": "",
|
||||
"Microsoft.AspNet.Security": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.Cookies": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.Framework.DependencyInjection": "1.0.0-*"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>19711880-46da-4a26-9e0f-9b2e41d27651</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>CookieSessionSample</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="$(OutputType) == 'Console'">
|
||||
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(OutputType) == 'Web'">
|
||||
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" Label="Configuration">
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.MemoryCache;
|
||||
using Microsoft.AspNet.Security;
|
||||
using Microsoft.AspNet.Security.Cookies.Infrastructure;
|
||||
|
||||
namespace CookieSessionSample
|
||||
{
|
||||
public class MemoryCacheSessionStore : IAuthenticationSessionStore
|
||||
{
|
||||
private const string KeyPrefix = "AuthSessionStore-";
|
||||
private IMemoryCache _cache;
|
||||
|
||||
public MemoryCacheSessionStore()
|
||||
{
|
||||
_cache = new MemoryCache();
|
||||
}
|
||||
|
||||
public async Task<string> StoreAsync(AuthenticationTicket ticket)
|
||||
{
|
||||
var guid = Guid.NewGuid();
|
||||
var key = KeyPrefix + guid.ToString();
|
||||
await RenewAsync(key, ticket);
|
||||
return key;
|
||||
}
|
||||
|
||||
public Task RenewAsync(string key, AuthenticationTicket ticket)
|
||||
{
|
||||
_cache.Set(key, ticket, context =>
|
||||
{
|
||||
var expiresUtc = ticket.Properties.ExpiresUtc;
|
||||
if (expiresUtc.HasValue)
|
||||
{
|
||||
context.SetAbsoluteExpiration(expiresUtc.Value);
|
||||
}
|
||||
context.SetSlidingExpiraiton(TimeSpan.FromHours(1)); // TODO: configurable.
|
||||
|
||||
return (AuthenticationTicket)context.State;
|
||||
});
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<AuthenticationTicket> RetrieveAsync(string key)
|
||||
{
|
||||
AuthenticationTicket ticket;
|
||||
_cache.TryGetValue(key, out ticket);
|
||||
return Task.FromResult(ticket);
|
||||
}
|
||||
|
||||
public Task RemoveAsync(string key)
|
||||
{
|
||||
_cache.Remove(key);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Security.Cookies;
|
||||
|
||||
namespace CookieSessionSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IBuilder app)
|
||||
{
|
||||
app.UseCookieAuthentication(new CookieAuthenticationOptions()
|
||||
{
|
||||
SessionStore = new MemoryCacheSessionStore(),
|
||||
});
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
if (context.User == null || !context.User.Identity.IsAuthenticated)
|
||||
{
|
||||
// Make a large identity
|
||||
var claims = new List<Claim>(1001);
|
||||
claims.Add(new Claim("name", "bob"));
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.Role, "SomeRandomGroup" + i, ClaimValueTypes.String, "IssuedByBob", "OriginalIssuerJoe"));
|
||||
}
|
||||
context.Response.SignIn(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType));
|
||||
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync("Hello First timer");
|
||||
return;
|
||||
}
|
||||
|
||||
context.Response.ContentType = "text/plain";
|
||||
await context.Response.WriteAsync("Hello old timer");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.FeatureModel": "1.0.0-*",
|
||||
"Microsoft.AspNet.Hosting": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http": "1.0.0-*",
|
||||
"Microsoft.AspNet.HttpFeature": "1.0.0-*",
|
||||
"Microsoft.AspNet.MemoryCache": "1.0.0-*",
|
||||
"Microsoft.AspNet.PipelineCore": "1.0.0-*",
|
||||
"Microsoft.AspNet.RequestContainer": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security": "1.0.0-*",
|
||||
"Microsoft.AspNet.Security.Cookies": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.Framework.DependencyInjection": "1.0.0-*"
|
||||
},
|
||||
"commands": { "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:12345" },
|
||||
"frameworks": {
|
||||
"aspnet50": {
|
||||
},
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Collections": "4.0.10.0",
|
||||
"System.Console": "4.0.0.0",
|
||||
"System.Diagnostics.Debug": "4.0.10.0",
|
||||
"System.Diagnostics.Tools": "4.0.0.0",
|
||||
"System.Globalization": "4.0.10.0",
|
||||
"System.IO": "4.0.10.0",
|
||||
"System.Linq": "4.0.0.0",
|
||||
"System.Reflection": "4.0.10.0",
|
||||
"System.Resources.ResourceManager": "4.0.0.0",
|
||||
"System.Runtime": "4.0.20.0",
|
||||
"System.Runtime.Extensions": "4.0.10.0",
|
||||
"System.Runtime.InteropServices": "4.0.20.0",
|
||||
"System.Threading.Tasks": "4.0.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Security;
|
||||
|
@ -18,12 +20,14 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
private const string HeaderNameExpires = "Expires";
|
||||
private const string HeaderValueNoCache = "no-cache";
|
||||
private const string HeaderValueMinusOne = "-1";
|
||||
private const string SessionIdClaim = "Microsoft.AspNet.Security.Cookies-SessionId";
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private bool _shouldRenew;
|
||||
private DateTimeOffset _renewIssuedUtc;
|
||||
private DateTimeOffset _renewExpiresUtc;
|
||||
private string _sessionKey;
|
||||
|
||||
public CookieAuthenticationHandler([NotNull] ILogger logger)
|
||||
{
|
||||
|
@ -51,12 +55,33 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
return null;
|
||||
}
|
||||
|
||||
if (Options.SessionStore != null)
|
||||
{
|
||||
Claim claim = ticket.Identity.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaim));
|
||||
if (claim == null)
|
||||
{
|
||||
_logger.WriteWarning(@"SessoinId missing");
|
||||
return null;
|
||||
}
|
||||
_sessionKey = claim.Value;
|
||||
ticket = await Options.SessionStore.RetrieveAsync(_sessionKey);
|
||||
if (ticket == null)
|
||||
{
|
||||
_logger.WriteWarning(@"Identity missing in session store");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
|
||||
DateTimeOffset? issuedUtc = ticket.Properties.IssuedUtc;
|
||||
DateTimeOffset? expiresUtc = ticket.Properties.ExpiresUtc;
|
||||
|
||||
if (expiresUtc != null && expiresUtc.Value < currentUtc)
|
||||
{
|
||||
if (Options.SessionStore != null)
|
||||
{
|
||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -95,6 +120,7 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
|
||||
if (shouldSignin || shouldSignout || _shouldRenew)
|
||||
{
|
||||
AuthenticationTicket model = await AuthenticateAsync();
|
||||
var cookieOptions = new CookieOptions
|
||||
{
|
||||
Domain = Options.CookieDomain,
|
||||
|
@ -144,7 +170,19 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
context.CookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime;
|
||||
}
|
||||
|
||||
var model = new AuthenticationTicket(context.Identity, context.Properties);
|
||||
model = new AuthenticationTicket(context.Identity, context.Properties);
|
||||
if (Options.SessionStore != null)
|
||||
{
|
||||
if (_sessionKey != null)
|
||||
{
|
||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||
}
|
||||
_sessionKey = await Options.SessionStore.StoreAsync(model);
|
||||
ClaimsIdentity identity = new ClaimsIdentity(
|
||||
new[] { new Claim(SessionIdClaim, _sessionKey) },
|
||||
Options.AuthenticationType);
|
||||
model = new AuthenticationTicket(identity, null);
|
||||
}
|
||||
string cookieValue = Options.TicketDataFormat.Protect(model);
|
||||
|
||||
Options.CookieManager.AppendResponseCookie(
|
||||
|
@ -155,6 +193,11 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
}
|
||||
else if (shouldSignout)
|
||||
{
|
||||
if (Options.SessionStore != null && _sessionKey != null)
|
||||
{
|
||||
await Options.SessionStore.RemoveAsync(_sessionKey);
|
||||
}
|
||||
|
||||
var context = new CookieResponseSignOutContext(
|
||||
Context,
|
||||
Options,
|
||||
|
@ -169,11 +212,18 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
}
|
||||
else if (_shouldRenew)
|
||||
{
|
||||
AuthenticationTicket model = await AuthenticateAsync();
|
||||
|
||||
model.Properties.IssuedUtc = _renewIssuedUtc;
|
||||
model.Properties.ExpiresUtc = _renewExpiresUtc;
|
||||
|
||||
if (Options.SessionStore != null && _sessionKey != null)
|
||||
{
|
||||
await Options.SessionStore.RenewAsync(_sessionKey, model);
|
||||
ClaimsIdentity identity = new ClaimsIdentity(
|
||||
new[] { new Claim(SessionIdClaim, _sessionKey) },
|
||||
Options.AuthenticationType);
|
||||
model = new AuthenticationTicket(identity, null);
|
||||
}
|
||||
|
||||
string cookieValue = Options.TicketDataFormat.Protect(model);
|
||||
|
||||
if (model.Properties.IsPersistent)
|
||||
|
|
|
@ -143,5 +143,11 @@ namespace Microsoft.AspNet.Security.Cookies
|
|||
/// ChunkingCookieManager will be used by default.
|
||||
/// </summary>
|
||||
public ICookieManager CookieManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional container in which to store the identity across requests. When used, only a session identifier is sent
|
||||
/// to the client. This can be used to mitigate potential problems with very large identities.
|
||||
/// </summary>
|
||||
public IAuthenticationSessionStore SessionStore { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Security.Cookies.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// This provides an abstract storage mechanic to preserve identity information on the server
|
||||
/// while only sending a simple identifier key to the client. This is most commonly used to mitigate
|
||||
/// issues with serializing large identities into cookies.
|
||||
/// </summary>
|
||||
public interface IAuthenticationSessionStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Store the identity ticket and return the associated key.
|
||||
/// </summary>
|
||||
/// <param name="ticket">The identity information to store.</param>
|
||||
/// <returns>The key that can be used to retrieve the identity later.</returns>
|
||||
Task<string> StoreAsync(AuthenticationTicket ticket);
|
||||
|
||||
/// <summary>
|
||||
/// Tells the store that the given identity should be updated.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="ticket"></param>
|
||||
/// <returns></returns>
|
||||
Task RenewAsync(string key, AuthenticationTicket ticket);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an identity from the store for the given key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key associated with the identity.</param>
|
||||
/// <returns>The identity associated with the given key, or if not found.</returns>
|
||||
Task<AuthenticationTicket> RetrieveAsync(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Remove the identity associated with the given key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key associated with the identity.</param>
|
||||
/// <returns></returns>
|
||||
Task RemoveAsync(string key);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче