ASP.NET Identity,持久性cookie

编程入门 行业动态 更新时间:2024-10-22 03:00:48
ASP.NET Identity,持久性cookie - 就像这样的内置?(ASP.NET Identity, persistent cookie - is something like this build in?)

我们正在使用CookieAuthenticationProvider,并希望在我们的应用程序中实现这样的“记住我”功能:

无论是否选中“记住我”复选框, 令牌到期时间应始终设置为30分钟 (打开SlidingExpiration)

如果用户没有选中“记住我”,我们所做的就是检查令牌是否过期 - 如果是,那么用户被重定向到登录界面(这是内置到OWIN并且工作正常)

但是,如果用户选中“记住我”,他的凭据应保存在附加 cookie中(默认生命周期为30天)。 如果他的令牌过期(超时仍应设置为30分钟), OWIN应使用该附加cookie在后台自动续订令牌 。 换句话说 - 如果用户检查'记住我',他应该登录30天或直到他退出。

问题是 - 如何用OWIN完成这样的事情? 据我所知,默认实现仍然使用ExpireTimeSpan参数 - 唯一的区别是,cookie被标记为持久性,因此如果用户重新启动浏览器,他将登录 - 但令牌到期仍然受ExpireTimeSpan的限制。

我想我必须以某种方式在SignIn期间手动保存用户凭据并覆盖OnApplyRedirect事件(这似乎是未经授权的用户尝试访问需要授权的视图时触发的唯一事件),而不是重定向,以某种方式重新生成用户的令牌......但有人知道究竟是怎么做到的吗?

We are using CookieAuthenticationProvider and would like to implement the 'Remember me' functionality in our application that would work like this:

No matter if the 'Remember me' checkbox is checked or not, the token expiration time should always be set to 30 minutes (with SlidingExpiration turned on)

If user doesn't check 'Remember me' all we do is check if token expired - if it did, then user is redirected to login screen (this is build in into OWIN and works fine)

However if user checks 'Remember me' his credentials should be saved in the additional cookie (with default lifetime of 30 days). If his token expires (the timeout should still be set to 30 minutes), OWIN should use that additional cookie to renew the token automatically in the background. So in other words - if user check 'Remember me' he should be logged in for 30 days or until he logs out.

Question is - how can something like this be done with OWIN? As far as I can see, the default implementation still uses ExpireTimeSpan parameter - the only difference is, that the cookie is marked as persistent, so if user restarts browser he is logged in - but token expiration is still limited by ExpireTimeSpan.

I guess I have to somehow manually save user credentials during the SignIn and override the OnApplyRedirect event (that seems to be the only event fired if an unauthorized user tries to access a view that requires authorization), and instead of redirecting, somehow regenerate user's token... but does anybody know how exactly to do that?

最满意答案

最后,我最终编写了自定义中间件并将其插入:

RememberMeTokenMiddleware.cs:

