注意 意思 scrip ons date als dpa configure
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 = 86400;
- 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()
- {
- varjwtFactory =new JwtFactory();
- varclaims =newList()
- {
- newClaim("role","myRole"),
- newClaim("org","myOrg")
- };
- varjti = Guid.NewGuid().ToString("N");
- varjwtString = jwtFactory.CreateEncodedJwt(new ClaimsIdentity(claims), jti);
- varjwt = jwtFactory.CreateJwt(jwtFactory.CreateSecurityTokenDescriptor(new ClaimsIdentity(claims), jti));
- varclaimsPrincipal = jwtFactory.ValidateJwtToken(jwtString,out var token);
- varjwtClaims = 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
来源: http://www.bubuko.com/infodetail-2138080.html