[.NET Core project combat - unified authentication platform] Chapter XII authorization papers - depth understanding of JWT generation and validation process

[.NET Core project combat - unified authentication platform] opening and directory

Based on article describes the Ids4password authorization mode, from the use of scenarios, principle analysis, custom system integration complete account describes the contents of password authorization mode, and finally gives the three thinking herein, this would first think for at issue detailed explanation Ids4of how access_token is generated, and how to verify the validity of access_token, and finally we use .net webapi to implement an external interface (had wanted to use JAVA to achieve, nonetheless did not learn, when you initiate it, there would JAVA according to friends using JAVA case I wrote to implement a case).

.netcore project combat exchange group (637 326 624), interested friends can exchange discussed in the group.

A, JWT Profile

  1. What is JWT?
    JSON Web Token (JWT) is an open standard (RFC 7519), which defines a compact, self-contained manner, for as JSON object securely transfer information between the parties. This information can be verified and trusted, since it was digitally signed.

  2. When to use JWT?

1) certification, which is more common usage scenarios, as long as the user login system once, after which the request will include a signature out of the token, by token can also be used to implement a single sign-on.

2), to exchange information by using a key to securely transmit information, can know who the sender is placed whether a message has been tampered with.

  1. JWT structure is what?

JSON Web Token consists of three parts, connected by a dot (.) Between them. These three parts are:

  • Header
  • Payload
  • Signature

Header
header typically consists of two parts: token type ( "JWT") algorithm and a name (such as: HMAC SHA256 or RSA, etc.).

E.g:

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

Then, Base64-encoded JSON get this first portion of JWT

Payload

The second part is the JWT payload, which contains declaration (required). Statement is a statement about an entity (usually the user) and other data. Disclaimer There are three types: registered, public and private.

  • Registered claims: There is a predefined set of statements, they are not mandatory, but recommended. For example: iss (issuer), exp (expiration time), sub (subject), aud (audience) and the like.
  • Public claims: can be defined freely.
  • Private claims: for consent to use or disclose statements to share information between their parties and are not registered.

Below is an example:

{
 "nbf": 1545919058,
 "exp": 1545922658,
 "iss": "http://localhost:7777", "aud": [ "http://localhost:7777/resources", "mpc_gateway" ], "client_id": "clienta", "sub": "1", "auth_time": 1545919058, "idp": "local", "nickname": "金焰的世界", "email": "[email protected]", "mobile": "13888888888", "scope": [ "mpc_gateway", "offline_access" ], "amr": [ "pwd" ] }

Base64 encoding of the payload for the second portion to obtain the JWT

Be careful not to place sensitive information in the header or payload of JWT, unless they are encrypted.

Signature

为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的                那个,然对它们签名即可。

E.g:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

The signature is used to verify that a message has not been altered during delivery, and, for the token using the private key signatures, it can verify that the sender JWT for its alleged sender.

Two, IdentityServer4 is how to generate jwt of?

In the understanding of JWTthe basic concepts, we want to know JWTis how to generate and what encryption is the way how we use your own key for encryption.

IdentityServer4 encryption method?

Ids4Currently it is using RS256asymmetric manner, using a private signature, and then test the client to sign the public key. Some people may ask, we generated Ids4when there is no configuration certificate, why can also run up? Here we must explain the use of the certificate, and Ids4use certificate encryption process.

1, loading certificate

Ids4Default temporary certificate for tokenthe production, use the code  .AddDeveloperSigningCredential()here to automatically generate tempkey.rsathe certificate file, so if the project root directory using the default configuration can view this file, codes are as follows:

