NET Core里Jwt的生成倒是不麻烦,就是要踩完坑才知道正确的生成姿势……

Jwt的结构

jwt的结构是{Header}.{Playload}.{Signature}三截。其中Header和Playload是base64编码字符串,Signature是签名字符串。

Header是比较固定的

typ是固定的“JWT”。

alg是你使用的签名算法,通常有HS256和RS256两种。

例子:

{
"alg": "RS256",
"typ": "JWT"
}

Playload你要写入自定义内容的区域。

例子:

{
"role": "myRole",
"org": "myOrg",
"jti": "a8b8ea421e834fd1b90ac09dbf40e158",
"nbf": 1498397026,
"exp": 1498483426,
"iat": 1498397026,
"iss": "Zonciu"
}

Signature是使用Header中alg的算法来对{Header}.{Playload}这个结构进行签名生成一个字符串,然后接到Jwt串的最后,形成带签名的Jwt。在签发Token之后,其他应用就可以使用相同的算法和Key来重新运算并对比签名,由此判断Token中的信息是否被修改过。

注意:Jwt默认是明文的,不要把敏感数据放到playload里去,当然你可以先把要放进去的数据先加密,把密文放到playload里(但是最好不要这样做)。

Jwt创建过程

在NET Core 1.1里是通过JwtSecurityTokenHandler(程序集System.IdentityModel.Tokens.Jwt)这个类来创建Jwt。

但是创建Jwt之前还要先生成一个SecurityTokenDescriptor(程序集Microsoft.IdentityModel.Tokens),所以就比较绕。

Jwt工厂代码:

我这里用的是RS256签名(RsaSecurityKey),是用私钥签名,公钥验证。如果是要用HS256的话,就把签名和验证的SecurityKey都换成SymmetricSecurityKey(如果我没记错的话)

这里的公钥密钥是方便测试所以硬编码的,生产环境不要这样搞。

