https://docs.microsoft.com/zh-cn/aspnet/core/migration/1x-to-2x/identity-2x?view=aspnetcore-2.2
1、Cookie-based authentication
2、JWT Bearer Authentication
3、OpenID Connect (OIDC) authentication
JWT (https://jwt.io/):
应用场景:移动端,前后端分离项目,API 请求
使用示例:
services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtBearerOptions => { jwtBearerOptions.RequireHttpsMetadata = false; jwtBearerOptions.SaveToken = false; jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true,//是否验证Issuer ValidateAudience = true,//是否验证Audience ValidIssuer = Configuration["Tokens:ValidIssuer"], ValidAudience = Configuration["Tokens:ValidAudience"], ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"])), ValidateLifetime = true, //validate the expiration and not before values in the token ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date }; });
Discovery Endpoint(/.well-known/openid-configuration)
{ "issuer": "http://192.168.1.7:50081", "jwks_uri": "http://192.168.1.7:50081/.well-known/openid-configuration/jwks", "authorization_endpoint": "http://192.168.1.7:50081/connect/authorize", "token_endpoint": "http://192.168.1.7:50081/connect/token", "userinfo_endpoint": "http://192.168.1.7:50081/connect/userinfo", "end_session_endpoint": "http://192.168.1.7:50081/connect/endsession", "check_session_iframe": "http://192.168.1.7:50081/connect/checksession", "revocation_endpoint": "http://192.168.1.7:50081/connect/revocation", "introspection_endpoint": "http://192.168.1.7:50081/connect/introspect", "device_authorization_endpoint": "http://192.168.1.7:50081/connect/deviceauthorization", "frontchannel_logout_supported": true, "frontchannel_logout_session_supported": true, "backchannel_logout_supported": true, "backchannel_logout_session_supported": true, "scopes_supported": ["openid", "profile", "Asmkt.UnifyAdmin.Api", "Asmkt.UnifyAdmin.SignalrHub", "offline_access"], "claims_supported": ["sub", "name", "family_name", "given_name", "middle_name", "nickname", "preferred_username", "profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale", "updated_at"], "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token", "implicit", "urn:ietf:params:oauth:grant-type:device_code"], "response_types_supported": ["code", "token", "id_token", "id_token token", "code id_token", "code token", "code id_token token"], "response_modes_supported": ["form_post", "query", "fragment"], "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"], "subject_types_supported": ["public"], "id_token_signing_alg_values_supported": ["RS256"], "code_challenge_methods_supported": ["plain", "S256"] }
一个携带 Token 的请求从认证中间件到最终验证 Issuer 的逻辑参考:晓晨博客 https://www.cnblogs.com/stulzq/p/10339024.html
涉及类型方法如下:
颁发token :
var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.UserName), }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: issuer, audience: audience, claims: claims, notBefore: DateTime.Now, expires: expires, signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token);
JWT定制:(自定义token来源及验证)
JwtBearerHandler:
// 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)) { string authorization = Request.Headers["Authorization"]; // If no authorization header found, nothing to process further if (string.IsNullOrEmpty(authorization)) { return AuthenticateResult.NoResult(); } 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(); } }
由上源码可知 Token 可从messageReceivedContext 获取,当没有值时才从header 中根据 key Authorization 来获取token.
JwtBearerEvents 提供了定制来源,定制验证的接口
/// <summary> /// 自定义token验证 /// </summary> public class MyTokenValidator : ISecurityTokenValidator { public bool CanValidateToken => true; public int MaximumTokenSizeInBytes { get; set; } public bool CanReadToken(string securityToken) { return true; } public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) { validatedToken = null; if (securityToken != "123") { return null; } var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); identity.AddClaim(new Claim(ClaimTypes.Name, "caolingyi")); var principal = new ClaimsPrincipal(identity); return principal; } }
使用:
.AddJwtBearer(option => { option.SecurityTokenValidators.Clear();//原先默认的验证方法清除 option.SecurityTokenValidators.Add(new MyTokenValidator()); option.Events = new JwtBearerEvents { OnMessageReceived = context => { var token = context.Request.Headers["mytoken"]; context.Token = token.FirstOrDefault(); return Task.CompletedTask; } }; });
https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/simple?view=aspnetcore-2.2
services.AddAuthorization(options => { options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator")); }); [Authorize(Policy = "RequireAdministratorRole")]
services.AddAuthorization(options => { options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber")); }); [Authorize(Policy = "EmployeeOnly")] services.AddAuthorization(options => { options.AddPolicy("Founders", policy => policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5")); });
services.AddAuthorization(options => { options.AddPolicy("AtLeast21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21))); });
using Microsoft.AspNetCore.Authorization; public class MinimumAgeRequirement : IAuthorizationRequirement { public int MinimumAge { get; } public MinimumAgeRequirement(int minimumAge) { MinimumAge = minimumAge; } }
using System; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using PoliciesAuthApp1.Services.Requirements; public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement) { if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com")) { //TODO: Use the following if targeting a version of //.NET Framework older than 4.6: // return Task.FromResult(0); return Task.CompletedTask; } var dateOfBirth = Convert.ToDateTime( context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com").Value); int calculatedAge = DateTime.Today.Year - dateOfBirth.Year; if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge)) { calculatedAge--; } if (calculatedAge >= requirement.MinimumAge) { context.Succeed(requirement); } //TODO: Use the following if targeting a version of //.NET Framework older than 4.6: // return Task.FromResult(0); return Task.CompletedTask; } }
[Authorize(Policy = "AtLeast21")]
多策略参考官方文档
identityserver4 参考文档:
官方文档 :https://identityserver4.readthedocs.io/en/latest/
源代码:https://github.com/IdentityServer
OAuth2.0:http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html