#105 Use DataProtection to encrypt the cookie
This commit is contained in:
parent
627dfdc646
commit
dabd28a5d9
|
|
@ -15,10 +15,12 @@
|
||||||
"Hosting:Environment": "Development"
|
"Hosting:Environment": "Development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"web": {
|
"SessionSample": {
|
||||||
"commandName": "web",
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "http://localhost:5000/",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"Hosting:Environment": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,11 @@ namespace SessionSample
|
||||||
// o.SchemaName = "dbo";
|
// o.SchemaName = "dbo";
|
||||||
// o.TableName = "Sessions";
|
// o.TableName = "Sessions";
|
||||||
//});
|
//});
|
||||||
|
#if NET451
|
||||||
// Uncomment the following line to use the Redis implementation of IDistributedCache.
|
// Uncomment the following line to use the Redis implementation of IDistributedCache.
|
||||||
// This will override any previously registered IDistributedCache service.
|
// This will override any previously registered IDistributedCache service.
|
||||||
//services.AddSingleton<IDistributedCache, RedisCache>();
|
//services.AddSingleton<IDistributedCache, RedisCache>();
|
||||||
|
#endif
|
||||||
// Adds a default in-memory implementation of IDistributedCache
|
// Adds a default in-memory implementation of IDistributedCache
|
||||||
services.AddMemoryCache();
|
services.AddMemoryCache();
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
|
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
|
||||||
"Microsoft.AspNetCore.Session": "1.0.0-*",
|
"Microsoft.AspNetCore.Session": "1.0.0-*",
|
||||||
"Microsoft.Extensions.Caching.Memory": "1.0.0-*",
|
"Microsoft.Extensions.Caching.Memory": "1.0.0-*",
|
||||||
"Microsoft.Extensions.Caching.Redis": "1.0.0-*",
|
|
||||||
"Microsoft.Extensions.Caching.SqlServer": "1.0.0-*",
|
"Microsoft.Extensions.Caching.SqlServer": "1.0.0-*",
|
||||||
"Microsoft.Extensions.Logging.Console": "1.0.0-*"
|
"Microsoft.Extensions.Logging.Console": "1.0.0-*"
|
||||||
},
|
},
|
||||||
|
|
@ -18,7 +17,22 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net451": {}
|
"net451": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Caching.Redis": "1.0.0-*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"netcoreapp1.0": {
|
||||||
|
"imports": [
|
||||||
|
"dnxcore50"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"version": "1.0.0-*",
|
||||||
|
"type": "platform"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tools": {
|
"tools": {
|
||||||
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
|
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
// 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;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Session
|
||||||
|
{
|
||||||
|
internal static class CookieProtection
|
||||||
|
{
|
||||||
|
internal static string Protect(IDataProtector protector, string data)
|
||||||
|
{
|
||||||
|
if (protector == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(protector));
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(data))
|
||||||
|
{
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userData = Encoding.UTF8.GetBytes(data);
|
||||||
|
|
||||||
|
var protectedData = protector.Protect(userData);
|
||||||
|
return Convert.ToBase64String(protectedData).TrimEnd('=');
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string Unprotect(IDataProtector protector, string protectedText, ILogger logger)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(protectedText))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var protectedData = Convert.FromBase64String(Pad(protectedText));
|
||||||
|
if (protectedData == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userData = protector.Unprotect(protectedData);
|
||||||
|
if (userData == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(userData);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log the exception, but do not leak other information
|
||||||
|
logger.ErrorUnprotectingSessionCookie(ex);
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Pad(string text)
|
||||||
|
{
|
||||||
|
var padding = 3 - ((text.Length + 3) % 4);
|
||||||
|
if (padding == 0)
|
||||||
|
{
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return text + new string('=', padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ namespace Microsoft.Extensions.Logging
|
||||||
private static Action<ILogger, string, string, Exception> _sessionStarted;
|
private static Action<ILogger, string, string, Exception> _sessionStarted;
|
||||||
private static Action<ILogger, string, string, int, Exception> _sessionLoaded;
|
private static Action<ILogger, string, string, int, Exception> _sessionLoaded;
|
||||||
private static Action<ILogger, string, string, int, Exception> _sessionStored;
|
private static Action<ILogger, string, string, int, Exception> _sessionStored;
|
||||||
|
private static Action<ILogger, Exception> _errorUnprotectingCookie;
|
||||||
|
|
||||||
static LoggingExtensions()
|
static LoggingExtensions()
|
||||||
{
|
{
|
||||||
|
|
@ -35,6 +36,10 @@ namespace Microsoft.Extensions.Logging
|
||||||
eventId: 5,
|
eventId: 5,
|
||||||
logLevel: LogLevel.Debug,
|
logLevel: LogLevel.Debug,
|
||||||
formatString: "Session stored; Key:{sessionKey}, Id:{sessionId}, Count:{count}");
|
formatString: "Session stored; Key:{sessionKey}, Id:{sessionId}, Count:{count}");
|
||||||
|
_errorUnprotectingCookie = LoggerMessage.Define(
|
||||||
|
eventId: 6,
|
||||||
|
logLevel: LogLevel.Warning,
|
||||||
|
formatString: "Error unprotecting the session cookie.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ErrorClosingTheSession(this ILogger logger, Exception exception)
|
public static void ErrorClosingTheSession(this ILogger logger, Exception exception)
|
||||||
|
|
@ -61,5 +66,10 @@ namespace Microsoft.Extensions.Logging
|
||||||
{
|
{
|
||||||
_sessionStored(logger, sessionKey, sessionId, count, null);
|
_sessionStored(logger, sessionKey, sessionId, count, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ErrorUnprotectingSessionCookie(this ILogger logger, Exception exception)
|
||||||
|
{
|
||||||
|
_errorUnprotectingCookie(logger, exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -24,17 +25,20 @@ namespace Microsoft.AspNetCore.Session
|
||||||
private readonly SessionOptions _options;
|
private readonly SessionOptions _options;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ISessionStore _sessionStore;
|
private readonly ISessionStore _sessionStore;
|
||||||
|
private readonly IDataProtector _dataProtector;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="SessionMiddleware"/>.
|
/// Creates a new <see cref="SessionMiddleware"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
|
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
|
||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> representing the factory that used to create logger instances.</param>
|
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> representing the factory that used to create logger instances.</param>
|
||||||
|
/// <param name="dataProtectionProvider">The <see cref="IDataProtectionProvider"/> used to protect and verify the cookie.</param>
|
||||||
/// <param name="sessionStore">The <see cref="ISessionStore"/> representing the session store.</param>
|
/// <param name="sessionStore">The <see cref="ISessionStore"/> representing the session store.</param>
|
||||||
/// <param name="options">The session configuration options.</param>
|
/// <param name="options">The session configuration options.</param>
|
||||||
public SessionMiddleware(
|
public SessionMiddleware(
|
||||||
RequestDelegate next,
|
RequestDelegate next,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
|
IDataProtectionProvider dataProtectionProvider,
|
||||||
ISessionStore sessionStore,
|
ISessionStore sessionStore,
|
||||||
IOptions<SessionOptions> options)
|
IOptions<SessionOptions> options)
|
||||||
{
|
{
|
||||||
|
|
@ -48,6 +52,11 @@ namespace Microsoft.AspNetCore.Session
|
||||||
throw new ArgumentNullException(nameof(loggerFactory));
|
throw new ArgumentNullException(nameof(loggerFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dataProtectionProvider == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(dataProtectionProvider));
|
||||||
|
}
|
||||||
|
|
||||||
if (sessionStore == null)
|
if (sessionStore == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(sessionStore));
|
throw new ArgumentNullException(nameof(sessionStore));
|
||||||
|
|
@ -60,6 +69,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
|
|
||||||
_next = next;
|
_next = next;
|
||||||
_logger = loggerFactory.CreateLogger<SessionMiddleware>();
|
_logger = loggerFactory.CreateLogger<SessionMiddleware>();
|
||||||
|
_dataProtector = dataProtectionProvider.CreateProtector(nameof(SessionMiddleware));
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
_sessionStore = sessionStore;
|
_sessionStore = sessionStore;
|
||||||
}
|
}
|
||||||
|
|
@ -73,14 +83,16 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
var isNewSessionKey = false;
|
var isNewSessionKey = false;
|
||||||
Func<bool> tryEstablishSession = ReturnTrue;
|
Func<bool> tryEstablishSession = ReturnTrue;
|
||||||
string sessionKey = context.Request.Cookies[_options.CookieName];
|
var cookieValue = context.Request.Cookies[_options.CookieName];
|
||||||
|
var sessionKey = CookieProtection.Unprotect(_dataProtector, cookieValue, _logger);
|
||||||
if (string.IsNullOrWhiteSpace(sessionKey) || sessionKey.Length != SessionKeyLength)
|
if (string.IsNullOrWhiteSpace(sessionKey) || sessionKey.Length != SessionKeyLength)
|
||||||
{
|
{
|
||||||
// No valid cookie, new session.
|
// No valid cookie, new session.
|
||||||
var guidBytes = new byte[16];
|
var guidBytes = new byte[16];
|
||||||
CryptoRandom.GetBytes(guidBytes);
|
CryptoRandom.GetBytes(guidBytes);
|
||||||
sessionKey = new Guid(guidBytes).ToString();
|
sessionKey = new Guid(guidBytes).ToString();
|
||||||
var establisher = new SessionEstablisher(context, sessionKey, _options);
|
cookieValue = CookieProtection.Protect(_dataProtector, sessionKey);
|
||||||
|
var establisher = new SessionEstablisher(context, cookieValue, _options);
|
||||||
tryEstablishSession = establisher.TryEstablishSession;
|
tryEstablishSession = establisher.TryEstablishSession;
|
||||||
isNewSessionKey = true;
|
isNewSessionKey = true;
|
||||||
}
|
}
|
||||||
|
|
@ -114,14 +126,14 @@ namespace Microsoft.AspNetCore.Session
|
||||||
private class SessionEstablisher
|
private class SessionEstablisher
|
||||||
{
|
{
|
||||||
private readonly HttpContext _context;
|
private readonly HttpContext _context;
|
||||||
private readonly string _sessionKey;
|
private readonly string _cookieValue;
|
||||||
private readonly SessionOptions _options;
|
private readonly SessionOptions _options;
|
||||||
private bool _shouldEstablishSession;
|
private bool _shouldEstablishSession;
|
||||||
|
|
||||||
public SessionEstablisher(HttpContext context, string sessionKey, SessionOptions options)
|
public SessionEstablisher(HttpContext context, string cookieValue, SessionOptions options)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_sessionKey = sessionKey;
|
_cookieValue = cookieValue;
|
||||||
_options = options;
|
_options = options;
|
||||||
context.Response.OnStarting(OnStartingCallback, state: this);
|
context.Response.OnStarting(OnStartingCallback, state: this);
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +157,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
Path = _options.CookiePath ?? SessionDefaults.CookiePath,
|
Path = _options.CookiePath ?? SessionDefaults.CookiePath,
|
||||||
};
|
};
|
||||||
|
|
||||||
_context.Response.Cookies.Append(_options.CookieName, _sessionKey, cookieOptions);
|
_context.Response.Cookies.Append(_options.CookieName, _cookieValue, cookieOptions);
|
||||||
|
|
||||||
_context.Response.Headers["Cache-Control"] = "no-cache";
|
_context.Response.Headers["Cache-Control"] = "no-cache";
|
||||||
_context.Response.Headers["Pragma"] = "no-cache";
|
_context.Response.Headers["Pragma"] = "no-cache";
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
}
|
}
|
||||||
|
|
||||||
services.AddTransient<ISessionStore, DistributedSessionStore>();
|
services.AddTransient<ISessionStore, DistributedSessionStore>();
|
||||||
|
services.AddDataProtection();
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"Microsoft.AspNetCore.DataProtection": "1.0.0-*",
|
||||||
"Microsoft.AspNetCore.Http.Abstractions": "1.0.0-*",
|
"Microsoft.AspNetCore.Http.Abstractions": "1.0.0-*",
|
||||||
"Microsoft.Extensions.Caching.Abstractions": "1.0.0-*",
|
"Microsoft.Extensions.Caching.Abstractions": "1.0.0-*",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
|
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,8 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
</Project>
|
</Project>
|
||||||
Loading…
Reference in New Issue