public class JwtFactory
{
public const string publicKey = @"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArM1i3Q9ukD7DWhuYWFFH
fAR0Ao8W5OnrlaZH2aB2G+dgvrlW6VzFVtjZQLWkQ488j65MTS7Nr0GITsoGB5r4
LRdnua5PwkLCML9ZaqOejMYix4mc7ZfencsQvy5bHotfvEAad42IhvHROseqC77W
5Zbt+YDtA7aU2aBKzHufZ1vPgWKPOgGVJup6sjqviXz3qP2HD5K9ae0iyYDptKKN
e5kb36DNTD7P62yWrVpZpy0MpMkCBZJdDeUgtA3lsxY5FcEaB5Bk+O695djogq84
vsyTKP1Jp6GrgszIuJCb52dI5c1lY5tN6bxsMTYB/Hxhgo7dGG/LBU9lMoT83E15
KQIDAQAB
-----END PUBLIC KEY-----
"; public const string privateKey = @"-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEArM1i3Q9ukD7DWhuYWFFHfAR0Ao8W5OnrlaZH2aB2G+dgvrlW
6VzFVtjZQLWkQ488j65MTS7Nr0GITsoGB5r4LRdnua5PwkLCML9ZaqOejMYix4mc
7ZfencsQvy5bHotfvEAad42IhvHROseqC77W5Zbt+YDtA7aU2aBKzHufZ1vPgWKP
OgGVJup6sjqviXz3qP2HD5K9ae0iyYDptKKNe5kb36DNTD7P62yWrVpZpy0MpMkC
BZJdDeUgtA3lsxY5FcEaB5Bk+O695djogq84vsyTKP1Jp6GrgszIuJCb52dI5c1l
Y5tN6bxsMTYB/Hxhgo7dGG/LBU9lMoT83E15KQIDAQABAoIBACvHrXCMZFqvTBcc
PrDBhvboueucDRTaHxG/Gx0MBmBzcpNfqaFeG7ExJ3m5i3CCbbmJU1OKtBne5IXx
sS1kGdRyxZjJjPOOrlxjXmgiJB1OZalgOB4KCCC6Pffx6qwGa67qHsqDVT+7LGNU
CsUHCLMKViiMfYAfVf79GXZNK8mnki8pPCXc50qCGre3LRq6Egmb8NIsSIj05aHM
UeQbOuOM+Bbf/dICYLV8qFmR2xpM3G5CmVX07LzGCX5k320z0kHrxH/r6QXl/bEP
X5kMRdoYfUoX6jDnd71aoLVDaPqZvDLDOMDG59riqcMsaWVqv7iZn2keWT6WTPfE
ZwGl4gECgYEA5GJlVXFcg91lSHWVprXeJHwIT4um8reGB6xt1CMxmhGx/e5vUiSo
KirrYEf4sDlE81MY0oLo0oZzDzadvTlPDFazacZZlNOVattOdC/L2TKzkfmsR18o
j1LsQApnDVVXGYLzGoQmASPk9GtfOE1phKZSyXZ3pV3D/JFQ1vHWmVkCgYEAwbJ0
ohW369yGSZUdV/vnpcpAmqav3duT1vx7UIUW7OUv5TTYmeXnpHV9m3Egdtsgy4Cy
eULrnKJqQ0Cnon4Lg/wzZPVKKdnBH94+duhSNu4+Q5DNFp9IEi76KFm7UI8vOX4e
4QtAIQUUBQjnjcW0fLlOw1r1Nkqcrwbw8dVMFFECgYAVTmCpwfOhkav7QIz/ioP4
32FfGmYuypREbv+oBMiB2RjD2dSk0yqlFG/1AYHf3tfh42SzbucNjOF7D9tTZd9M
BWKjgY+l5L9Rwrfk+viHgMVj3ukFl4kPJetIZjAK/GUtyhun46AwBws7CjFN7Vrk
tyeOB/FNihvYmi3yf4lHsQKBgQC1pntVClMq4ewaE7qqKbart4pwvoPN5z+1baDj
+Xxve9w38yBy67YaeIjsfuI4NPaDgtVdfVHi2joXignsDJMWGy3Dr3n2150TGuSv
tN5tX26LBMAhSA1Z6C54KvbM7QsXutyQpnFkxhNpSVmGjnPeSBbChInUeZKJXlQW
J7eqkQKBgDX88tAAM/FIZANoKPfmuoiFJ33USdC5mwNsHNBZvMAR2UsSeBSJZzy3
iar0ldCuTBolGpwRkLs1+pgoc4XDGDdV9367gjppQa0EqvrMwqNe8hcR7K/Dm+MU
B1lk88g8TJK7fUd6ibkJtmWTZXMGdCSC2+NG1mjeRbf1d2TB+zHM
-----END RSA PRIVATE KEY-----
"; public const string JwtAlgorithm = "RS256";
public const string Issuer = "Zonciu";
public const int Lifttime = ; private TokenValidationParameters _tokenValidationParameters { get; set; } = new TokenValidationParameters()
{
ValidateActor = false,
ValidateAudience = false, ValidateIssuer = true,
ValidIssuer = "Zonciu", ValidateIssuerSigningKey = true,
IssuerSigningKey = new RsaSecurityKey(Rsa.CreateFromPublicKey(publicKey)), ValidateLifetime = true,
RequireExpirationTime = true,
}; private SigningCredentials _signingCredentials { get; set; } =
new SigningCredentials(new RsaSecurityKey(Rsa.CreateFromPrivateKey(privateKey)), JwtAlgorithm); private JwtSecurityTokenHandler _jwtSecurityTokenHandler { get; set; } = new JwtSecurityTokenHandler(); /// <summary>
/// 创建编码后的Jwt
/// </summary>
/// <param name="claimsIdentity">身份声明</param>
/// <param name="jwtId">令牌Id</param>
/// <returns></returns>
public string CreateEncodedJwt(ClaimsIdentity claimsIdentity, string jwtId)
{
var jwtDesc = CreateSecurityTokenDescriptor(claimsIdentity, jwtId);
return _jwtSecurityTokenHandler.CreateEncodedJwt(jwtDesc);
} /// <summary>
/// 创建Jwt
/// </summary>
/// <param name="descriptor"></param>
/// <returns></returns>
public JwtSecurityToken CreateJwt(SecurityTokenDescriptor descriptor)
{
return _jwtSecurityTokenHandler.CreateJwtSecurityToken(descriptor);
} /// <summary>
/// 创建Jwt描述
/// </summary>
/// <param name="claimsIdentity">身份声明</param>
/// <param name="jwtId">令牌Id</param>
/// <returns></returns>
public SecurityTokenDescriptor CreateSecurityTokenDescriptor(ClaimsIdentity claimsIdentity, string jwtId)
{
claimsIdentity.AddClaim(new Claim("jti", jwtId));
var issueTime = DateTime.Now;
var jwtDesc = new SecurityTokenDescriptor
{
Issuer = Issuer,
IssuedAt = issueTime,
Expires = issueTime + TimeSpan.FromSeconds(Lifttime),
SigningCredentials = _signingCredentials,
Subject = claimsIdentity
};
return jwtDesc;
} /// <summary>
/// 校验Jwt
/// </summary>
/// <param name="jwtToken"></param>
/// <param name="securityToken"></param>
/// <returns></returns>
public ClaimsPrincipal ValidateJwtToken(string jwtToken, out SecurityToken securityToken)
{
return _jwtSecurityTokenHandler.ValidateToken(jwtToken, _tokenValidationParameters, out securityToken);
}
}

