我正在尝试了解cookie中间件身份验证行为及其流程。但是,我无法理解这一点。以下是我的问题
我已经实现了记住我的功能,它创建了cookie,所以在这里我需要使用Cookie中间件或不是吗?,为此我使用下面的代码
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
那么,创建cookie 1 OR 2点的首选方法是什么?
对此有任何帮助表示赞赏!
答案 0 :(得分:2)
此解决方案由多个文件组成:
身份验证中间件:
public class CustomAuthentication
{
private readonly RequestDelegate _next;
private readonly ISecurityManager _securityManager = null;
private readonly List<string> _publiclyAccessiblePaths = new List<string> {
"security/forgotpassword",
"security/resetpassword"
};
public BBAuthentication(RequestDelegate next, ISecurityManager securityManager)
{
this._next = next;
this._securityManager = securityManager;
}
public async Task Invoke(HttpContext context)
{
bool authenticated = await _securityManager.ProcessSecurityContext(context);
bool containsLoginPath = context.Request.Path.Value.Trim().ToLower().Contains("security/login");
bool containsPublicPaths = _publiclyAccessiblePaths.Any(x => context.Request.Path.Value.Trim().ToLower().Contains(x));
bool blockPipeline = false;
if(!containsPublicPaths) {
if (authenticated) {
if (containsLoginPath) {
// If the user is authenticated and requests login page, redirect them to home (disallow accessing login page if authenticated)
context.Response.StatusCode = 302;
context.Response.Redirect("/");
blockPipeline = true;
}
} else {
if (!containsLoginPath) {
context.Response.StatusCode = 401;
context.Response.Redirect("/security/login");
blockPipeline = true;
}
}
}
if (!blockPipeline)
await _next(context);
}
}
ISecurityManager界面:
public interface ISecurityManager
{
void Login(HttpContext context, UserMetadata userMetadata,bool persistent);
void Logout(HttpContext context);
Task<bool> ProcessSecurityContext(HttpContext context);
}
加密库:
internal class EncryptionLib
{
/// <summary>
/// Encrypt the given string using AES. The string can be decrypted using
/// DecryptStringAES(). The sharedSecret parameters must match.
/// </summary>
/// <param name="plainText">The text to encrypt.</param>
/// <param name="sharedSecret">A password used to generate a key for encryption.</param>
/// <param name="salt">The key salt used to derive the key.</param>
/// <exception cref="ArgumentNullException">Text is null or empty.</exception>
/// <exception cref="ArgumentNullException">Password is null or empty.</exception>
public static string EncryptStringAES(string plainText, string sharedSecret, byte[] salt)
{
if (string.IsNullOrEmpty(plainText))
throw new ArgumentNullException("plainText", "Text is null or empty.");
if (string.IsNullOrEmpty(sharedSecret))
throw new ArgumentNullException("sharedSecret", "Password is null or empty.");
string outStr = null;
try
{
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, salt);
using (var aesAlg = new RijndaelManaged())
{
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8);
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
swEncrypt.Write(plainText);
outStr = Convert.ToBase64String(msEncrypt.ToArray());
}
}
}
catch { }
return outStr;
}
/// <summary>
/// Decrypt the given string. Assumes the string was encrypted using
/// EncryptStringAES(), using an identical sharedSecret.
/// </summary>
/// <param name="cipherText">The text to decrypt.</param>
/// <param name="sharedSecret">A password used to generate a key for decryption.</param>
/// <param name="salt">The key salt used to derive the key.</param>
/// <exception cref="ArgumentNullException">Text is null or empty.</exception>
/// <exception cref="ArgumentNullException">Password is null or empty.</exception>
public static string DecryptStringAES(string cipherText, string sharedSecret, byte[] salt)
{
if (string.IsNullOrEmpty(cipherText))
throw new ArgumentNullException("cipherText", "Text is null or empty.");
if (string.IsNullOrEmpty(sharedSecret))
throw new ArgumentNullException("sharedSecret", "Password is null or empty.");
string plaintext = null;
try
{
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, salt);
using (var aesAlg = new RijndaelManaged())
{
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8);
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
byte[] bytes = Convert.FromBase64String(cipherText);
using (MemoryStream msDecrypt = new MemoryStream(bytes))
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
plaintext = srDecrypt.ReadToEnd();
}
}
catch { }
return plaintext;
}
public static string HashSHA1(string plainText)
{
using (var sha = SHA1Managed.Create())
{
return Convert.ToBase64String(sha.ComputeHash(Encoding.ASCII.GetBytes(plainText)));
}
}
public static string HashSHA256(string plainText)
{
using (var sha = SHA256Managed.Create())
{
return Convert.ToBase64String(sha.ComputeHash(Encoding.ASCII.GetBytes(plainText)));
}
}
public static string HashHMACMD5(string plainText, byte[] key)
{
using (HMACMD5 hmac = new HMACMD5(key))
{
var bytes = Encoding.ASCII.GetBytes(plainText);
return Convert.ToBase64String(hmac.ComputeHash(bytes));
}
}
}
SecurityCookie:
internal class SecurityCookie
{
public string Name { get; set; }
public DateTime ExpiryDate { get; set; }
public SecurityContext Context { get; set; }
#region Security keys
private static readonly byte[] _cookieSigEncryptionKey = new byte[64] {
//Enter 64 bytes of encryption keys
};
private static readonly byte[] _cookiePayloadEncryptionSalt = new byte[48] {
//Enter 48 bytes of encryption salts
};
private static readonly string _cookiePayloadEncryptionKey = "here goes your complex encryption password";
#endregion
private static readonly string _cookiePayloadToken = "~~";
public SecurityCookie(string name, DateTime expiryDate, SecurityContext context)
{
this.Name = name;
this.ExpiryDate = expiryDate;
this.Context = context;
}
public SecurityCookie()
{
this.Name = Guid.NewGuid().ToString();
}
public KeyValuePair<string,Tuple<CookieOptions,string>> CreateCookie()
{
CookieOptions cookieOptions = new CookieOptions() {
Expires = ExpiryDate,
HttpOnly = true,
Secure = false
};
return new KeyValuePair<string, Tuple<CookieOptions,string>>(Name,new Tuple<CookieOptions,string>(cookieOptions, GenerateContent()));
}
public static SecurityContext ExtractContent(string encryptedContent)
{
string decodedContent = null;
string decryptedContent = null;
try {
decodedContent = Encoding.ASCII.GetString(Convert.FromBase64String(encryptedContent));
decryptedContent = EncryptionLib.DecryptStringAES(decodedContent, _cookiePayloadEncryptionKey, _cookiePayloadEncryptionSalt);
} catch {
decryptedContent = null;
}
if (string.IsNullOrWhiteSpace(decryptedContent))
return null;
string[] dataParts = decryptedContent.Split(new string[] { _cookiePayloadToken },StringSplitOptions.RemoveEmptyEntries);
if(dataParts == null || dataParts.Length != 2)
return null;
if (dataParts[1] != Sign(dataParts[0]))
return null;
return JsonConvert.DeserializeObject<SecurityContext>(dataParts[0]);
}
public string GenerateContent()
{
string data = JsonConvert.SerializeObject(Context);
string signature = Sign(data);
// _cookiePayloadToken denotes end of payload segment and start of signature (checksum) segment
return EncryptAndHashCookieContent(data + _cookiePayloadToken + signature);
}
private string EncryptAndHashCookieContent(string content)
{
return Convert.ToBase64String(
Encoding.ASCII.GetBytes(
EncryptionLib.EncryptStringAES(content,_cookiePayloadEncryptionKey,_cookiePayloadEncryptionSalt)
)
);
}
private static string Sign(string data)
{
return EncryptionLib.HashHMACMD5(data,_cookieSigEncryptionKey);
}
}
安全经理:
public class SecurityManager : ISecurityManager
{
private const string _authCookieName = "aa-bm";
private const string _authCookieItemsKey = "security-cookie";
private const int _authCookieExpiryMinutesPersistent = 60 * 24 * 30;
private const int _authCookieExpiryMinutesTransient = 60 * 1;
private const int _authCookieSecurityContextRefreshMinutes = 5;
private const string _securityContextCurrentVersion = "1.0";
private readonly ISecurityService _securityService;
public SecurityManager(ISecurityService securityService)
{
this._securityService = securityService;
}
private string GetSecurityCookieValue(HttpContext context)
{
var cookies = context.Request.Cookies[_authCookieName];
if(cookies.Count == 0)
return null;
return cookies[0];
}
public async Task<bool> ProcessSecurityContext(HttpContext context)
{
string encryptedValue = GetSecurityCookieValue(context);
if (string.IsNullOrWhiteSpace(encryptedValue))
return false;
SecurityContext securityContext = ExtractSecurityContext(encryptedValue);
if (securityContext == null || securityContext.Metadata.UserID <= 0 || securityContext.ContextVersion != _securityContextCurrentVersion) {
context.Response.Cookies.Delete(_authCookieName);
return false;
}
securityContext = await RefreshCookieContext(context,securityContext);
if(securityContext == null) {
context.Response.Cookies.Delete(_authCookieName);
return false;
}
ClaimsIdentity identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.UserData, securityContext.ToString()) });
context.User.AddIdentity(identity);
return true;
}
private SecurityContext ExtractSecurityContext(string encryptedValue)
{
return SecurityCookie.ExtractContent(encryptedValue);
}
private async Task<SecurityContext> RefreshCookieContext(HttpContext context,SecurityContext currentContext)
{
DateTime expiry = DateTime.Now.AddMinutes(_authCookieExpiryMinutesTransient);
currentContext.Expires = expiry;
if(currentContext.SecurityDomainContext.RefreshSecurityContextDate < DateTime.Now) {
UserMetadata userMetadata = await _securityService.GetUserMetadata(currentContext.Metadata.UserID);
if(userMetadata == null)
return null;
currentContext.Metadata = userMetadata;
currentContext.SecurityDomainContext.RefreshSecurityContextDate = DateTime.Now.AddMinutes(_authCookieSecurityContextRefreshMinutes);
currentContext.Status = userMetadata.Status;
}
SecurityCookie secureCookie = new SecurityCookie(
_authCookieName,
expiry,
currentContext
);
var cookie = secureCookie.CreateCookie();
context.Response.Cookies.Append(cookie.Key,cookie.Value.Item2,cookie.Value.Item1);
return currentContext;
}
public void Login(HttpContext context, UserMetadata userMetadata, bool persistent)
{
DateTime expiry = (persistent ? DateTime.Now.AddMinutes(_authCookieExpiryMinutesPersistent) : DateTime.Now.AddMinutes(_authCookieExpiryMinutesTransient));
SecurityCookie secureCookie = new SecurityCookie(
_authCookieName,
expiry,
new SecurityContext() {
Expires = expiry,
Metadata = userMetadata,
SecurityDomainContext = new SecurityDomainContext() {
RefreshSecurityContextDate = DateTime.Now.AddMinutes(_authCookieSecurityContextRefreshMinutes)
},
ContextVersion = _securityContextCurrentVersion
}
);
var cookie = secureCookie.CreateCookie();
context.Response.Cookies.Append(cookie.Key,cookie.Value.Item2,cookie.Value.Item1);
}
public void Logout(HttpContext context)
{
context.Response.Cookies.Delete(_authCookieName, new CookieOptions() { Expires = DateTime.Now.AddDays(-365) });
}
}
安全扩展程序:
public static void UseCustomAuthentication(this IApplicationBuilder builder)
{
builder.UseMiddleware<BBAuthentication>();
}
public static SecurityContext GetUserSecurityContext(this ClaimsPrincipal claimsPrincipal)
{
var userDataClaim = claimsPrincipal.Claims.Where(x => x.Type == ClaimTypes.UserData).FirstOrDefault();
if(userDataClaim == null)
return null;
return JsonConvert.DeserializeObject<SecurityContext>(userDataClaim.Value);
}
public static bool HasRoles(this ClaimsPrincipal claimsPrincipal, params RoleEnum[] roles)
{
SecurityContext context = claimsPrincipal.GetUserSecurityContext();
foreach(var role in roles)
if(!context.Metadata.Roles.Contains(role))
return false;
return true;
}
public static bool HasPermissions(this ClaimsPrincipal claimsPrincipal, params PermissionEnum[] permissions)
{
SecurityContext context = claimsPrincipal.GetUserSecurityContext();
foreach(var perm in permissions)
if(!context.Metadata.Permissions.Contains(perm))
return false;
return true;
}
public static int GetUserID(this ClaimsPrincipal claimsPrincipal)
{
SecurityContext context = claimsPrincipal.GetUserSecurityContext();
return context.Metadata.UserID;
}
public static int GetCompanyID(this ClaimsPrincipal claimsPrincipal)
{
SecurityContext context = claimsPrincipal.GetUserSecurityContext();
return context.Metadata.CompanyID;
}
public static PersonalSettings GetUserSettings(this ClaimsPrincipal claimsPrincipal)
{
SecurityContext context = claimsPrincipal.GetUserSecurityContext();
return context.Metadata.Settings;
}
public static string GetFullName(this ClaimsPrincipal claimsPrincipal)
{
SecurityContext context = claimsPrincipal.GetUserSecurityContext();
return context.Metadata.FirstName + " " + context.Metadata.LastName;
}
public static string GetFirstName(this ClaimsPrincipal claimsPrincipal)
{
SecurityContext context = claimsPrincipal.GetUserSecurityContext();
return context.Metadata.FirstName;
}
public static string GetLastName(this ClaimsPrincipal claimsPrincipal)
{
SecurityContext context = claimsPrincipal.GetUserSecurityContext();
return context.Metadata.LastName;
}
}
权限授权示例(用此装饰您的控制器方法或整个控制器并逐个说明权限。此外,几乎相同的RolesAuthorize属性对角色执行相同的操作):
public class AuthorizePermissionsAttribute : ActionFilterAttribute
{
public PermissionEnum[] Permissions { get; set; }
public BBAuthorizePermissionsAttribute(params PermissionEnum[] permissions)
{
this.Permissions = permissions;
}
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
bool hasPermissions = context.HttpContext.User.HasPermissions(Permissions);
if(!hasPermissions) {
throw new UnauthenticatedException();
}
return base.OnActionExecutionAsync(context, next);
}
}
现在可以使用。
您使用中间件进行身份验证,使用自定义属性进行授权。 SecurityManager类是操作安全性的中心点,它对加密数据进行序列化和反序列化,并在每个请求上使用该数据(通过控制器中的SeccurityExtensions使用)。 SecurityContext类是你需要的我建议加密至少3,4个字段(UserID,CompanyID,UserSettings,Roles,Permissions)
在UseStaticFiles或useIISPlatformHandler之后的Configure方法中的Startup.cs中立即编写app.UseBBAuthentication(); 这个顺序非常重要。
不幸的是,由于我很忙,我现在不能给你详细的使用说明。但我认为代码是不言自明的。这在现在的生产系统中有效,因此需要进行现场测试。
如果您在彻底分析后有具体问题,请随时提出!
答案 1 :(得分:0)
如果要进行自定义安全性和自定义cookie身份验证,而不是绕过ASP.NET身份提供程序,那么这可能是最合乎逻辑的选择。在这种情况下,您主要需要自定义代码(尽管您可以从Identity框架中选择部分并覆盖/实现所需的接口)。如果这是案例回复,我将为您提供完整的代码,因为我在我的最新项目中做了这个。
如果你想使用ASP.NET身份,95%的配置和代码都在Startup.cs文件中,你可以在那里配置各种身份验证提供程序,包括cookie提供程序。
请查看以下链接以获取更多信息:https://docs.asp.net/en/latest/security/authentication/cookie.html