#32 - Port cookie auth session store from Katana. Add MemoryCache sample.

This commit is contained in:
Chris Ross 2014-09-09 15:25:39 -07:00
Родитель f6d6f31414
Коммит d6b82b8799
9 изменённых файлов: 283 добавлений и 5 удалений

Просмотреть файл

@ -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);
}
}