NET Core 1.1中使用Jwt
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的更多相关文章
- ASP.Net Core 3.0 中使用JWT认证
JWT认证简单介绍 关于Jwt的介绍网上很多,此处不在赘述,我们主要看看jwt的结构. JWT主要由三部分组成,如下: HEADER.PAYLOAD.SIGNATURE HEADER包 ...
- 翻译一篇英文文章,主要是给自己看的——在ASP.NET Core Web Api中如何刷新token
原文地址 :https://www.blinkingcaret.com/2018/05/30/refresh-tokens-in-asp-net-core-web-api/ 先申明,本人英语太菜,每次 ...
- 如何简单的在 ASP.NET Core 中集成 JWT 认证?
前情提要:ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统 文章超长预警(1万字以上),不想看全部实现过程的同学可以直接跳转到末尾查看成果或者一键安装相关的 nuget 包 自上一 ...
- ASP.NET Core WebAPI中使用JWT Bearer认证和授权
目录 为什么是 JWT Bearer 什么是 JWT JWT 的优缺点 在 WebAPI 中使用 JWT 认证 刷新 Token 使用授权 简单授权 基于固定角色的授权 基于策略的授权 自定义策略授权 ...
- ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程
ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程 翻译自:地址 在今年年初,我整理了有关将JWT身份验证与ASP.NET Core Web API和Angular一起使用的详 ...
- .net core中使用jwt进行认证
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息.由于此信息是经过数字签名的,因此可以被验证和信任 ...
- ASP.NET Core 2.2 : 二十七. JWT与用户授权(细化到Action)
上一章分享了如何在ASP.NET Core中应用JWT进行用户认证以及Token的刷新,本章继续进行下一步,用户授权.涉及到的例子也以上一章的为基础.(ASP.NET Core 系列目录) 一.概述 ...
- asp.net core 3.0 中使用 swagger
asp.net core 3.0 中使用 swagger Intro 上次更新了 asp.net core 3.0 简单的记录了一下 swagger 的使用,那个项目的 api 比较简单,都是匿名接口 ...
- 避免在ASP.NET Core 3.0中为启动类注入服务
本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...
随机推荐
- chrome gps位置模拟设置
chrome gps位置模拟设置 调试公众号页面定位,Edge 虽好实现方便,介于界面实在不符合我的调试习惯 遂上度娘寻觅chrome模拟GPS方法 找了好几个帖子,发现新版本已经不再试用.不得感叹 ...
- pl/sql调试存储过程
1.找到对应的存储过程 2.在存储过程名称上右键,选择Test 3.点击1标识的按钮(begin debugger),选择2开始调试 4.存储过程如需参数,需要在右侧下方的表格区域(3)填入对应的值即 ...
- linux的常用指令和配置文件
一. 常用的指令 mkdir -p 创建文件夹 parents递归创建 ls -alh 查看当前目录内容 cd 切换工作目录 pwd 打印当前工作目录 touch 文件名 创建文件 echo 字符 ...
- 01 C语言程序设计--01 C语言基础--第3章 基本数据类型01
01.1.3.1序言 00:02:17 01.1.3.2 C语言中的基本元素和常量的概念 00:08:54 01.1.3.3示例--常量 00:12:08 01.1.3.4变量的概念和命名规则 00: ...
- maple推导剑桥模型塑性势函数
with(DEtools); Parameter(M); de := diff(q(p), p)+(M^*p^-q(p)^)/(*p*q(p)) = ; dsolve({de, q(px) = }, ...
- golang使用 gzip压缩
golang使用 gzip压缩 这个例子中使用gzip压缩格式,标准库还支持zlib, bz2, flate, lzw 压缩处理_三步: 1.创建压缩文件2.gzip write包装3.写入数据 ou ...
- eclipse启动时要求高版本jdk的问题
在eclipse.ini文件首行添加 -vm C:\Program Files\Java\jdk1.8\jdk1.8.0_131\bin https://blog.csdn.net/wanlin77/ ...
- ps修改图片文字
原图(机密内容以打马赛克): 需要修改的地方: 1.去除蓝色的线条, 2.改表格的字体 操作: 1.去线条 放大图片,使用魔棒工具选中蓝色点,调节容差,取消连续,然后填充白色,ctrl+delete ...
- dell T130服务器加内存
需求:客户一台dell T130塔式服务器,由于本机只有一条8G内存,系统运行比较慢,需要再增加一条8G内存. 增加过程:第一次增加时由于没有注意机器上内存频率是2133的,所以新增加的一条2400频 ...
- qrcode.php
<!doctype html><html><head> <meta charset="UTF-8"/> <meta name= ...