ValidateJwtToken中校验失败直接抛出异常,校验成功则返回ClaimsPrincipal(Controller里HttpContext.User这货),并out出从string解析到的SecurityToken(Jwt反序列化的意思)。在NET Core 1.1里不用主动调用验证方法,这里只是用来做测试或者其他用途。

测试:

 public static void JwtTest()
{
var jwtFactory = new JwtFactory(); var claims = new List<Claim>()
{
new Claim("role", "myRole"),
new Claim("org", "myOrg")
};
var jti = Guid.NewGuid().ToString("N");
var jwtString = jwtFactory.CreateEncodedJwt(new ClaimsIdentity(claims), jti);
var jwt = jwtFactory.CreateJwt(jwtFactory.CreateSecurityTokenDescriptor(new ClaimsIdentity(claims), jti));
var claimsPrincipal = jwtFactory.ValidateJwtToken(jwtString, out var token);
var jwtClaims = claimsPrincipal.Claims.Select(
claim => new
{
claim.Type,
claim.Value
}).ToList();
Console.WriteLine(
$@"
playloadClaims: {jwtClaims.ToJsonString(camelCase: false, indented: true)} jwtString: {jwtString} token.ToJsonString: {token.ToJsonString(camelCase: false, indented: true)} jwt: {jwt}.{jwt.RawSignature} ");
}

输出结果:

playloadClaims: [
{
"Type": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
"Value": "myRole"
},
{
"Type": "org",
"Value": "myOrg"
},
{
"Type": "jti",
"Value": "a8b8ea421e834fd1b90ac09dbf40e158"
},
{
"Type": "nbf",
"Value": "1498397026"
},
{
"Type": "exp",
"Value": "1498483426"
},
{
"Type": "iat",
"Value": "1498397026"
},
{
"Type": "iss",
"Value": "Zonciu"
}
] jwtString: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoibXlSb2xlIiwib3JnIjoibXlPcmciLCJqdGkiOiJhOGI4ZWE0MjFlODM0ZmQxYjkwYWMwOWRiZjQwZTE1OCIsIm5iZiI6MTQ5ODM5NzAyNiwiZXhwIjoxNDk4NDgzNDI2LCJpYXQiOjE0OTgzOTcwMjYsImlzcyI6IlpvbmNpdSJ9.F5g9XanhffrPGwlvve5YA7fxU9-ENnunkqxTuRKE5rTWo2fthxs_MHXew44LJ21MJOxeWgS6j5Asws9VAjHGjOuxclFxhuf4jTE9otk4w8JEKf8IHhERJy4qKO1ooNUM3wp-WGzreJNCWRNxax2eT4EiCJZsawUBZtl-r4yztNMUGea37wgnta4CyzZTzNErzDeaAZLMb96YYdOjKSKP5Od5Hm-W0tqWaRhyzOTQ9nqHW7AV_g7qffUBQGUwqdL8H4dsDxzelS2xY5Ypjr-a2Z6hByYPPwyiBXkwXJYO9wKCSVuG72Y54UG_6R-dbowPmTwvirsnyeqWjQ2dFY0l9Q token.ToJsonString: {
"Actor": null,
"Audiences": [],
"Claims": [
{
"Issuer": "Zonciu",
"OriginalIssuer": "Zonciu",
"Properties": {},
"Subject": null,
"Type": "role",
"Value": "myRole",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
},
{
"Issuer": "Zonciu",
"OriginalIssuer": "Zonciu",
"Properties": {},
"Subject": null,
"Type": "org",
"Value": "myOrg",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
},
{
"Issuer": "Zonciu",
"OriginalIssuer": "Zonciu",
"Properties": {},
"Subject": null,
"Type": "jti",
"Value": "a8b8ea421e834fd1b90ac09dbf40e158",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
},
{
"Issuer": "Zonciu",
"OriginalIssuer": "Zonciu",
"Properties": {},
"Subject": null,
"Type": "nbf",
"Value": "1498397026",
"ValueType": "http://www.w3.org/2001/XMLSchema#integer"
},
{
"Issuer": "Zonciu",
"OriginalIssuer": "Zonciu",
"Properties": {},
"Subject": null,
"Type": "exp",
"Value": "1498483426",
"ValueType": "http://www.w3.org/2001/XMLSchema#integer"
},
{
"Issuer": "Zonciu",
"OriginalIssuer": "Zonciu",
"Properties": {},
"Subject": null,
"Type": "iat",
"Value": "1498397026",
"ValueType": "http://www.w3.org/2001/XMLSchema#integer"
},
{
"Issuer": "Zonciu",
"OriginalIssuer": "Zonciu",
"Properties": {},
"Subject": null,
"Type": "iss",
"Value": "Zonciu",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
}
],
"EncodedHeader": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
"EncodedPayload": "eyJyb2xlIjoibXlSb2xlIiwib3JnIjoibXlPcmciLCJqdGkiOiJhOGI4ZWE0MjFlODM0ZmQxYjkwYWMwOWRiZjQwZTE1OCIsIm5iZiI6MTQ5ODM5NzAyNiwiZXhwIjoxNDk4NDgzNDI2LCJpYXQiOjE0OTgzOTcwMjYsImlzcyI6IlpvbmNpdSJ9",
"Header": {
"alg": "RS256",
"typ": "JWT"
},
"Id": "a8b8ea421e834fd1b90ac09dbf40e158",
"Issuer": "Zonciu",
"Payload": {
"role": "myRole",
"org": "myOrg",
"jti": "a8b8ea421e834fd1b90ac09dbf40e158",
"nbf": 1498397026,
"exp": 1498483426,
"iat": 1498397026,
"iss": "Zonciu"
},
"InnerToken": null,
"RawAuthenticationTag": null,
"RawCiphertext": null,
"RawData": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoibXlSb2xlIiwib3JnIjoibXlPcmciLCJqdGkiOiJhOGI4ZWE0MjFlODM0ZmQxYjkwYWMwOWRiZjQwZTE1OCIsIm5iZiI6MTQ5ODM5NzAyNiwiZXhwIjoxNDk4NDgzNDI2LCJpYXQiOjE0OTgzOTcwMjYsImlzcyI6IlpvbmNpdSJ9.F5g9XanhffrPGwlvve5YA7fxU9-ENnunkqxTuRKE5rTWo2fthxs_MHXew44LJ21MJOxeWgS6j5Asws9VAjHGjOuxclFxhuf4jTE9otk4w8JEKf8IHhERJy4qKO1ooNUM3wp-WGzreJNCWRNxax2eT4EiCJZsawUBZtl-r4yztNMUGea37wgnta4CyzZTzNErzDeaAZLMb96YYdOjKSKP5Od5Hm-W0tqWaRhyzOTQ9nqHW7AV_g7qffUBQGUwqdL8H4dsDxzelS2xY5Ypjr-a2Z6hByYPPwyiBXkwXJYO9wKCSVuG72Y54UG_6R-dbowPmTwvirsnyeqWjQ2dFY0l9Q",
"RawEncryptedKey": null,
"RawInitializationVector": null,
"RawHeader": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
"RawPayload": "eyJyb2xlIjoibXlSb2xlIiwib3JnIjoibXlPcmciLCJqdGkiOiJhOGI4ZWE0MjFlODM0ZmQxYjkwYWMwOWRiZjQwZTE1OCIsIm5iZiI6MTQ5ODM5NzAyNiwiZXhwIjoxNDk4NDgzNDI2LCJpYXQiOjE0OTgzOTcwMjYsImlzcyI6IlpvbmNpdSJ9",
"RawSignature": "F5g9XanhffrPGwlvve5YA7fxU9-ENnunkqxTuRKE5rTWo2fthxs_MHXew44LJ21MJOxeWgS6j5Asws9VAjHGjOuxclFxhuf4jTE9otk4w8JEKf8IHhERJy4qKO1ooNUM3wp-WGzreJNCWRNxax2eT4EiCJZsawUBZtl-r4yztNMUGea37wgnta4CyzZTzNErzDeaAZLMb96YYdOjKSKP5Od5Hm-W0tqWaRhyzOTQ9nqHW7AV_g7qffUBQGUwqdL8H4dsDxzelS2xY5Ypjr-a2Z6hByYPPwyiBXkwXJYO9wKCSVuG72Y54UG_6R-dbowPmTwvirsnyeqWjQ2dFY0l9Q",
"SecurityKey": null,
"SignatureAlgorithm": "RS256",
"SigningCredentials": null,
"EncryptingCredentials": null,
"SigningKey": {
"HasPrivateKey": false,
"KeySize": 2048,
"Parameters": {
"D": null,
"DP": null,
"DQ": null,
"Exponent": null,
"InverseQ": null,
"Modulus": null,
"P": null,
"Q": null
},
"Rsa": {
"LegalKeySizes": [
{
"MinSize": 512,
"MaxSize": 16384,
"SkipSize": 64
}
],
"KeySize": 2048
},
"KeyId": null,
"CryptoProviderFactory": {
"CustomCryptoProvider": null
}
},
"Subject": null,
"ValidFrom": "2017-06-25T13:23:46Z",
"ValidTo": "2017-06-26T13:23:46Z"
} jwt: {"alg":"RS256","typ":"JWT"}.{"role":"myRole","org":"myOrg","jti":"a8b8ea421e834fd1b90ac09dbf40e158","nbf":1498397026,"exp":1498483426,"iat":1498397026,"iss":"Zonciu"}.F5g9XanhffrPGwlvve5YA7fxU9-ENnunkqxTuRKE5rTWo2fthxs_MHXew44LJ21MJOxeWgS6j5Asws9VAjHGjOuxclFxhuf4jTE9otk4w8JEKf8IHhERJy4qKO1ooNUM3wp-WGzreJNCWRNxax2eT4EiCJZsawUBZtl-r4yztNMUGea37wgnta4CyzZTzNErzDeaAZLMb96YYdOjKSKP5Od5Hm-W0tqWaRhyzOTQ9nqHW7AV_g7qffUBQGUwqdL8H4dsDxzelS2xY5Ypjr-a2Z6hByYPPwyiBXkwXJYO9wKCSVuG72Y54UG_6R-dbowPmTwvirsnyeqWjQ2dFY0l9Q

公钥和密钥是openssl生成的2048位key

openssl genrsa -out rsa_private_key.pem 2048
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

把jwtString和公钥、密钥拿去https://jwt.io/验证成功

Jwt在NET Core 1.1的引入方式:

在Startup的Configure方法中加入这个(JwtOptions和JwtEventsDefaults是我自己写的类)

因为Jwt校验失败的原因有很多种,校验失败时触发OnAuthenticationFailed事件,这个部分可以自己实现,也可以不管,默认会在Response的Header里添加错误信息。

 app.UseJwtBearerAuthentication(
new JwtBearerOptions
{
RequireHttpsMetadata = jwtFactory.JwtOptions.EnableHttps,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new JwtBearerEvents()
{
OnAuthenticationFailed = JwtEventsDefaults.AuthenticationFailed
},
ClaimsIssuer = jwtFactory.JwtOptions.Issuer,
TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateActor = false,
ValidateIssuer = true,
ValidIssuer = jwtFactory.JwtOptions.Issuer,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new RsaSecurityKey(jwtFactory.JwtOptions.PublicRsa),
RequireExpirationTime = true,
}
});

