Identity Server 4 授权
1.生成 jwt token
/// <summary> /// jwt token /// </summary> public static class JwtHelper { //密钥 必须是32字节 private static string SecretKey = "4046341C6E174E11B296AB4628E5AC99"; public static string LogonCachePrefixKey = "LogonCachePrefixKey"; /// <summary> /// 生成token /// </summary> /// <returns></returns> public static string GetAccessToken(User user , List<string> userAuthoritys , Dictionary<long, string> fabs , Dictionary<long, string> rtAreas , Dictionary<long, string> nonRTAreas , IConfiguration configuration) { string jwtIssuer = configuration["Startup:JwtSettings:Issuer"]; string jwtAudience = configuration["Startup:JwtSettings:Audience"]; string jwtSecretKey = configuration["Startup:JwtSettings:SecretKey"]; string validMinutes = configuration["Startup:JwtSettings:AccessTokenValidMinutes"]; var tokenHandler = new JwtSecurityTokenHandler(); byte[] byteKey = Encoding.UTF8.GetBytes(jwtSecretKey); var authTime = DateTime.Now;//授权时间 var expiresAt = GetAccessTokenExpiresAt(authTime,validMinutes); var claims = GetClaimsByuser(user , userAuthoritys , fabs , rtAreas , nonRTAreas , jwtIssuer, jwtAudience, authTime); var tokenDescripor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = expiresAt, SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(byteKey), SecurityAlgorithms.HmacSha256Signature) }; SecurityToken securityToken = tokenHandler.CreateToken(tokenDescripor); string token = tokenHandler.WriteToken(securityToken); return token; } private static DateTime GetAccessTokenExpiresAt(DateTime authTime, string validMinutes) { double minutes = 20; double.TryParse(validMinutes, out minutes); return authTime.AddMinutes(minutes); } private static DateTime GetRefreshTokenExpiresAt(string validMinutes) { double minutes = 24*60; double.TryParse(validMinutes, out minutes); return DateTime.Now.AddMinutes(minutes); } public static int GetRefreshTokenExpiresAtNumber(IConfiguration configuration) { int number = 24 * 60; int.TryParse(configuration["Startup:JwtSettings:RefreshTokenValidMinutes"], out number); return number; } public static string GetRefreshToken(User user, HttpContext httpContext, IConfiguration configuration,string isForMems) { string validMinutes = configuration["Startup:JwtSettings:RefreshTokenValidMinutes"]; if (user == null) return string.Empty; var obj = new RefreshTokenDto() { UserName = user.UserName, UserID = user.UserID, ClientIP = httpContext.GetClientIP(), IsForMems = isForMems, ExpiresAt = GetRefreshTokenExpiresAt(validMinutes), LastLoginTime = user.LastLoginTime }; var jsonStr = JsonConvert.SerializeObject(obj); return AESEncrypt(jsonStr); } public static RefreshTokenDto GetLogonUsers(HttpContext httpContext, IConfiguration configuration, User user) { var expiresAt = GetRefreshTokenExpiresAt(configuration["Startup:JwtSettings:RefreshTokenValidMinutes"]); return new RefreshTokenDto() { UserID = user.UserID, UserName = user.UserName, ClientIP = httpContext.GetClientIP(), ExpiresAt = expiresAt, LastLoginTime = user.LastLoginTime, }; } public static string GetLogonUserKey(HttpContext httpContext, string userID) { return $"{LogonCachePrefixKey} - {userID} - {httpContext.GetClientIP()}"; } public static RefreshTokenDto GetRefreshTokenDataByToken(string refreshToken) { if (string.IsNullOrEmpty(refreshToken) == false) { var json = AESDEncrypt(refreshToken); var obj = JsonConvert.DeserializeObject<RefreshTokenDto>(json); return obj; } return null; } public static bool CheckRefreshToken(HttpContext httpContext, string refreshToken) { var tokenData = GetRefreshTokenDataByToken(refreshToken); if (tokenData != null) { var clientIP = httpContext.GetClientIP(); //验证 IP 、时间 if(tokenData.ClientIP.Equals(clientIP) && tokenData.ExpiresAt.CompareTo(DateTime.Now) > 0) { return true; } } return false; } private static IList<Claim> GetClaimsByuser(User user , List<string> userAuthoritys , Dictionary<long, string> fabs , Dictionary<long, string> rtAreas , Dictionary<long, string> nonRTAreas , string jwtIssuer, string jwtAudience, DateTime authTime) { string timeStamp = Datetime2Unix(authTime).ToString(); var claims = new List<Claim>(); if (user != null) { claims.Add(new Claim(JwtClaimTypes.Issuer, jwtIssuer)); claims.Add(new Claim(JwtClaimTypes.Audience, jwtAudience)); claims.Add(new Claim(JwtClaimTypes.Id, user.UserID)); claims.Add(new Claim(JwtClaimTypes.Name, user.UserName)); claims.Add(new Claim(JwtClaimTypes.Expiration, timeStamp)); } if (userAuthoritys != null && userAuthoritys.Count > 0) { foreach (var item in userAuthoritys) { claims.Add(new Claim(SPCPermissions.SPCPermissionPolicyName, item));//策略授权 } } return claims; } private static double Datetime2Unix(DateTime time) { DateTime startTime = TimeZoneInfo.ConvertTimeFromUtc(new DateTime(1970, 1, 1), TimeZoneInfo.Local); return (time - startTime).TotalSeconds; } //AES加密 private static string AESEncrypt(string str) { byte[] keyArray = UTF8Encoding.UTF8.GetBytes(SecretKey); byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(str); RijndaelManaged rDel = new RijndaelManaged(); rDel.Key = keyArray; rDel.Mode = CipherMode.ECB; rDel.Padding = PaddingMode.PKCS7; ICryptoTransform cTransform = rDel.CreateEncryptor(); byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return Convert.ToBase64String(resultArray, 0, resultArray.Length); } //AES解密 private static string AESDEncrypt(string str) { byte[] keyArray = UTF8Encoding.UTF8.GetBytes(SecretKey); byte[] toEncryptArray = Convert.FromBase64String(str); RijndaelManaged rDel = new RijndaelManaged(); rDel.Key = keyArray; rDel.Mode = CipherMode.ECB; rDel.Padding = PaddingMode.PKCS7; ICryptoTransform cTransform = rDel.CreateDecryptor(); byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return UTF8Encoding.UTF8.GetString(resultArray); } }
2.注册服务
public class Startup { private IServiceCollection _services; public Startup(IConfiguration configuration, IWebHostEnvironment env) { Configuration = configuration; Env = env; } public IConfiguration Configuration { get; } public IWebHostEnvironment Env { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); //redis注入 //services.AddRedisCacheSetup(); services.Configure<CookiePolicyOptions>(options => { options.MinimumSameSitePolicy = SameSiteMode.None; options.Secure = CookieSecurePolicy.SameAsRequest; }); //允许跨域注入 //services.AddCorsSetup(); services.AddCors(option => { option.AddPolicy(Configuration["Startup:Cors:PolicyName"], policy => { policy.WithOrigins(Configuration["Startup:Cors:IPs"] .Split(",", StringSplitOptions.RemoveEmptyEntries) .ToArray()) .SetIsOriginAllowedToAllowWildcardSubdomains(); policy.SetPreflightMaxAge(TimeSpan.FromMinutes(HttpContextExtensions.GePreflightExpireTime(Configuration)));//预检缓存最大时间 policy.AllowAnyHeader(); policy.AllowAnyMethod(); policy.AllowCredentials(); }); }); //swagger注入 //services.AddSwaggerAnalysisSetup(); ConfigureSwaggerServices(services); services.AddAuthentication(b => { b.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; b.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(a => { //jwt token参数设置 a.TokenValidationParameters = new TokenValidationParameters { NameClaimType = JwtClaimTypes.Name, //ClockSkew = TimeSpan.FromSeconds(10), //Token颁发机构 ValidIssuer = Configuration["Startup:JwtSettings:Issuer"], //颁发给谁 ValidAudience = Configuration["Startup:JwtSettings:Audience"], //这里的key要进行加密 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Startup:JwtSettings:SecretKey"])), }; a.Events = new JwtBearerEvents() { OnAuthenticationFailed = context => { context.Response.StatusCode = SPCJwtMiddleware.AuthenticationFailedCode; return Task.CompletedTask; }, OnForbidden = context => { context.Response.StatusCode = 200; context.Response.ContentType = "application/json;charset=utf-8"; return context.Response.WriteAsync(JsonConvert.SerializeObject(Dto.Common.SPCJsonResult.JsonErrorResult(SPCJwtMiddleware.ForbiddenCode, string.Empty))); }, OnChallenge = context => { context.HandleResponse(); return Task.CompletedTask; } }; }); services.AddAuthorization(b => { foreach (var item in SPCPermissions.GetStrings()) { b.AddPolicy(item, policy => policy.RequireClaim(SPCPermissions.SPCPermissionPolicyName, item)); } foreach (KeyValuePair<string,string[]> item in SPCPermissions.GetKeyValues()) { b.AddPolicy(item.Key, policy => policy.RequireClaim(SPCPermissions.SPCPermissionPolicyName, item.Value)); } }); //Session services.AddDistributedMemoryCache(); services.AddMemoryCache(); // session 设置 services.AddSession(options => { // 设置 Session 过期时间 options.IdleTimeout = TimeSpan.FromMinutes(SPCSessionExtensions.GetLoginExpireTime(Configuration)); // 跨域 谷歌浏览器 session id 丢失 options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Cookie.HttpOnly = true; }); string RouteName = Appsettings.app("AppSettings", "RouteName"); ; services.AddControllers(o => { // 全局异常过滤 o.Filters.Add(typeof(GlobalExceptionsFilter)); // 全局路由权限公约 //o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention()); // 全局路由前缀,统一修改路由 o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RouteName))); }).ConfigureApiBehaviorOptions(options => { //FromBody //options.SuppressConsumesConstraintForFormFileParameters = true; //options.SuppressInferBindingSourcesForParameters = true; options.SuppressModelStateInvalidFilter = true; options.SuppressMapClientErrors = true; }) //全局配置Json序列化处理 .AddNewtonsoftJson(options => { //忽略循环引用 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; //不使用驼峰样式的key options.SerializerSettings.ContractResolver = new DefaultContractResolver(); //设置时间格式 options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; }); _services = services; NHSessionStorage.SetServiceProvider(services.BuildServiceProvider()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env , IHostApplicationLifetime applicationLeftTime) { // 记录请求与返回数据 app.UseReuestResponseLog(); // 记录ip请求 app.UseIPLogMildd(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // 封装Swagger展示 //app.UseSwaggerMildd(() => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("WebAPI.index.html")); // CORS跨域 app.UseCors(Configuration["Startup:Cors:PolicyName"]); // 跳转https //app.UseHttpsRedirection(); // 使用静态文件 app.UseStaticFiles(); // 使用cookie app.UseCookiePolicy(); // 返回错误码 app.UseStatusCodePages(); // Routing app.UseRouting(); // 这种自定义授权中间件,可以尝试,但不推荐 // app.UseJwtTokenAuth(); // 先开启认证 app.UseAuthentication(); app.UseMiddleware<SPCJwtMiddleware>(); // 然后是授权中间件 app.UseAuthorization(); // 开启异常中间件,要放到最后 //app.UseExceptionHandlerMidd(); // 性能分析 app.UseMiniProfiler(); app.UseSwagger(); app.UseSwaggerUI(options => { typeof(ApiGroupNames).GetFields().Skip(1).ToList().ForEach(f => { var info = f.GetCustomAttributes(typeof(GroupInfoAttribute), false).OfType<GroupInfoAttribute>().FirstOrDefault(); options.SwaggerEndpoint($"/swagger/{f.Name}/swagger.json", f.Name); //options.SwaggerDoc(f.Name, new Microsoft.OpenApi.Models.OpenApiInfo //{ // Title = info?.Title, // Version = info?.Version, // Description = info?.Description //}); }); }); app.UseSession(); applicationLeftTime.ApplicationStarted.Register(() => { string resourcePath = Configuration["AppSettings:ResourcePath"]; string resourceName = Configuration["AppSettings:ResourceName"]; UtilCulture.InitialResource(resourceName, resourcePath, "en-US"); }); app.UseMiddleware<SPCCultureMiddleware>(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } private void ConfigureSwaggerServices(IServiceCollection services) { services.AddSwaggerGen(c => { //遍历ApiGroupNames所有枚举值生成接口文档,Skip(1)是因为Enum第一个FieldInfo是内置的一个Int值 typeof(ApiGroupNames).GetFields().Skip(1).ToList().ForEach(f => { var info = f.GetCustomAttributes(typeof(GroupInfoAttribute), false).OfType<GroupInfoAttribute>().FirstOrDefault(); c.SwaggerDoc(f.Name, new OpenApiInfo { //Title = info?.Title, //Version = info?.Version, //Description = info?.Description Title = $"{info?.Title} 接口文档——{RuntimeInformation.FrameworkDescription}", Description = $"{info?.Title} " + info?.Version, Contact = new OpenApiContact { Name = info?.Title, Email = "contact@fa-software.com", Url = new Uri("https://www.fa-software.com/") }, License = new OpenApiLicense { Name = info?.Title + " 官方简介", Url = new Uri("https://www.fa-software.com/") } }); }); // 使用 [ApiGroup(ApiGroupNames.UI)] 进行分组 c.DocInclusionPredicate((docName, apiDescription) => { if (docName == ApiGroupNames.SPC_API.ToString()) { return true; } //反射拿到值 var actionlist = apiDescription.ActionDescriptor.EndpointMetadata.Where(x => x is ApiGroupAttribute); if (actionlist.Count() > 0) { //判断是否包含这个分组 var actionfilter = actionlist.FirstOrDefault() as ApiGroupAttribute; return actionfilter.GroupName.Count(x => x.ToString() == docName) > 0; } return false; }); var basePath = Path.GetDirectoryName(typeof(Startup).Assembly.Location); var xmlPath = Path.Combine(basePath, "WebAPI.xml"); c.IncludeXmlComments(xmlPath, true); c.AddSecurityRequirement(new OpenApiSecurityRequirement() { { new OpenApiSecurityScheme() { Reference = new OpenApiReference() { Id = JwtBearerDefaults.AuthenticationScheme, Type = ReferenceType.SecurityScheme } }, Array.Empty<string>() } }); c.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme() { Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, }); }); } }
基于策略的授权(角色授权就是特殊的策略授权)
services.AddAuthorization(b => { //设置一个值 foreach (var item in SPCPermissions.GetStrings()) { b.AddPolicy(item, policy => policy.RequireClaim(SPCPermissions.SPCPermissionPolicyName, item)); } //设置多个值或关系 foreach (KeyValuePair<string,string[]> item in SPCPermissions.GetKeyValues()) { b.AddPolicy(item.Key, policy => policy.RequireClaim(SPCPermissions.SPCPermissionPolicyName, item.Value));//item.Value 为 string[] { "r1", "r2" }
} });
原理不懂请参考:https://www.cnblogs.com/wangjunwei/p/10964604.html#autoid-7-0-0