using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using System.Web; using System.Web.Security; using WebApplicationtoRemove.Owin.HelperClasses; using Microsoft.AspNet.Identity.Owin; namespace WebApplicationtoRemove.Owin.Middleware { public class RememberMeTokenMiddleware : OwinMiddleware { #region Private Members private static double RememberMeTokenPeriodOfvalidityInMinutes = 43200; private IOwinContext Context { get; set; } #endregion #region Public Static Members #endregion #region Constructor public RememberMeTokenMiddleware(OwinMiddleware next) : base(next) { } public RememberMeTokenMiddleware(OwinMiddleware next, double RememberMeTokenPeriodOfvalidityInMinutes) : base(next) { RememberMeTokenMiddleware.RememberMeTokenPeriodOfvalidityInMinutes = RememberMeTokenPeriodOfvalidityInMinutes; } #endregion #region Public Methods public override async Task Invoke(IOwinContext context) { try { Context = context; bool shouldDeleteRememberMeToken = CheckIfRememberMeTokenShouldBeDeleted(context); if (shouldDeleteRememberMeToken) { context.Response.Cookies.Delete("RemoveRememberMeToken"); context.Response.Cookies.Delete("RememberMeToken"); } else { if (context.Authentication.User == null || !context.Authentication.User.Identity.IsAuthenticated) { //User is either not set or is not authenticated - try to log him in, using the RememberMeCookie Login(context); } } } catch (Exception ex) { //Something went wrong - we assume that cookie and/or token was damaged and should be deleted context.Response.Cookies.Delete("RememberMeToken"); } await this.Next.Invoke(context); } #endregion #region Static Methods /// <summary> /// Check conditions and creates RememberMeToken cookie if necessary. This should be called inside SidnedIn event of CookieProvider /// </summary> public static void CheckAndCreateRememberMeToken(CookieResponseSignedInContext ctx) { try { bool signedInFromRememberMeToken = CheckIfUserWasSignedInFromRememberMeToken(ctx.OwinContext); if (!signedInFromRememberMeToken && ctx.Properties.IsPersistent) { //Login occured using 'normal' path and IsPersistant was set - generate RememberMeToken cookie var claimsToAdd = GenerateSerializableClaimListFromIdentity(ctx.Identity); SerializableClaim cookieExpirationDate = GenerateRememberMeTokenExpirationDateClaim(); claimsToAdd.Add(cookieExpirationDate); var allClaimsInFinalCompressedAndProtectedBase64Token = GenerateProtectedAndBase64EncodedClaimsToken(claimsToAdd); ctx.Response.Cookies.Append("RememberMeToken", allClaimsInFinalCompressedAndProtectedBase64Token, new CookieOptions() { Expires = DateTime.Now.AddMinutes(RememberMeTokenPeriodOfvalidityInMinutes) }); //Remove the SignedInFromRememberMeToken cookie, to let the middleware know, that user was signed in using normal path ctx.OwinContext.Set("SignedInFromRememberMeToken", false); } } catch (Exception ex) { //Log errors using your favorite logger here } } /// <summary> /// User logged out - sets information (using cookie) for RememberMeTokenMiddleware that RememberMeToken should be removed /// </summary> public static void Logout(IOwinContext ctx) { ctx.Response.Cookies.Append("RemoveRememberMeToken", ""); } #endregion #region Private Methods /// <summary> /// Returns information if user was signed in from RememberMeToken cookie - this information should be used to determine if RememberMeToken lifetime should be regenerated or not (it should be, if user signed in using normal path) /// </summary> private static bool CheckIfUserWasSignedInFromRememberMeToken(IOwinContext ctx) { bool signedInFromRememberMeToken = ctx.Get<bool>("SignedInFromRememberMeToken"); return signedInFromRememberMeToken; } /// <summary> /// Generates serializable collection of user claims, that will be saved inside the cookie token. Custom class is used because Claim class causes 'Circular Reference Exception.' /// </summary> private static List<SerializableClaim> GenerateSerializableClaimListFromIdentity(ClaimsIdentity identity) { var dataToReturn = identity.Claims.Select(x => new SerializableClaim() { Type = x.Type, ValueType = x.ValueType, Value = x.Value }).ToList(); return dataToReturn; } /// <summary> /// Generates a special claim containing an expiration date of RememberMeToken cookie. This is necessary because we CANNOT rely on browsers here - since each one threat cookies differently /// </summary> private static SerializableClaim GenerateRememberMeTokenExpirationDateClaim() { SerializableClaim cookieExpirationDate = new SerializableClaim() { Type = "RememberMeTokenExpirationDate", Value = DateTime.Now.AddMinutes(RememberMeTokenPeriodOfvalidityInMinutes).ToBinary().ToString() }; return cookieExpirationDate; } /// <summary> /// Generates token containing user claims. The token is compressed, encrypted using machine key and returned as base64 string - this string will be saved inside RememberMeToken cookie /// </summary> private static string GenerateProtectedAndBase64EncodedClaimsToken(List<SerializableClaim> claimsToAdd) { var allClaimsAsString = JsonConvert.SerializeObject(claimsToAdd); var allClaimsAsBytes = Encoding.UTF8.GetBytes(allClaimsAsString); var allClaimsAsCompressedBytes = CompressionHelper.CompressDeflate(allClaimsAsBytes); var allClaimsAsCompressedBytesProtected = MachineKey.Protect(allClaimsAsCompressedBytes, "RememberMeToken"); var allClaimsInFinalCompressedAndProtectedBase64Token = Convert.ToBase64String(allClaimsAsCompressedBytesProtected); return allClaimsInFinalCompressedAndProtectedBase64Token; } /// <summary> /// Primary login method /// </summary> private void Login(IOwinContext context) { var base64ProtectedCompressedRememberMeTokenBytes = context.Request.Cookies["RememberMeToken"]; if (!string.IsNullOrEmpty(base64ProtectedCompressedRememberMeTokenBytes)) { var RememberMeToken = GetRememberMeTokenFromData(base64ProtectedCompressedRememberMeTokenBytes); var claims = JsonConvert.DeserializeObject<IEnumerable<SerializableClaim>>(RememberMeToken); bool isRememberMeTokenStillValid = IsRememberMeTokenStillValid(claims); if (isRememberMeTokenStillValid) { //Token is still valid - sign in SignInUser(context, claims); //We set information that user was signed in using the RememberMeToken cookie context.Set("SignedInFromRememberMeToken", true); } else { //Token is invalid or expired - we remove unnecessary cookie context.Response.Cookies.Delete("RememberMeToken"); } } } /// <summary> /// We log user, using passed claims /// </summary> private void SignInUser(IOwinContext context, IEnumerable<SerializableClaim> claims) { List<Claim> claimList = new List<Claim>(); foreach (var item in claims) { string type = item.Type; string value = item.Value; claimList.Add(new Claim(type, value)); } ClaimsIdentity ci = new ClaimsIdentity(claimList, DefaultAuthenticationTypes.ApplicationCookie); context.Authentication.SignIn(ci); context.Authentication.User = context.Authentication.AuthenticationResponseGrant.Principal; } /// <summary> /// Get information if RememberMeToken cookie is still valid (checks not only the date, but also some additional information) /// </summary> private bool IsRememberMeTokenStillValid(IEnumerable<SerializableClaim> claims) { var userIdClaim = claims.Where(x => x.Type == ClaimTypes.NameIdentifier).SingleOrDefault(); if (userIdClaim == null) { throw new Exception("RememberMeTokenAuthMiddleware. Claim of type NameIdentifier was not found."); } var userSecurityStampClaim = claims.Where(x => x.Type == "AspNet.Identity.SecurityStamp").SingleOrDefault(); if (userSecurityStampClaim == null) { throw new Exception("RememberMeTokenAuthMiddleware. Claim of type SecurityStamp was not found."); } string userId = userIdClaim.Value; var userManager = Context.GetUserManager<ApplicationUserManager>(); if (userManager == null) { throw new Exception("RememberMeTokenAuthMiddleware. Unable to get UserManager"); } var currentUserData = userManager.FindById(userId); if (currentUserData == null) { return false; } if (currentUserData.LockoutEndDateUtc >= DateTime.Now) { return false; } if (currentUserData.SecurityStamp != userSecurityStampClaim.Value) { //User Securitystamp was changed return false; } return GetRememberMeTokenExpirationMinutesLeft(claims) > 0; } /// <summary> /// Returns how many minutes the RememberMeToken will be valid - if it expired, returns zero or negative value /// </summary> private double GetRememberMeTokenExpirationMinutesLeft(IEnumerable<SerializableClaim> claims) { double dataToReturn = -1; var RememberMeTokenExpirationDate = GetRememberMeTokenExpirationDate(claims); dataToReturn = (RememberMeTokenExpirationDate - DateTime.Now).TotalMinutes; return dataToReturn; } /// <summary> /// Returns a DateTime object containing the expiration date of the RememberMeToken /// </summary> private DateTime GetRememberMeTokenExpirationDate(IEnumerable<SerializableClaim> claims) { DateTime RememberMeTokenExpirationDate = DateTime.Now.AddDays(-1); var RememberMeTokenExpirationClaim = GetRememberMeTokenExpirationDateClaim(claims); if (RememberMeTokenExpirationClaim == null) { throw new Exception("RememberMeTokenAuthMiddleware. RememberMeTokenExpirationClaim was not found."); } long binaryTime = Convert.ToInt64(RememberMeTokenExpirationClaim.Value); RememberMeTokenExpirationDate = DateTime.FromBinary(binaryTime); return RememberMeTokenExpirationDate; } /// <summary> /// Returns the claim determining the expiration date of the token /// </summary> private SerializableClaim GetRememberMeTokenExpirationDateClaim(IEnumerable<SerializableClaim> claims) { var RememberMeTokenExpirationClaim = claims.Where(x => x.Type == "RememberMeTokenExpirationDate").SingleOrDefault(); return RememberMeTokenExpirationClaim; } /// <summary> /// Attempts to decipher the RememberMeToken to the JSON format containing claims /// </summary> private string GetRememberMeTokenFromData(string base64ProtectedCompressedRememberMeTokenBytes) { var protectedCompressedRememberMeTokenBytes = Convert.FromBase64String(base64ProtectedCompressedRememberMeTokenBytes); var compressedRememberMeTokenBytes = MachineKey.Unprotect(protectedCompressedRememberMeTokenBytes, "RememberMeToken"); var RememberMeTokenBytes = CompressionHelper.DecompressDeflate(compressedRememberMeTokenBytes); var RememberMeToken = Encoding.UTF8.GetString(RememberMeTokenBytes); return RememberMeToken; } /// <summary> /// Returns information if token cookie should be delated (for example, when user click 'Logout') /// </summary> private bool CheckIfRememberMeTokenShouldBeDeleted(IOwinContext context) { bool shouldDeleteRememberMeToken = (context.Request.Cookies.Where(x => x.Key == "RemoveRememberMeToken").Count() > 0); return shouldDeleteRememberMeToken; } #endregion } }

还有一些辅助类: CompressionHelper.cs:

using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Web; namespace WebApplicationtoRemove.Owin.HelperClasses { /// <summary> /// Data compression helper /// </summary> public static class CompressionHelper { public static byte[] CompressDeflate(byte[] data) { MemoryStream output = new MemoryStream(); using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal)) { dstream.Write(data, 0, data.Length); } return output.ToArray(); } public static byte[] DecompressDeflate(byte[] data) { MemoryStream input = new MemoryStream(data); MemoryStream output = new MemoryStream(); using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress)) { dstream.CopyTo(output); } return output.ToArray(); } } }

SerializableClaim.cs:

using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace WebApplicationtoRemove.Owin.HelperClasses { public class SerializableClaim { public string Type { get; set; } public string ValueType { get; set; } public string Value { get; set; } } }

要测试上述内容 - 创建新的MVC 4.6.x项目( 身份验证模式:个人用户帐户 ),将上面的类添加到其中,然后修改Startup.Auth.cs:

using System; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Google; using Owin; using WebApplicationtoRemove.Models; using WebApplicationtoRemove.Owin.Middleware; namespace WebApplicationtoRemove { public partial class Startup { // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864 public void ConfigureAuth(IAppBuilder app) { // Configure the db context, user manager and signin manager to use a single instance per request app.CreatePerOwinContext(ApplicationDbContext.Create); app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create); // Enable the application to use a cookie to store information for the signed in user // and to use a cookie to temporarily store information about a user logging in with a third party login provider // Configure the sign in cookie app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { // Enables the application to validate the security stamp when the user logs in. // This is a security feature which is used when you change a password or add an external login to your account. OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)), OnResponseSignedIn = ctx => { RememberMeTokenMiddleware.CheckAndCreateRememberMeToken(ctx); }, OnResponseSignOut = ctx => { RememberMeTokenMiddleware.Logout(ctx.OwinContext); } } }); app.Use<RememberMeTokenMiddleware>(); } } }

你有兴趣的是这些:

OnResponseSignedIn = ctx => { RememberMeTokenMiddleware.CheckAndCreateRememberMeToken(ctx); }, OnResponseSignOut = ctx => { RememberMeTokenMiddleware.Logout(ctx.OwinContext); }

这一行:

app.Use<RememberMeTokenMiddleware>();

这应该启用中间件。 如何工作:如果用户选中“ 记住我 ”复选框, 将创建一个RememberMeToken cookie (包含用户在登录期间拥有的所有声明)以及“AspNet.ApplicationCookie”。

当会话超时时, 中间件将检查RememberMeToken是否存在,并且仍然有效 - 如果是这样:它将在后台无缝登录用户。

希望这有助于任何人。

Finally, I ended up writing custom middleware and plugging it in:

RememberMeTokenMiddleware.cs:

using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using System.Web; using System.Web.Security; using WebApplicationtoRemove.Owin.HelperClasses; using Microsoft.AspNet.Identity.Owin; namespace WebApplicationtoRemove.Owin.Middleware { public class RememberMeTokenMiddleware : OwinMiddleware { #region Private Members private static double RememberMeTokenPeriodOfvalidityInMinutes = 43200; private IOwinContext Context { get; set; } #endregion #region Public Static Members #endregion #region Constructor public RememberMeTokenMiddleware(OwinMiddleware next) : base(next) { } public RememberMeTokenMiddleware(OwinMiddleware next, double RememberMeTokenPeriodOfvalidityInMinutes) : base(next) { RememberMeTokenMiddleware.RememberMeTokenPeriodOfvalidityInMinutes = RememberMeTokenPeriodOfvalidityInMinutes; } #endregion #region Public Methods public override async Task Invoke(IOwinContext context) { try { Context = context; bool shouldDeleteRememberMeToken = CheckIfRememberMeTokenShouldBeDeleted(context); if (shouldDeleteRememberMeToken) { context.Response.Cookies.Delete("RemoveRememberMeToken"); context.Response.Cookies.Delete("RememberMeToken"); } else { if (context.Authentication.User == null || !context.Authentication.User.Identity.IsAuthenticated) { //User is either not set or is not authenticated - try to log him in, using the RememberMeCookie Login(context); } } } catch (Exception ex) { //Something went wrong - we assume that cookie and/or token was damaged and should be deleted context.Response.Cookies.Delete("RememberMeToken"); } await this.Next.Invoke(context); } #endregion #region Static Methods /// <summary> /// Check conditions and creates RememberMeToken cookie if necessary. This should be called inside SidnedIn event of CookieProvider /// </summary> public static void CheckAndCreateRememberMeToken(CookieResponseSignedInContext ctx) { try { bool signedInFromRememberMeToken = CheckIfUserWasSignedInFromRememberMeToken(ctx.OwinContext); if (!signedInFromRememberMeToken && ctx.Properties.IsPersistent) { //Login occured using 'normal' path and IsPersistant was set - generate RememberMeToken cookie var claimsToAdd = GenerateSerializableClaimListFromIdentity(ctx.Identity); SerializableClaim cookieExpirationDate = GenerateRememberMeTokenExpirationDateClaim(); claimsToAdd.Add(cookieExpirationDate); var allClaimsInFinalCompressedAndProtectedBase64Token = GenerateProtectedAndBase64EncodedClaimsToken(claimsToAdd); ctx.Response.Cookies.Append("RememberMeToken", allClaimsInFinalCompressedAndProtectedBase64Token, new CookieOptions() { Expires = DateTime.Now.AddMinutes(RememberMeTokenPeriodOfvalidityInMinutes) }); //Remove the SignedInFromRememberMeToken cookie, to let the middleware know, that user was signed in using normal path ctx.OwinContext.Set("SignedInFromRememberMeToken", false); } } catch (Exception ex) { //Log errors using your favorite logger here } } /// <summary> /// User logged out - sets information (using cookie) for RememberMeTokenMiddleware that RememberMeToken should be removed /// </summary> public static void Logout(IOwinContext ctx) { ctx.Response.Cookies.Append("RemoveRememberMeToken", ""); } #endregion #region Private Methods /// <summary> /// Returns information if user was signed in from RememberMeToken cookie - this information should be used to determine if RememberMeToken lifetime should be regenerated or not (it should be, if user signed in using normal path) /// </summary> private static bool CheckIfUserWasSignedInFromRememberMeToken(IOwinContext ctx) { bool signedInFromRememberMeToken = ctx.Get<bool>("SignedInFromRememberMeToken"); return signedInFromRememberMeToken; } /// <summary> /// Generates serializable collection of user claims, that will be saved inside the cookie token. Custom class is used because Claim class causes 'Circular Reference Exception.' /// </summary> private static List<SerializableClaim> GenerateSerializableClaimListFromIdentity(ClaimsIdentity identity) { var dataToReturn = identity.Claims.Select(x => new SerializableClaim() { Type = x.Type, ValueType = x.ValueType, Value = x.Value }).ToList(); return dataToReturn; } /// <summary> /// Generates a special claim containing an expiration date of RememberMeToken cookie. This is necessary because we CANNOT rely on browsers here - since each one threat cookies differently /// </summary> private static SerializableClaim GenerateRememberMeTokenExpirationDateClaim() { SerializableClaim cookieExpirationDate = new SerializableClaim() { Type = "RememberMeTokenExpirationDate", Value = DateTime.Now.AddMinutes(RememberMeTokenPeriodOfvalidityInMinutes).ToBinary().ToString() }; return cookieExpirationDate; } /// <summary> /// Generates token containing user claims. The token is compressed, encrypted using machine key and returned as base64 string - this string will be saved inside RememberMeToken cookie /// </summary> private static string GenerateProtectedAndBase64EncodedClaimsToken(List<SerializableClaim> claimsToAdd) { var allClaimsAsString = JsonConvert.SerializeObject(claimsToAdd); var allClaimsAsBytes = Encoding.UTF8.GetBytes(allClaimsAsString); var allClaimsAsCompressedBytes = CompressionHelper.CompressDeflate(allClaimsAsBytes); var allClaimsAsCompressedBytesProtected = MachineKey.Protect(allClaimsAsCompressedBytes, "RememberMeToken"); var allClaimsInFinalCompressedAndProtectedBase64Token = Convert.ToBase64String(allClaimsAsCompressedBytesProtected); return allClaimsInFinalCompressedAndProtectedBase64Token; } /// <summary> /// Primary login method /// </summary> private void Login(IOwinContext context) { var base64ProtectedCompressedRememberMeTokenBytes = context.Request.Cookies["RememberMeToken"]; if (!string.IsNullOrEmpty(base64ProtectedCompressedRememberMeTokenBytes)) { var RememberMeToken = GetRememberMeTokenFromData(base64ProtectedCompressedRememberMeTokenBytes); var claims = JsonConvert.DeserializeObject<IEnumerable<SerializableClaim>>(RememberMeToken); bool isRememberMeTokenStillValid = IsRememberMeTokenStillValid(claims); if (isRememberMeTokenStillValid) { //Token is still valid - sign in SignInUser(context, claims); //We set information that user was signed in using the RememberMeToken cookie context.Set("SignedInFromRememberMeToken", true); } else { //Token is invalid or expired - we remove unnecessary cookie context.Response.Cookies.Delete("RememberMeToken"); } } } /// <summary> /// We log user, using passed claims /// </summary> private void SignInUser(IOwinContext context, IEnumerable<SerializableClaim> claims) { List<Claim> claimList = new List<Claim>(); foreach (var item in claims) { string type = item.Type; string value = item.Value; claimList.Add(new Claim(type, value)); } ClaimsIdentity ci = new ClaimsIdentity(claimList, DefaultAuthenticationTypes.ApplicationCookie); context.Authentication.SignIn(ci); context.Authentication.User = context.Authentication.AuthenticationResponseGrant.Principal; } /// <summary> /// Get information if RememberMeToken cookie is still valid (checks not only the date, but also some additional information) /// </summary> private bool IsRememberMeTokenStillValid(IEnumerable<SerializableClaim> claims) { var userIdClaim = claims.Where(x => x.Type == ClaimTypes.NameIdentifier).SingleOrDefault(); if (userIdClaim == null) { throw new Exception("RememberMeTokenAuthMiddleware. Claim of type NameIdentifier was not found."); } var userSecurityStampClaim = claims.Where(x => x.Type == "AspNet.Identity.SecurityStamp").SingleOrDefault(); if (userSecurityStampClaim == null) { throw new Exception("RememberMeTokenAuthMiddleware. Claim of type SecurityStamp was not found."); } string userId = userIdClaim.Value; var userManager = Context.GetUserManager<ApplicationUserManager>(); if (userManager == null) { throw new Exception("RememberMeTokenAuthMiddleware. Unable to get UserManager"); } var currentUserData = userManager.FindById(userId); if (currentUserData == null) { return false; } if (currentUserData.LockoutEndDateUtc >= DateTime.Now) { return false; } if (currentUserData.SecurityStamp != userSecurityStampClaim.Value) { //User Securitystamp was changed return false; } return GetRememberMeTokenExpirationMinutesLeft(claims) > 0; } /// <summary> /// Returns how many minutes the RememberMeToken will be valid - if it expired, returns zero or negative value /// </summary> private double GetRememberMeTokenExpirationMinutesLeft(IEnumerable<SerializableClaim> claims) { double dataToReturn = -1; var RememberMeTokenExpirationDate = GetRememberMeTokenExpirationDate(claims); dataToReturn = (RememberMeTokenExpirationDate - DateTime.Now).TotalMinutes; return dataToReturn; } /// <summary> /// Returns a DateTime object containing the expiration date of the RememberMeToken /// </summary> private DateTime GetRememberMeTokenExpirationDate(IEnumerable<SerializableClaim> claims) { DateTime RememberMeTokenExpirationDate = DateTime.Now.AddDays(-1); var RememberMeTokenExpirationClaim = GetRememberMeTokenExpirationDateClaim(claims); if (RememberMeTokenExpirationClaim == null) { throw new Exception("RememberMeTokenAuthMiddleware. RememberMeTokenExpirationClaim was not found."); } long binaryTime = Convert.ToInt64(RememberMeTokenExpirationClaim.Value); RememberMeTokenExpirationDate = DateTime.FromBinary(binaryTime); return RememberMeTokenExpirationDate; } /// <summary> /// Returns the claim determining the expiration date of the token /// </summary> private SerializableClaim GetRememberMeTokenExpirationDateClaim(IEnumerable<SerializableClaim> claims) { var RememberMeTokenExpirationClaim = claims.Where(x => x.Type == "RememberMeTokenExpirationDate").SingleOrDefault(); return RememberMeTokenExpirationClaim; } /// <summary> /// Attempts to decipher the RememberMeToken to the JSON format containing claims /// </summary> private string GetRememberMeTokenFromData(string base64ProtectedCompressedRememberMeTokenBytes) { var protectedCompressedRememberMeTokenBytes = Convert.FromBase64String(base64ProtectedCompressedRememberMeTokenBytes); var compressedRememberMeTokenBytes = MachineKey.Unprotect(protectedCompressedRememberMeTokenBytes, "RememberMeToken"); var RememberMeTokenBytes = CompressionHelper.DecompressDeflate(compressedRememberMeTokenBytes); var RememberMeToken = Encoding.UTF8.GetString(RememberMeTokenBytes); return RememberMeToken; } /// <summary> /// Returns information if token cookie should be delated (for example, when user click 'Logout') /// </summary> private bool CheckIfRememberMeTokenShouldBeDeleted(IOwinContext context) { bool shouldDeleteRememberMeToken = (context.Request.Cookies.Where(x => x.Key == "RemoveRememberMeToken").Count() > 0); return shouldDeleteRememberMeToken; } #endregion } }

And some helper classes: CompressionHelper.cs:

using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Web; namespace WebApplicationtoRemove.Owin.HelperClasses { /// <summary> /// Data compression helper /// </summary> public static class CompressionHelper { public static byte[] CompressDeflate(byte[] data) { MemoryStream output = new MemoryStream(); using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal)) { dstream.Write(data, 0, data.Length); } return output.ToArray(); } public static byte[] DecompressDeflate(byte[] data) { MemoryStream input = new MemoryStream(data); MemoryStream output = new MemoryStream(); using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress)) { dstream.CopyTo(output); } return output.ToArray(); } } }

SerializableClaim.cs:

using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace WebApplicationtoRemove.Owin.HelperClasses { public class SerializableClaim { public string Type { get; set; } public string ValueType { get; set; } public string Value { get; set; } } }

To test the above - create new MVC 4.6.x project (authentication mode: Individual User Accounts), add the above classes to it and then modify the Startup.Auth.cs:

using System; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Google; using Owin; using WebApplicationtoRemove.Models; using WebApplicationtoRemove.Owin.Middleware; namespace WebApplicationtoRemove { public partial class Startup { // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864 public void ConfigureAuth(IAppBuilder app) { // Configure the db context, user manager and signin manager to use a single instance per request app.CreatePerOwinContext(ApplicationDbContext.Create); app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create); // Enable the application to use a cookie to store information for the signed in user // and to use a cookie to temporarily store information about a user logging in with a third party login provider // Configure the sign in cookie app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { // Enables the application to validate the security stamp when the user logs in. // This is a security feature which is used when you change a password or add an external login to your account. OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)), OnResponseSignedIn = ctx => { RememberMeTokenMiddleware.CheckAndCreateRememberMeToken(ctx); }, OnResponseSignOut = ctx => { RememberMeTokenMiddleware.Logout(ctx.OwinContext); } } }); app.Use<RememberMeTokenMiddleware>(); } } }

What interests you there are these:

OnResponseSignedIn = ctx => { RememberMeTokenMiddleware.CheckAndCreateRememberMeToken(ctx); }, OnResponseSignOut = ctx => { RememberMeTokenMiddleware.Logout(ctx.OwinContext); }

and this line:

app.Use<RememberMeTokenMiddleware>();

This should enable the middleware. How this works: if the user checks 'Remember me' checkbox, a RememberMeToken cookie will be created (containing all the claims user had during login) alongside the 'AspNet.ApplicationCookie'.

When the session times out, the middleware will check if the RememberMeToken exists, and is still valid - if so: it will log in the user seamlessly in background.

Hope this helps anyone.

更多推荐

本文发布于:2023-07-06 07:33:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1047356.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:持久性   NET   ASP   cookie   Identity

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!