public static IIdentityServerBuilder AddDeveloperSigningCredential(this IIdentityServerBuilder builder, bool persistKey = true, string filename = null) { if (filename == null) { filename = Path.Combine(Directory.GetCurrentDirectory(), "tempkey.rsa"); } if (File.Exists(filename)) { var keyFile = File.ReadAllText(filename); var tempKey = JsonConvert.DeserializeObject<TemporaryRsaKey>(keyFile, new JsonSerializerSettings { ContractResolver = new RsaKeyContractResolver() }); return builder.AddSigningCredential(CreateRsaSecurityKey(tempKey.Parameters, tempKey.KeyId)); } else { var key = CreateRsaSecurityKey(); RSAParameters parameters; if (key.Rsa != null) parameters = key.Rsa.ExportParameters(includePrivateParameters: true); else parameters = key.Parameters; var tempKey = new TemporaryRsaKey { Parameters = parameters, KeyId = key.KeyId }; if (persistKey) { File.WriteAllText(filename, JsonConvert.SerializeObject(tempKey, new JsonSerializerSettings { ContractResolver = new RsaKeyContractResolver() })); } return builder.AddSigningCredential(key); } }

This also can understand why the same can not configure the certificate to use.

Note: In a production environment, we'd use to configure their own credentials.

If we already have the certificate, you can use the following code to achieve, as to how certificates are generated, a lot of online information, not presented here.

 .AddSigningCredential(new X509Certificate2(Path.Combine(basePath,"test.pfx"),"123456"));

Then injecting the certificate information, as follows:

builder.Services.AddSingleton<ISigningCredentialStore>(new DefaultSigningCredentialsStore(credential));
            builder.Services.AddSingleton<IValidationKeysStore>(new DefaultValidationKeysStore(new[] { credential.Key }));

You can use the back of the certificate-related operations in the project, such as encryption, such as inspection sign.

2, the use of encryption certificate

Part I describes the password licensing model, a detailed explanation of the process, when all the information by checking, Claimafter the build is complete, they begin to generate tokenthe core code is as follows.

