https://learn.microsoft.com/zh-cn/aspnet/core/security/authentication/?view=aspnetcore-8.0
CookieAuthenticationHandler
作为处理逻辑CookieAuthenticationOptions
类中一些常用属性的说明:
true
,这是一个安全特性,用于防止跨站脚本攻击(XSS)。CookieSecurePolicy.None
、CookieSecurePolicy.Always
或 CookieSecurePolicy.SameAsRequest
。这决定了Cookie是否应通过HTTPS传输。SameSiteMode.None
、SameSiteMode.Lax
或 SameSiteMode.Strict
。true
,则每次用户请求页面时,Cookie的过期时间都会重置为其原始过期时间。这有助于在用户活跃时保持会话的活跃状态。CookieAuthenticationEvents
的实例,该实例包含可以在身份验证过程中调用的委托,以自定义行为(如重定向、登录后操作等)。CookieAuthenticationEvents
类包含多个事件,这些事件在 Cookie 身份验证的不同阶段被触发:
OnRedirectToLogin
: 当用户尝试访问需要身份验证的资源,但尚未登录时触发。OnRedirectToAccessDenied
: 当用户已登录,但尝试访问他们没有权限的资源时触发。OnRedirectToLogout
: 当用户登出时触发。OnSigningIn
: 在用户登录之前触发,但身份验证票据(ticket)已经被创建。OnSignedIn
: 在用户成功登录后触发。OnSigningOut
: 在用户登出之前触发,但身份验证票据尚未被移除。OnSignedOut
: 在用户成功登出后触发。OnValidatePrincipal
: 在每次请求时触发,用于验证身份验证票据的有效性。 builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
// 配置Cookie参数
options.Cookie.Name = ".AspNetCore.Cookies"; // Cookie名称
options.Cookie.HttpOnly = true; // 限制Cookie只能通过HTTP访问,不能通过客户端脚本访问
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 仅在HTTPS下传输Cookie
options.Cookie.SameSite = SameSiteMode.Lax; // 设置SameSite属性
options.LoginPath = "/Account/Login"; // 登录页面路径
options.AccessDeniedPath = "/Account/AccessDenied"; // 访问被拒绝时重定向到的路径
options.SlidingExpiration = true; // 是否在每次请求时滑动Cookie的过期时间
options.ExpireTimeSpan = TimeSpan.FromHours(1); // Cookie过期时间
// 如果需要,你还可以配置其他事件,如登录成功、登出成功等
//options.Events = new CookieAuthenticationEvents()
//{
// OnRedirectToAccessDenied = context =>
// {
// return context.Response.WriteAsJsonAsync(new
// {
// Result = false,
// Message = "访问失败,请先登录"
// });
// },
// OnValidatePrincipal = context =>
// {
// return context.Response.WriteAsJsonAsync(new
// {
// Result = false,
// Message = "访问失败,请先登录"
// });
// },
// OnRedirectToLogin = context =>
// {
// return context.Response.WriteAsJsonAsync(new
// {
// Result = false,
// Message = "访问失败,请先登录"
// });
// },
//};
});
//鉴权 (核心源码就是AuthenticationMiddleware中间件)
app.UseAuthentication();
//授权 使用Authorize必须配置app.UseAuthorization();
app.UseAuthorization();
ClaimsPrincipal
:代表当前经过身份验证的用户的主体,验证后附加到HTTP请求的上下文中,通常可以通过 HttpContext.User
属性来访问
ClaimsIdentity
:表示一个特定的身份,并存储与该用户相关的所有声明
Claim
:用于描述用户的某个属性或权限,例如用户名、电子邮件地址、角色等
/// <summary>
/// http://localhost:5555/Auth/Login?name=admin&password=123456
/// </summary>
/// <param name="name"></param>
/// <param name="password"></param>
/// <returns></returns>
public async Task<IActionResult> Login(string name, string password)
{
if ("admin".Equals(name, StringComparison.CurrentCultureIgnoreCase)
&& password.Equals("123456"))//等同于去数据库校验
{
var claimIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
claimIdentity.AddClaim(new Claim(ClaimTypes.Name, name));
claimIdentity.AddClaim(new Claim(ClaimTypes.Email, "2545233857@qq.com"));
claimIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
claimIdentity.AddClaim(new Claim(ClaimTypes.Country, "Chinese"));
claimIdentity.AddClaim(new Claim(ClaimTypes.DateOfBirth, "1998"));
await base.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimIdentity), new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddSeconds(30),
});
return new JsonResult(new
{
Result = true,
Message = "登录成功"
});
}
else
{
await Task.CompletedTask;
return new JsonResult(new
{
Result = false,
Message = "登录失败"
});
}
}
在其他控制器上标记[Authorize]特性,在访问接口框架会自动进行鉴权并将身份信息写入上下文
[AllowAnonymous]
:匿名可访问[Authorize]
:必须登录才可访问 // <summary>
/// 不需要权限就能访问---
/// http://localhost:5555/Auth/Index
/// 但是项目里面总有些数据是要登陆后才能看到的
/// </summary>
/// <returns></returns>
[AllowAnonymous]
public IActionResult Index()
{
return View();
}
/// <summary>
/// 要求登陆后才能看到,没登陆是不能看的
/// http://localhost:5555/Auth/Info
/// </summary>
/// <returns></returns>
[Authorize]//表明该Action需要鉴权通过---得有鉴权动作
public IActionResult Info()
{
return View();
}
/// <summary>
/// 退出登陆
/// http://localhost:5555/Auth/Logout
/// </summary>
/// <returns></returns>
public async Task<IActionResult> Logout()
{
await base.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return new JsonResult(new
{
Result = true,
Message = "退出成功"
});
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//确保Cookie票据(验证Cookie)
var result = await EnsureCookieTicket();
if (!result.Succeeded)
{
return result;
}
// We check this before the ValidatePrincipal event because we want to make sure we capture a clean clone
// without picking up any per-request modifications to the principal.
await CheckForRefreshAsync(result.Ticket);
Debug.Assert(result.Ticket != null);
//认证cookie校验认证上下文的方法
var context = new CookieValidatePrincipalContext(Context, Scheme, Options, result.Ticket);
await Events.ValidatePrincipal(context);
if (context.Principal == null)
{
return AuthenticateResults.NoPrincipal;
}
//判断上下文中的ShouldRenew参数,判断是否刷新Cookie
if (context.ShouldRenew)
{
RequestRefresh(result.Ticket, context.Principal);
}
return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name));
}
//读取Cookie
private async Task<AuthenticateResult> ReadCookieTicket()
{
//读取客户端存在的cookie信息.
var cookie = Options.CookieManager.GetRequestCookie(Context, Options.Cookie.Name!);
if (string.IsNullOrEmpty(cookie))
{
return AuthenticateResult.NoResult();
}
//解密Cookie内容
var ticket = Options.TicketDataFormat.Unprotect(cookie, GetTlsTokenBinding());
if (ticket == null)
{
return AuthenticateResults.FailedUnprotectingTicket;
}
//如果配置了SessionStore,可以进行持久化管理,
if (Options.SessionStore != null)
{
// 拿到seesionId的cliam
var claim = ticket.Principal.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaim));
if (claim == null)
{
return AuthenticateResults.MissingSessionId;
}
// Only store _sessionKey if it matches an existing session. Otherwise we'll create a new one.
ticket = await Options.SessionStore.RetrieveAsync(claim.Value, Context, Context.RequestAborted);
if (ticket == null)
{
return AuthenticateResults.MissingIdentityInSession;
}
_sessionKey = claim.Value;
}
var currentUtc = TimeProvider.GetUtcNow();
var expiresUtc = ticket.Properties.ExpiresUtc;
//cookie过期检测
if (expiresUtc != null && expiresUtc.Value < currentUtc)
{
if (Options.SessionStore != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey!, Context, Context.RequestAborted);
// Clear out the session key if its expired, so renew doesn't try to use it
_sessionKey = null;
}
return AuthenticateResults.ExpiredTicket;
}
// Finally we have a valid ticket
return AuthenticateResult.Success(ticket);
}
// 检查并且刷新
private async Task CheckForRefreshAsync(AuthenticationTicket ticket)
{
var currentUtc = TimeProvider.GetUtcNow();
var issuedUtc = ticket.Properties.IssuedUtc;
var expiresUtc = ticket.Properties.ExpiresUtc;
var allowRefresh = ticket.Properties.AllowRefresh ?? true;
if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration && allowRefresh)
//Options.SlidingExpiration 和allowRefresh控制是否自动刷新
{
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
var timeRemaining = expiresUtc.Value.Subtract(currentUtc);
var eventContext = new CookieSlidingExpirationContext(Context, Scheme, Options, ticket, timeElapsed, timeRemaining)
{
ShouldRenew = timeRemaining < timeElapsed,
};
await Events.CheckSlidingExpiration(eventContext);
if (eventContext.ShouldRenew)
{
//请求刷新
RequestRefresh(ticket);
}
}
}
JWT是JSON Web Token的简称,是一个开放标准,用于在各方之间安全地传输信息。
JWT通常用于用户认证和信息交换。由于它是数字签名的,所以信息可以验证和信任
JWT由三部分组成,分别是Header(头部)、Payload(负载)和Signature(签名),它们之间用点(.)分隔。
{"alg":"HS256","typ":"JWT"}
。这个JSON对象会被Base64Url编码以形成JWT的第一部分。{"sub":"123","name":"Tom","admin":true}
。这个JSON对象也会被Base64Url编码以形成JWT的第二部分。JWT的工作原理
JWT的优势
使用场景
JWT常用于用户认证、单点登录、信息交换等场景。由于其紧凑、自包含和可验证的特性,JWT在现代Web应用中得到了广泛的应用。
public class JWTTokenOptions
{
public string Audience { get; set; }
public string SecurityKey { get; set; }
public string Issuer { get; set; }
}
public interface IJWTService
{
/// <summary>
/// 新版本
/// </summary>
/// <param name="userInfo"></param>
/// <returns></returns>
string GetTokenWithModel(User userInfo);
/// <summary>
/// 获取Token+RefreshToken
/// </summary>
/// <param name="userInfo"></param>
/// <returns>Token+RefreshToken</returns>
Tuple<string, string> GetTokenWithRefresh(User userInfo);
/// <summary>
/// 基于refreshToken获取Token
/// </summary>
/// <param name="refreshToken"></param>
/// <returns></returns>
string GetTokenByRefresh(string refreshToken);
}
public class JWTService: IJWTService
{
private static Dictionary<string, User> TokenCache = new Dictionary<string, User>();
private JWTTokenOptions _JWTTokenOptions = null;
public JWTService(IOptions<JWTTokenOptions> options)
{
this._JWTTokenOptions = options.Value;
}
/// <summary>
/// 刷新token的有效期问题上端校验
/// </summary>
/// <param name="refreshToken"></param>
/// <returns></returns>
public string GetTokenByRefresh(string refreshToken)
{
//refreshToken在有效期,但是缓存可能没有? 还能去手动清除--比如改密码了,清除缓存,用户来刷新token就发现没有了,需要重新登陆
if (TokenCache.ContainsKey(refreshToken))
{
string token = this.IssueToken(TokenCache[refreshToken], 60);
return token;
}
else
{
return "";
}
}
/// <summary>
/// 2个token 就是有效期不一样
/// </summary>
/// <param name="userInfo"></param>
/// <returns></returns>
public Tuple<string, string> GetTokenWithRefresh(User userInfo)
{
string token = this.IssueToken(userInfo, 60);//1分钟
string refreshToken = this.IssueToken(userInfo, 60 * 60 * 24 * 7);//7*24小时
TokenCache.Add(refreshToken, userInfo);
return Tuple.Create(token, refreshToken);
}
public string GetTokenWithModel(User userModel)
{
//return this.IssueToken(userModel);
return this.IssueToken(userModel, 1);
}
private string IssueToken(User userModel, int second = 600)
{
var claims = new[]
{
new Claim(ClaimTypes.Name, userModel.UserName),
new Claim(ClaimTypes.Email, userModel.Email),
new Claim(ClaimTypes.Role,userModel.Role),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._JWTTokenOptions.SecurityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
/**
* Claims (Payload)
Claims 部分包含了一些跟这个 token 有关的重要信息。 JWT 标准规定了一些字段,下面节选一些字段:
iss: The issuer of the token,token 是给谁的
sub: The subject of the token,token 主题
exp: Expiration Time。 token 过期时间,Unix 时间戳格式
iat: Issued At。 token 创建时间, Unix 时间戳格式
jti: JWT ID。针对当前 token 的唯一标识
除了规定的字段外,可以包含其他任何 JSON 兼容的字段。
* */
var token = new JwtSecurityToken(
issuer: this._JWTTokenOptions.Issuer,
audience: this._JWTTokenOptions.Audience,
claims: claims,
expires: DateTime.Now.AddSeconds(second),//10分钟有效期
notBefore: DateTime.Now,//立即生效 DateTime.Now.AddMilliseconds(30),//30s后有效
signingCredentials: creds);
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
return returnToken;
}
}
/// <summary>
/// 用户类
/// </summary>
public class User
{
public string UserName { get; set; }
public string Email { get; set; }
public string Role { get; set; }
}
定义JWTTokenOptions
"JWTTokenOptions": {
"Audience": "http://localhost:5555",
"Issuer": "http://localhost:5555",
"SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB"
}
在 builder.Services
中,你需要定义你的自定义身份验证方案,并配置相关的处理程序。这可以通过 AddAuthentication
和 AddScheme
方法来完成。
//配置JWTTokenOptions
builder.Services.Configure<JWTTokenOptions>(builder.Configuration.GetSection("JWTTokenOptions"));
builder.Services.AddTransient<IJWTService, JWTService>();
JWTTokenOptions tokenOptions = new JWTTokenOptions();
builder.Configuration.Bind("JWTTokenOptions", tokenOptions);
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true, //是否验证Audience
ValidateLifetime = true, //是否验证失效时间
ValidateIssuerSigningKey = true, //是否验证SecurityKey
ValidAudience = tokenOptions.Audience, //订阅人Audience
ValidIssuer = tokenOptions.Issuer,//发行人Issuer
ClockSkew = TimeSpan.FromSeconds(60), //特别注意:默认是5分钟缓冲,过期时间容错值,解决服务器端时间不同步问题(秒)
RequireExpirationTime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOptions.SecurityKey)) //SecurityKey
};
//options.Events = new JwtBearerEvents
//{
// OnAuthenticationFailed = async (context) =>
// {
// await context.Response.WriteAsJsonAsync(
// new
// {
// Result = false,
// Message = context?.Exception?.Message
// });
// // await Task.CompletedTask;
// },
// OnTokenValidated = async (context) =>
// {
// //await context.Response.WriteAsJsonAsync(
// // new
// // {
// // Result = false,
// // Message = context?.Result?.Failure?.Message
// // });
// await Console.Out.WriteLineAsync(context?.Result?.Failure?.Message);
// },
// OnChallenge = async (context) =>
// {
// await context.Response.WriteAsJsonAsync(
// new
// {
// Result = false,
// Message = context?.AuthenticateFailure?.Message
// });
// },
// OnForbidden = async (context) =>
// {
// await context.Response.WriteAsJsonAsync(
// new
// {
// Result = false,
// Message = context?.Result?.Failure?.Message
// });
// },
// OnMessageReceived = async (context) =>
// {
// await Console.Out.WriteLineAsync(context?.Result?.Failure?.Message);
// //await context.Response.WriteAsJsonAsync(
// // new
// // {
// // Result = false,
// // Message = context?.Result?.Failure?.Message
// // });
// }
//};
})
;
TokenValidationParameters
类用于配置 JWT(JSON Web Tokens)的验证参数。以下是这个类中的一些常用属性
string
IEnumerable<string>
string
IEnumerable<string>
SecurityKey
SymmetricSecurityKey
(用于 HMACSHA 系列算法)或 RsaSecurityKey
(用于 RSA 算法)。IEnumerable<SecurityKey>
bool
true
。bool
true
。bool
true
。bool
exp
和 nbf
声明)。默认值为 true
。TimeSpan
exp
和 nbf
声明时,此值将被考虑在内。默认值为 5 分钟。bool
exp
声明)。默认值为 false
。bool
true
。bool
false
。SecurityKey
IEnumerable<SecurityKey>
bool
TokenValidationParameters
类中的内置属性,但可以通过自定义逻辑实现。JwtBearerEvents
类提供了一组事件,这些事件可以在 JWT 承载令牌认证过程中被触发,以便你可以添加自定义逻辑。以下是 JwtBearerEvents
类中的一些常用事件
ClaimsIdentity
之前触发。你可以在这个事件处理器中添加或修改用户的声明(Claims
)。在 Startup.Configure
方法中,确保 UseAuthentication
中间件被添加到请求处理管道中。
//鉴权 (核心源码就是AuthenticationMiddleware中间件)
app.UseAuthentication();
//授权 使用Authorize必须配置app.UseAuthorization();
app.UseAuthorization();
在你的应用程序代码中,你可以通过 HttpContext.AuthenticateAsync
方法来触发认证流程。但是,在大多数情况下,认证是在中间件级别自动处理的
/// <summary>
/// http://localhost:5555/Auth/JWTLogin?name=admin&password=123456
/// </summary>
/// <param name="name"></param>
/// <param name="password"></param>
/// <returns></returns>
public async Task<IActionResult> JWTLogin(string name, string password)
{
if ("admin".Equals(name, StringComparison.CurrentCultureIgnoreCase)
&& password.Equals("123456"))//等同于去数据库校验
{
User user = new User
{
UserName = name,
Email = "2545233857@qq.com",
Role = "admin",
};
var token = _jwtService.GetTokenWithModel(user);
return new JsonResult(new
{
Result = true,
token = token,
Message = "登录成功"
});
}
else
{
await Task.CompletedTask;
return new JsonResult(new
{
Result = false,
Message = "登录失败"
});
}
}
/// <summary>
/// http://localhost:5555/Auth/JWTToken
/// </summary>
/// <returns></returns>
//[Authorize] //使用Authorize必须配置app.UseAuthorization();
public async Task<IActionResult> JWTToken()
{
var userOrigin = base.HttpContext.User;
var result = await base.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
if (result?.Principal == null)
{
return new JsonResult(new
{
Result = false,
Message = result?.Failure?.Message ?? $"认证失败,用户未登录"
});
}
else
{
base.HttpContext.User = result.Principal;
StringBuilder sb = new StringBuilder();
foreach (var item in base.HttpContext.User.Identities.First().Claims)
{
Console.WriteLine($"Claim {item.Type}:{item.Value}");
}
return new JsonResult(new
{
Result = true,
Message = $"认证成功,用户已登录"
});
}
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string? token;
try
{
// Give application opportunity to find from a different location, adjust, or reject token
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
// event can set the token
//发布消息订阅
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
// If application retrieved token from somewhere else, use that.
token = messageReceivedContext.Token;
if (string.IsNullOrEmpty(token))
{
//获取authorization
string authorization = Request.Headers.Authorization.ToString();
// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
//获取token
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
// If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
var tvp = await SetupTokenValidationParametersAsync();
List<Exception>? validationFailures = null;
SecurityToken? validatedToken = null;
ClaimsPrincipal? principal = null;
//不使用SecurityToken验证器
if (!Options.UseSecurityTokenValidators)
{
foreach (var tokenHandler in Options.TokenHandlers)
{
try
{
//验证token
var tokenValidationResult = await tokenHandler.ValidateTokenAsync(token, tvp);
if (tokenValidationResult.IsValid)
{
principal = new ClaimsPrincipal(tokenValidationResult.ClaimsIdentity);
validatedToken = tokenValidationResult.SecurityToken;
break;
}
else
{
validationFailures ??= new List<Exception>(1);
RecordTokenValidationError(tokenValidationResult.Exception ?? new SecurityTokenValidationException($"The TokenHandler: '{tokenHandler}', was unable to validate the Token."), validationFailures);
}
}
catch (Exception ex)
{
validationFailures ??= new List<Exception>(1);
RecordTokenValidationError(ex, validationFailures);
}
}
}
else
{
#pragma warning disable CS0618 // Type or member is obsolete
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
try
{
//验证token
principal = validator.ValidateToken(token, tvp, out validatedToken);
}
catch (Exception ex)
{
validationFailures ??= new List<Exception>(1);
RecordTokenValidationError(ex, validationFailures);
continue;
}
}
}
#pragma warning restore CS0618 // Type or member is obsolete
}
//判断凭证和token
if (principal != null && validatedToken != null)
{
Logger.TokenValidationSucceeded();
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal
};
tokenValidatedContext.SecurityToken = validatedToken;
tokenValidatedContext.Properties.ExpiresUtc = GetSafeDateTime(validatedToken.ValidTo);
tokenValidatedContext.Properties.IssuedUtc = GetSafeDateTime(validatedToken.ValidFrom);
//发布Token验证成功事件
await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}
if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
tokenValidatedContext.Success();
return tokenValidatedContext.Result!;
}
//验证失败结果
if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
};
//发布验证失败事件
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
}
if (!Options.UseSecurityTokenValidators)
{
return AuthenticateResults.TokenHandlerUnableToValidate;
}
return AuthenticateResults.ValidatorNotFound;
}
catch (Exception ex)
{
Logger.ErrorProcessingMessage(ex);
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
//发布验证失败事件
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
在 builder.Services
中,你需要定义你的自定义身份验证方案,并配置相关的处理程序。这可以通过 AddAuthentication
和 AddScheme
方法来完成。
builder.Services.AddAuthentication(options =>
{
options.AddScheme<XTokenAuthenticationHandler2>(XTokenAuthenticationDefaults.AuthenticationScheme, "");
options.DefaultAuthenticateScheme = XTokenAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = XTokenAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = XTokenAuthenticationDefaults.AuthenticationScheme;
options.DefaultForbidScheme = XTokenAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = XTokenAuthenticationDefaults.AuthenticationScheme;
});
你需要创建一个类,实现 IAuthenticationHandler
接口或继承 AuthenticationHandler<TOptions>
类(其中 TOptions
是你的认证选项类),并在这个类中实现你的自定义认证逻辑。
/// <summary>
/// DES加解密
/// </summary>
public class DesEncrypt
{
private static byte[] key = Encoding.UTF8.GetBytes("1234567812345678"); // 16字节的密钥
private static byte[] iv = Encoding.UTF8.GetBytes("1234567812345678"); // 16字节的初始化向量
/// <summary>
/// 加密
/// </summary>
/// <param name="plainText"></param>
/// <returns></returns>
public static string Encrypt(string plainText)
{
using (Aes aes = Aes.Create())
{
aes.Key = key;
aes.IV = iv;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter sw = new StreamWriter(cs))
{
sw.Write(plainText);
}
}
return Convert.ToBase64String(ms.ToArray());
}
}
}
/// <summary>
/// 解密
/// </summary>
/// <param name="cipherText"></param>
/// <returns></returns>
public static string Decrypt(string cipherText)
{
using (Aes aes = Aes.Create())
{
aes.Key = key;
aes.IV = iv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] bytes = Convert.FromBase64String(cipherText);
using (MemoryStream ms = new MemoryStream(bytes))
{
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (StreamReader sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
}
/// <summary>
/// 用户类
/// </summary>
public class User
{
public string UserName { get; set; }
public string Email { get; set; }
public string Role { get; set; }
}
IAuthenticationHandler
接口 public class XTokenAuthenticationHandler : IAuthenticationHandler
{
private AuthenticationScheme _authenticationScheme;
private HttpContext _httpContext;
private string _tokenName = "x-token";
private ILogger<XTokenAuthenticationHandler> _logger;
public XTokenAuthenticationHandler(ILogger<XTokenAuthenticationHandler> logger)
{
_logger = logger;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_authenticationScheme = scheme;
_httpContext = context;
return Task.CompletedTask;
}
public Task<AuthenticateResult> AuthenticateAsync()
{
try
{
if (_httpContext.Request.Headers.ContainsKey(_tokenName))
{
string token = _httpContext.Request.Headers[_tokenName];
var userStr = DesEncrypt.Decrypt(token);
var userInfo = JsonConvert.DeserializeObject<User>(userStr);
//校验---整理信息,保存起来
var claimIdentity = new ClaimsIdentity("Custom");
claimIdentity.AddClaim(new Claim(ClaimTypes.Name, userInfo.UserName));
claimIdentity.AddClaim(new Claim(ClaimTypes.Role, userInfo.Role));
claimIdentity.AddClaim(new Claim(ClaimTypes.Email, userInfo.Email));
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimIdentity);//信息拼装和传递
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, null, _authenticationScheme.Name)));
}
else
{
return Task.FromResult(AuthenticateResult.NoResult());//没有凭证
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return Task.FromResult(AuthenticateResult.Fail($"认证失败请重新登录"));
}
}
/// <summary>
/// 未登录
/// </summary>
/// <param name="properties"></param>
/// <returns></returns>
public Task ChallengeAsync(AuthenticationProperties? properties)
{
_httpContext.Response.StatusCode = 401;
//_httpContext.Response.WriteAsJsonAsync(new
//{
// Result = false,
// Message = !string.IsNullOrEmpty(_errorMessage) ? _errorMessage : "认证失败,请重新登录"
//}) ;
return Task.CompletedTask;
}
/// <summary>
/// 未授权,无权限
/// </summary>
/// <param name="properties"></param>
/// <returns></returns>
public Task ForbidAsync(AuthenticationProperties? properties)
{
_httpContext.Response.StatusCode = 403;
//_httpContext.Response.WriteAsJsonAsync(new
//{
// Result = false,
// Message = "访问失败,未授权"
//});
return Task.CompletedTask;
}
}
public class XTokenAuthenticationDefaults
{
/// <summary>
/// 提供固定名称
/// </summary>
public const string AuthenticationScheme = "XTokenScheme";
}
AuthenticationHandler<TOptions>
类 public class XTokenAuthenticationHandler2 : AuthenticationHandler<AuthenticationSchemeOptions>
{
private string _tokenName = "x-token";
private ILogger<XTokenAuthenticationHandler2> _logger;
public XTokenAuthenticationHandler2(ILogger<XTokenAuthenticationHandler2> logger1, IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
_logger = logger1;
}
/// <summary>
/// 认证
/// </summary>
/// <returns></returns>
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
try
{
if (Request.Headers.ContainsKey(_tokenName))
{
string token = Request.Headers[_tokenName];
var userStr = DesEncrypt.Decrypt(token);
var userInfo = JsonConvert.DeserializeObject<User>(userStr);
//校验---整理信息,保存起来
var claimIdentity = new ClaimsIdentity("Custom");
claimIdentity.AddClaim(new Claim(ClaimTypes.Name, userInfo.UserName));
claimIdentity.AddClaim(new Claim(ClaimTypes.Role, userInfo.Role));
claimIdentity.AddClaim(new Claim(ClaimTypes.Email, userInfo.Email));
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimIdentity);//信息拼装和传递
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, null, Scheme.Name)));
}
else
{
return Task.FromResult(AuthenticateResult.NoResult());//没有凭证
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return Task.FromResult(AuthenticateResult.Fail($"认证失败请重新登录"));
}
}
}
在 Startup.Configure
方法中,确保 UseAuthentication
中间件被添加到请求处理管道中。
//鉴权 (核心源码就是AuthenticationMiddleware中间件)
app.UseAuthentication();
在你的应用程序代码中,你可以通过 HttpContext.AuthenticateAsync
方法来触发认证流程。但是,在大多数情况下,认证是在中间件级别自动处理的
/// <summary>
/// http://localhost:5555/Auth/XTokenLogin?name=admin&password=123456
/// </summary>
/// <param name="name"></param>
/// <param name="password"></param>
/// <returns></returns>
public async Task<IActionResult> XTokenLogin(string name, string password)
{
if ("admin".Equals(name, StringComparison.CurrentCultureIgnoreCase)
&& password.Equals("123456"))//等同于去数据库校验
{
User user = new User
{
UserName = name,
Email = "2545233857@qq.com",
Role = "admin",
};
var token = DesEncrypt.Encrypt(Newtonsoft.Json.JsonConvert.SerializeObject(user));
return new JsonResult(new
{
Result = true,
token = token,
Message = "登录成功"
});
}
else
{
await Task.CompletedTask;
return new JsonResult(new
{
Result = false,
Message = "登录失败"
});
}
}
/// <summary>
/// http://localhost:5555/Auth/XToken
/// </summary>
/// <returns></returns>
//没有要求授权
public async Task<IActionResult> XToken()
{
var userOrigin = base.HttpContext.User;
var result = await base.HttpContext.AuthenticateAsync(XTokenAuthenticationDefaults.AuthenticationScheme);
if (result?.Principal == null)
{
return new JsonResult(new
{
Result = false,
Message = $"认证失败,用户未登录"
});
}
else
{
base.HttpContext.User = result.Principal;
StringBuilder sb = new StringBuilder();
foreach (var item in base.HttpContext.User.Identities.First().Claims)
{
Console.WriteLine($"Claim {item.Type}:{item.Value}");
}
return new JsonResult(new
{
Result = true,
Message = $"认证成功,用户已登录"
});
}
}
/// <summary>
/// Extension methods to add authentication capabilities to an HTTP application pipeline.
/// </summary>
public static class AuthAppBuilderExtensions
{
public static IApplicationBuilder UseAuthentication(this IApplicationBuilder app)
{
return app.UseMiddleware<AuthenticationMiddleware>();
}
}
public class AuthenticationMiddleware
{
public async Task Invoke(HttpContext context)
{
//其它...
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
//验证
var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
{
//赋值
context.User = result.Principal;
}
if (result?.Succeeded ?? false)
{
var authFeatures = new AuthenticationFeatures(result);
context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
context.Features.Set<IAuthenticateResultFeature>(authFeatures);
}
}
await _next(context);
}
}