有一个比较头疼的地方就是role这个claim,在jwt里是“role”没有变,解析之后会变成“http://schemas.microsoft.com/ws/2008/06/identity/claims/role”,这里要小心。(话说谁有办法解决这个问题吗?虽说改个名字就能避免被转换,但是好别扭也好憋屈……)

Jwt的删除办法

在我的实现中是添加了“jti”这个键,即Jwt Id,当服务端需要使Jwt提前失效,只能通过stateful的方式处理(因为你没办法保证客户端那边真的删掉了这个Jwt,比如证件你只能登报声明作废,但是如果其他单位没有检查作废信息,别人拿着你的证件去搞事情一样会通过),即在服务端把这个jti加入黑名单,黑名单删除时间是Jwt有效期之后的时间。这样的话就可以使得Jwt失效前通过黑名单来完成拒绝,黑名单清除之后通过Jwt的exp来完成拒绝。

NET Core 1.1中使用Jwt的更多相关文章

  1. ASP.Net Core 3.0 中使用JWT认证

    JWT认证简单介绍     关于Jwt的介绍网上很多,此处不在赘述,我们主要看看jwt的结构.     JWT主要由三部分组成,如下: HEADER.PAYLOAD.SIGNATURE HEADER包 ...

  2. 翻译一篇英文文章,主要是给自己看的——在ASP.NET Core Web Api中如何刷新token

    原文地址 :https://www.blinkingcaret.com/2018/05/30/refresh-tokens-in-asp-net-core-web-api/ 先申明,本人英语太菜,每次 ...

  3. 如何简单的在 ASP.NET Core 中集成 JWT 认证?

    前情提要:ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统 文章超长预警(1万字以上),不想看全部实现过程的同学可以直接跳转到末尾查看成果或者一键安装相关的 nuget 包 自上一 ...

  4. ASP.NET Core WebAPI中使用JWT Bearer认证和授权

    目录 为什么是 JWT Bearer 什么是 JWT JWT 的优缺点 在 WebAPI 中使用 JWT 认证 刷新 Token 使用授权 简单授权 基于固定角色的授权 基于策略的授权 自定义策略授权 ...

  5. ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程

    ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程 翻译自:地址 在今年年初,我整理了有关将JWT身份验证与ASP.NET Core Web API和Angular一起使用的详 ...

  6. .net core中使用jwt进行认证

    JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息.由于此信息是经过数字签名的,因此可以被验证和信任 ...

  7. ASP.NET Core 2.2 : 二十七. JWT与用户授权(细化到Action)

    上一章分享了如何在ASP.NET Core中应用JWT进行用户认证以及Token的刷新,本章继续进行下一步,用户授权.涉及到的例子也以上一章的为基础.(ASP.NET Core 系列目录) 一.概述 ...

  8. asp.net core 3.0 中使用 swagger

    asp.net core 3.0 中使用 swagger Intro 上次更新了 asp.net core 3.0 简单的记录了一下 swagger 的使用,那个项目的 api 比较简单,都是匿名接口 ...

  9. 避免在ASP.NET Core 3.0中为启动类注入服务

    本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...

