强制实施 HTTPS 在 ASP.NET Core
System.IdentityModel.Tokens.Jwt
JWT(JSON Web Token) 结构
JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串
标头(Header)
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
*
{
"alg": "HS256",
"type": "JWT"
}
*
*
有效载荷(Payload)
是JWT的主体内容部分,是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
*
签名(Signature)
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。
{
"sub": "123456",
"name": "admin",
"admin": true
}
*
管理 NuGet 程序包(N)...
Microsoft.AspNetCore.Authentication.JwtBearer
JwtSecurityToken:
JwtSecurityTokenHandler:创建、校验token,返回ClaimsPrincipal
CanReadToken():确定字符串是否是格式良好的Json Web令牌(JWT)
ReadJwtToken(string token):token字符串转为JwtSecurityToken对象
ValidateToken(string token、TokenValidationParameters parameter,out SecurityToken validatedToken):校验token,返回ClaimsIdentity
TokenValidationParameters:
SecurityKey:
本文章介绍的是资源服务器(api接口)验证 Token 是否合法
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.IO;
using AutoMapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using IdentityModel;
using log4net;
using log4net.Config;
using log4net.Repository;
using System.Net.Mime;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Threading.Tasks;
using System.Text;
using IdentityServer4.AccessTokenValidation;
using IdentityModel.AspNetCore.OAuth2Introspection;
using IdentityModel.Client;
using Microsoft.AspNetCore.Http;
namespace MobileNurse.WebAPI
{
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
public static ILoggerRepository loggerRepository { get; set; }
readonly string AllowSpecificOrigins = "AllowSpecificOrigins";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
string dbType = Configuration["DbSql:DbType"];
switch (dbType)
{
case "SqlServer":
//services.AddSingleton(Configuration.GetConnectionString("DefaultConnection"));
services.AddSingleton(Configuration["DbSql:SqlServerConnection"]);
break;
case "Oracle":
services.AddSingleton(Configuration["DbSql:OracleConnection"]);
break;
case "MySql":
services.AddSingleton(Configuration["DbSql:MySqlConnection"]);
break;
default:
break;
}
services.AddSingleton<IDbConnection, SqlConnection>();
#region 认证 第 1 步
services.AddAuthentication(config =>
{
config.DefaultScheme = "Bearer";
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration["IdentityAuthentication:Authority"];
options.ApiName = Configuration["IdentityAuthentication:ApiName"];
options.ApiSecret = Configuration["IdentityAuthentication:ApiSecret"];
options.RequireHttpsMetadata = true;
options.JwtBearerEvents = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
//Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
context.Response.Headers.Add("Token-Expired", "true");
context.Response.ContentType = "application/json";
context.Response.WriteAsync(JsonConvert.SerializeObject(new { code = "401", msg = "Unauthorized" }));
return Task.CompletedTask;
}
};
options.OAuth2IntrospectionEvents = new OAuth2IntrospectionEvents
{
OnAuthenticationFailed = context =>
{
//Token expired
if (context.Error.GetType() == typeof(SecurityTokenExpiredException))
context.Response.Headers.Add("Token-Expired", "true");
context.Response.ContentType = "application/json";
context.Response.WriteAsync(JsonConvert.SerializeObject(new { code = "401", msg = "Unauthorized" }));
return Task.CompletedTask;
}
};
//options.EnableCaching = true;
//options.CacheDuration = TimeSpan.FromMinutes(10);
});
#region 经测试可以使用(OK) 第 1 步
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration["IdentityAuthentication:Authority"];
options.ApiName = Configuration["IdentityAuthentication:ApiName"];
options.ApiSecret = Configuration["IdentityAuthentication:ApiSecret"];
options.RequireHttpsMetadata = true;
options.JwtBearerEvents = new JwtBearerEvents { };
options.OAuth2IntrospectionEvents = new OAuth2IntrospectionEvents { };
//options.EnableCaching = true;
//options.CacheDuration = TimeSpan.FromMinutes(10);
});
services.AddAuthentication(OAuth2IntrospectionDefaults.AuthenticationScheme)
.AddOAuth2Introspection(options =>
{
options.Authority = Configuration["IdentityAuthentication:Authority"];
options.ClientId = Configuration["IdentityAuthentication:ApiName"];
options.ClientSecret = Configuration["IdentityAuthentication:ApiSecret"];
//options.EnableCaching = true;
//options.CacheDuration = TimeSpan.FromMinutes(10);
options.Events = new OAuth2IntrospectionEvents
{
OnAuthenticationFailed = context =>
{
// 错误访问
var tokenType = context.Options.TokenTypeHint;
if (context.Error.GetType() == typeof(SecurityTokenExpiredException))
context.Response.Headers.Add("Token-Expired", "true");
context.Response.ContentType = "application/json";
//context.Response.StatusCode = StatusCodes.Status200OK;
//context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.WriteAsync(JsonConvert.SerializeObject(new { code = "401", msg = "Unauthorized" }));
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
// 正常访问
var token = context.SecurityToken;
return Task.CompletedTask;
},
};
});
#endregion
#region
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme,
jwtOptions =>
{
// jwt bearer options
jwtOptions.Authority = Configuration["IdentityAuthentication:Authority"];
jwtOptions.RequireHttpsMetadata = true;
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
//Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
context.Response.Headers.Add("Token-Expired", "true");
context.Response.ContentType = "application/json";
//context.Response.StatusCode = StatusCodes.Status200OK;
//context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.WriteAsync(JsonConvert.SerializeObject(new { code = "401", msg = "Unauthorized" }));
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
context.Options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true, //是否验证超时(失效)时间 当设置exp和nbf时有效
ValidateIssuerSigningKey = true, //是否验证密钥
ValidAudience = "http://localhost:49999",//Audience
ValidIssuer = "http://localhost:49998",//Issuer,这两项和登陆时颁发的一致
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("123456888jdijxhelloworldprefect")), //拿到SecurityKey
//缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟 //注意这是缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟
ClockSkew = TimeSpan.FromSeconds(30) //设置过期时间
};
return Task.CompletedTask;
}
};
},
referenceOptions =>
{
// oauth2 introspection options
referenceOptions.Authority = Configuration["IdentityAuthentication:Authority"];
referenceOptions.ClientId = Configuration["IdentityAuthentication:ApiName"];
referenceOptions.ClientSecret = Configuration["IdentityAuthentication:ApiSecret"];
//referenceOptions.EnableCaching = true;
//referenceOptions.CacheDuration = TimeSpan.FromMinutes(10);
referenceOptions.Events = new IdentityModel.AspNetCore.OAuth2Introspection.OAuth2IntrospectionEvents
{
OnAuthenticationFailed = context =>
{
string a = "";
if (context.Error.GetType() == typeof(SecurityTokenExpiredException))
context.Response.Headers.Add("Token-Expired", "true");
context.Response.ContentType = "application/json";
//context.Response.StatusCode = StatusCodes.Status200OK;
//context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.WriteAsync(JsonConvert.SerializeObject(new { code = "401", msg = "Unauthorized" }));
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
return Task.CompletedTask;
}
};
});
#endregion
#region Jwt 类型
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = Configuration["IdentityAuthentication:Authority"];
options.RequireHttpsMetadata = true;
options.Audience = Configuration["IdentityAuthentication:ApiName"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,//是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30),
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
//Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
context.Response.Headers.Add("Token-Expired", "true");
context.Response.ContentType = "application/json";
context.Response.WriteAsync(JsonConvert.SerializeObject(new { code = "401", msg = "Unauthorized" }));
return Task.CompletedTask;
}
};
});
#endregion
#endregion
services.AddControllers(options =>
{
//第 2 步
options.Filters.Add<QDAuthorizeAttribute>();
//options.Filters.Add<PermissionAttribute>();
options.Filters.Add<ClientIpCheckFilter>();
options.Filters.Add(typeof(CustomExceptionFilter));
options.Filters.Add<ValidateModelAttribute>();
options.Filters.Add<ResultFilterAttribute>();
options.Filters.Add<HttpResponseExceptionFilter>();
//options.UseCentralRoutePrefix(new RouteAttribute("api/v0.1/[controller]/[action]"));
//options.UseCentralRoutePrefix(new RouteAttribute($"api/v{Configuration["AppSettings:ApiVersion"]}/[controller]/[action]"));
//options.UseCentralRoutePrefix(new RouteAttribute($"api/v{Configuration["AppSettings:ApiVersion"]}/[controller]"));
//options.UseCentralRoutePrefix(new RouteAttribute($"api/v{Configuration["AppSettings:ApiVersion"]}"));
options.UseCentralRoutePrefix(new RouteAttribute($"api"));
options.MaxModelValidationErrors = 50;
})
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var result = new BadRequestObjectResult(context.ModelState);
result.ContentTypes.Add(MediaTypeNames.Application.Json);
result.ContentTypes.Add(MediaTypeNames.Application.Xml);
return result;
};
//options.SuppressConsumesConstraintForFormFileParameters = true;
//options.SuppressInferBindingSourcesForParameters = true;
//options.SuppressModelStateInvalidFilter = true;
//options.SuppressMapClientErrors = true;
//options.ClientErrorMapping[404].Link = "https://httpstatuses.com/404";
})
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
});
#region 仓储注入
foreach (var item in GetAssemblyName("MobileNurse.Repository"))
services.AddTransient(item.Key, item.Value);
#endregion
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(0, 1);
});
#region Swagger
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
#endregion
#region 跨域访问
var urls = Configuration["AppSettings:Cores"].Split(',');
services.AddCors(options =>
{
options.AddPolicy(AllowSpecificOrigins, builder => { builder.WithOrigins(urls); });
});
#endregion
services.AddSignalR(options =>
{
options.KeepAliveInterval = TimeSpan.FromSeconds(5);
});
services.AddAutoMapper(typeof(AutoMapperInit));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
//app.UseHsts();
}
//app.UseHttpsRedirection();
app.UseStaticFiles();
//app.UseMiddleware<AdminSafeListMiddleware>(Configuration["AdminSafeList"]);
app.UseStatusCodePages();
app.UseCors(AllowSpecificOrigins);
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); });
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<Chat>("/chathub");
});
app.UseApiVersioning();
}
private Dictionary<Type, Type> GetAssemblyName(string assemblyName)
{
var result = new Dictionary<Type, Type>();
if (!String.IsNullOrEmpty(assemblyName))
{
Assembly assembly = Assembly.Load(assemblyName);
List<Type> ts = assembly.GetTypes().ToList();
foreach (var item in ts.Where(x => x.IsClass && !x.IsAbstract && !x.IsGenericType))
result.Add(item.GetInterfaces().FirstOrDefault(x => !x.Name.StartsWith("IRepository")), item);
}
return result;
}
}
}
*
*
*
禁用 HTTPS 请求方式
Properties/launchSettings.json(推荐)
在我们项目的Properties/launchSettings.json文件中,把 "sslPort": 44383 修改为 "sslPort": 0
*
UseHttpsRedirection 方式(貌似有问题)
app.UseHttpsRedirection() 为应用添加了重定向HTTP请求到HTTPS请求的中间件。如果使用HTTP,那么就注释掉这行代码。
*
*
*
*
*