public virtual async Task<string> CreateTokenAsync(Token token) { var header = await CreateHeaderAsync(token); var payload = await CreatePayloadAsync(token); return await CreateJwtAsync(new JwtSecurityToken(header, payload)); } //使用配置的证书生成JWT头部 protected virtual async Task<JwtHeader> CreateHeaderAsync(Token token) { var credential = await Keys.GetSigningCredentialsAsync(); if (credential == null) { throw new InvalidOperationException("No signing credential is configured. Can't create JWT token"); } var header = new JwtHeader(credential); // emit x5t claim for backwards compatibility with v4 of MS JWT library if (credential.Key is X509SecurityKey x509key) { var cert = x509key.Certificate; if (Clock.UtcNow.UtcDateTime > cert.NotAfter) {//如果证书过期提示 Logger.LogWarning("Certificate {subjectName} has expired on {expiration}", cert.Subject, cert.NotAfter.ToString(CultureInfo.InvariantCulture)); } header["x5t"] = Base64Url.Encode(cert.GetCertHash()); } return header; } //生成内容 public static JwtPayload CreateJwtPayload(this Token token, ISystemClock clock, ILogger logger) { var payload = new JwtPayload( token.Issuer, null, null, clock.UtcNow.UtcDateTime, clock.UtcNow.UtcDateTime.AddSeconds(token.Lifetime)); foreach (var aud in token.Audiences) { payload.AddClaim(new Claim(JwtClaimTypes.Audience, aud)); } var amrClaims = token.Claims.Where(x => x.Type == JwtClaimTypes.AuthenticationMethod); var scopeClaims = token.Claims.Where(x => x.Type == JwtClaimTypes.Scope); var jsonClaims = token.Claims.Where(x => x.ValueType == IdentityServerConstants.ClaimValueTypes.Json); var normalClaims = token.Claims .Except(amrClaims) .Except(jsonClaims) .Except(scopeClaims); payload.AddClaims(normalClaims); // scope claims if (!scopeClaims.IsNullOrEmpty()) { var scopeValues = scopeClaims.Select(x => x.Value).ToArray(); payload.Add(JwtClaimTypes.Scope, scopeValues); } // amr claims if (!amrClaims.IsNullOrEmpty()) { var amrValues = amrClaims.Select(x => x.Value).Distinct().ToArray(); payload.Add(JwtClaimTypes.AuthenticationMethod, amrValues); } // deal with json types // calling ToArray() to trigger JSON parsing once and so later // collection identity comparisons work for the anonymous type try { var jsonTokens = jsonClaims.Select(x => new { x.Type, JsonValue = JRaw.Parse(x.Value) }).ToArray(); var jsonObjects = jsonTokens.Where(x => x.JsonValue.Type == JTokenType.Object).ToArray(); var jsonObjectGroups = jsonObjects.GroupBy(x => x.Type).ToArray(); foreach (var group in jsonObjectGroups) { if (payload.ContainsKey(group.Key)) { throw new Exception(string.Format("Can't add two claims where one is a JSON object and the other is not a JSON object ({0})", group.Key)); } if (group.Skip(1).Any()) { // add as array payload.Add(group.Key, group.Select(x => x.JsonValue).ToArray()); } else { // add just one payload.Add(group.Key, group.First().JsonValue); } } var jsonArrays = jsonTokens.Where(x => x.JsonValue.Type == JTokenType.Array).ToArray(); var jsonArrayGroups = jsonArrays.GroupBy(x => x.Type).ToArray(); foreach (var group in jsonArrayGroups) { if (payload.ContainsKey(group.Key)) { throw new Exception(string.Format("Can't add two claims where one is a JSON array and the other is not a JSON array ({0})", group.Key)); } var newArr = new List<JToken>(); foreach (var arrays in group) { var arr = (JArray)arrays.JsonValue; newArr.AddRange(arr); } // add just one array for the group/key/claim type payload.Add(group.Key, newArr.ToArray()); } var unsupportedJsonTokens = jsonTokens.Except(jsonObjects).Except(jsonArrays); var unsupportedJsonClaimTypes = unsupportedJsonTokens.Select(x => x.Type).Distinct(); if (unsupportedJsonClaimTypes.Any()) { throw new Exception(string.Format("Unsupported JSON type for claim types: {0}", unsupportedJsonClaimTypes.Aggregate((x, y) => x + ", " + y))); } return payload; } catch (Exception ex) { logger.LogCritical(ex, "Error creating a JSON valued claim"); throw; } } //生成最终的Token protected virtual Task<string> CreateJwtAsync(JwtSecurityToken jwt) { var handler = new JwtSecurityTokenHandler(); return Task.FromResult(handler.WriteToken(jwt)); }

Know these principles, we will be able to know exactly access_tokenhave put those things, and how we can verify generated Token.

Third, how to verify the validity of access_token?

Know how to produce, the main purpose is to direct our server interfaces is how to protect the safety of why the server is added under the code as long as the allocation of resources to protect it?

services.AddAuthentication("Bearer")
        .AddIdentityServerAuthentication(options =>
            {
               options.Authority ="http://localhost:7777";
               options.RequireHttpsMetadata = false; options.ApiName = "Api1"; options.SaveToken = true; }); //启用授权 app.UseAuthentication();

In understanding this before, we need to understand the system do the verification process, where the use of a map can be a good understanding of the process.

img
After reading is not suddenly see the light? Here you can well understand /.well-known/openid-configuration/jwksthe original certificate is public key information, is accessed via /.well-known/openid-configurationexposure to all clients to use, safe sex is used to ensure that the principle of asymmetric encryption, private key encryption, the public key can only be verified, so there is no key leakage problem.

Although only a few short code, they have done so much things, indicating Ids4 good package, we reduced the number coding. This is someone will ask, if our project is not .netcore, then how access to the gateway it?

Internet has a Python example, using Identity Server 4 (JWKS endpoints and RS256 algorithm) to protect Web API Python .

I originally planned to use Java to achieve, I have not touched have forgotten how to write, leaving friends will java to achieve it, the principle is the same.

Now I had webapian example to develop server-side interface, and then use Ids4 to protect the interface content.

