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. k8s的基本使用

    一.kubectl的命令参数 1)kubectl 能使用的命令.即查看帮助 [root@k8s6 ~]# kubectl kubectl controls the Kubernetes cluster ...

  2. Numpy与Matplotlib

    一.Numpy numpy支持大量的维度数组和矩阵运算,对数组运算提供了大量的数学函数库! numpy比Python列表更具优势,其中一个优势便是速度.在对大型数组执行操作时,numpy的速度比Pyt ...

  3. CSS基础一

    css作用 css将内容和样式相分离,便于修改样式.HTML 写网页的内容,CSS写内容的样式 CSS构成 p{ /*p为标签,也可以称为选择器,选择包住的内容的格式*/ font-size:12px ...

  4. php获取指定日期,前一天、前一周、前一个月、前一年,后一天,后一周,前一个月,前一年

    dump( date( 'Y-m-d', strtotime('2018-10-1 +1 day') ) ); dump( date( 'Y-m-d', strtotime('2018-10-1 +1 ...

  5. 在JAVA中对于类,对象,继承,多态的看法

    这是我第一次学高级语言.很抱歉选择了JAVA,目标是开发一款可以上线的APP. 类:是建立对象的一个模板,就是系列产品中的基础款图纸.只是图纸而已.不是产品. 对象:是一个可以操作的对象.新建一个对象 ...

  6. windows下Apache配置多个站点

    1. httpd.conf 找到以下两行去掉注释: # Include conf/extra/httpd-vhosts.conf # LoadModule vhost_alias_module mod ...

  7. 多线程.Thread.Sleep方法

    多线程执行中,调用Thread.Sleep()方法 分情况: 1. 单核的情况下 是把当前正在工作的主线程停止(也就是从把线程变成非工作线程). 其他需要工作的线程来争夺CPU这个闲下来的核.谁争夺到 ...

  8. U-Boot Makefile分析(2) config.mk分析

    浏览一下U-Boot各个子目录下的Makefile可以看到,几乎他们都会包含$(TOPDIR)/config.mk,那么这个文件进行了什么操作呢?简单概括:读入include/config.mk.in ...

  9. 【python-appium】Appium的一些坑问题错误解决 与 技巧集锦

    问题 1. error: Failed to start an Appium session, err was: Error: Requested a new session but one was ...

  10. 6 week work 2

    CSS颜色表示法和颜色表(调色板) 1.用颜色名表示 如:white.red.greenyellow.gold等. 2.用十六进制的颜色值表示(红.绿.蓝) #FF0000或者#F00 3.用rgb( ...