随机推荐

  1. Spring Boot jsp页面无法跳转问题

    可能的情况如下: 1.未在pom.xml中添加依赖 <!-- jsp 视图支持--> <dependency>    <groupId>org.apache.tom ...

  2. C++ 中利用 Opencv 得到不规则的ROI 区域(已知不规则区域)

    因为需要,之前写了一个利用mask 得到不规则ROI 区域的程序. 现在需要修改,发现自己都看不懂是怎么做的了.. 所以把它整理下来. 首先利用 鼠标可以得到 你想要的不规则区域的 顶点信息.具体这里 ...

  3. Web表现层

    目录 Web表现层调用过程... 2 延迟... 3 什么是延迟... 3 延迟的构成... 3 最基本的优化思路:... 4 Web表现层性能优化... 4 Web性能的基本指标... 4 Web性 ...

  4. JAVA主流日志梳理

    JAVA主流日志梳理 引入 历史故事 Log4j - JDK1.3及以前 JUL - JDK1.4 JCL - 日志门面commons-logging的出现 SLF4j - 可能是最好的日志框架 lo ...

  5. SQL Server数据库设置自动备份策略

    一. 简单介绍 SQL Server自带的维护计划是一个非常有用的维护工具,能够完成大部分的数据库的维护任务. 数据库的备份也是日常工作中非常重要的一个环节.备份的方法非常的多. 今天给大家介绍最简单 ...

  6. You just run!

    第一篇博客,无关技术,有关身体. 写一篇跑步干货 装备篇 用过的鞋: 光脚,拖鞋,人字拖,回力板鞋,皮鞋,特步,鸿星尔克,李宁超轻13,ASICS  gt2000,阿迪低端. 1,非常推荐攒钱买一双a ...

  7. Openvswitch手册(9): Flow

    这一节我们将flow table flow table主要由ovs-ofctl命令操作 ovs-ofctl可以走和openflow controller一样的协议: ssl:ip[:port]: Th ...

  8. 升讯威微信营销系统开发实践:订阅号和服务号深入分析( 完整开源于 Github)

    GitHub:https://github.com/iccb1013/Sheng.WeixinConstruction因为个人精力时间有限,不会再对现有代码进行更新维护,不过微信接口比较稳定,经测试至 ...

  9. Senparc.Weixin SDK v5.0 升级公告

    经过五年半的持续维护,Senparc.Weixin SDK 逐步丰满和完善,在升级的过程中,我们为基础库(Senparc.Weixin.dll)加入了许多通用的功能,例如加密/解密算法.通用缓存方法等 ...

  10. express使用记录

    express使用记录 文章用啥写?→→ VsCode. 代码用啥写?→→ VsCode. 编辑器下载:VsCode 一.windows下安装node.js环境: 下载地址 相比以前搭过的服务端语言的 ...