新建一个webapi项目,项目名称Czar.AuthPlatform.WebApi,为了让输出的结果为json,我们需要在WebApiConfig增加config.Formatters.Remove(config.Formatters.XmlFormatter);代码,然后修改默认的控制器ValuesController,修改代码如下。

[Ids4Auth("http://localhost:6611", "mpc_gateway")]
public IEnumerable<string> Get() { var Context = RequestContext.Principal; return new string[] { "WebApi Values" }; }

为了保护api安全,我们需要增加一个身份验证过滤器,实现代码如下。

using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Http.Controllers; using System.Web.Http.Filters; namespace Czar.AuthPlatform.WebApi { public class Ids4AuthAttribute : AuthorizationFilterAttribute { /// <summary> /// 认证服务器地址 /// </summary> private string issUrl = ""; /// <summary> /// 保护的API名称 /// </summary> private string apiName = ""; public Ids4AuthAttribute(string IssUrl,string ApiName) { issUrl = IssUrl; apiName = ApiName; } /// <summary> /// 重写验证方式 /// </summary> /// <param name="actionContext"></param> public override void OnAuthorization(HttpActionContext actionContext) { try { var access_token = actionContext.Request.Headers.Authorization?.Parameter; //获取请求的access_token if (String.IsNullOrEmpty(access_token)) {//401 actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized); actionContext.Response.Content = new StringContent("{\"errcode\":401,\"errmsg\":\"未授权\"}"); } else {//开始验证请求的Token是否合法 //1、获取公钥 var httpclient = new HttpClient(); var jwtKey= httpclient.GetStringAsync(issUrl + "/.well-known/openid-configuration/jwks").Result; //可以在此处缓存jwtkey,不用每次都获取。 var Ids4keys = JsonConvert.DeserializeObject<Ids4Keys>(jwtKey); var jwk = Ids4keys.keys; var parameters = new TokenValidationParameters { //可以增加自定义的验证项目 ValidIssuer = issUrl, IssuerSigningKeys = jwk , ValidateLifetime = true, ValidAudience = apiName }; var handler = new JwtSecurityTokenHandler(); //2、使用公钥校验是否合法,如果验证失败会抛出异常 var id = handler.ValidateToken(access_token, parameters, out var _); //请求的内容保存 actionContext.RequestContext.Principal = id; } } catch(Exception ex) { actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized); actionContext.Response.Content = new StringContent("{\"errcode\":401,\"errmsg\":\"未授权\"}"); } } } public class Ids4Keys { public JsonWebKey[] keys { get; set; } } }

代码非常简洁,就实现了基于Ids4的访问控制,现在我们开始使用PostMan来测试接口地址。

我们直接请求接口地址,返回401未授权。

然后我使用Ids4生成的access_token再次测试,可以得到我们预期结果。

为了验证是不是任何地方签发的token都可以通过验证,我使用其他项目生成的access_token来测试,发现提示的401未授权,可以达到我们预期结果。

现在就可以开心的使用我们熟悉的webapi开发我们的接口了,需要验证的地方增加类似[Ids4Auth("http://localhost:6611", "mpc_gateway")]代码即可。

使用其他语言实现的原理基本一致,就是公钥来验签,只要通过验证证明是允许访问的请求,由于公钥一直不变(除非认证服务器更新了证书),所以我们请求到后可以缓存到本地,这样验签时可以省去每次都获取公钥这步操作。

四、总结

本篇我们介绍了JWT的基本原理和Ids4JWT实现方式,然后使用.NET webapi实现了使用Ids4保护接口,其他语言实现方式一样,这样我们就可以把网关部署后,后端服务使用任何语言开发,然后接入到网关即可。

With this knowledge, the feeling is not to Ids4understand the deeper it? JWTReally convenient, but we want some special scenes Tokenby way of manual configuration failure immediately within the validity period, according to the existing Ids4authentication is no way to do it, then how to achieve it? I'll be the next one to describe how to implement mandatory tokenfailure, please look forward to it.

Guess you like

Origin www.cnblogs.com/lhxsoft/p/11945319.html