I. Introduction
1. background
There are many problems in traditional Session based verification, such as Session expiration, excessive server overhead, inability to deploy in a distributed way, and unsuitable for projects with front-end and back-end separation. Traditional token based verification needs to store key value information, which has disadvantages in Session or database. If symmetric encryption algorithm is used to generate token according to certain rules, although the above problems can be solved, once the symmetric encryption algorithm is leaked, it is very easy to decompile. Therefore, on this basis, continue to upgrade, and use userId to generate token, as long as the secret key is well preserved, from And lead to JWT.
2. What is JWT
Json web token (JWT) is an open standard based on JSON, which is used to transfer statements between network application environments. The token is designed to be compact and secure, which is especially suitable for single sign on (SSO) scenarios of distributed sites. JWT declaration is generally used to transfer the authenticated user identity information between identity provider and service provider, so as to obtain resources from resource server. It can also add some additional declaration information necessary for other business logic. The token can also be directly used for authentication or encryption.
Here is a JWT string (to be analyzed in detail later)
1 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qjw1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U
3. Advantages of JWT
(1) JWT is stateless, does not need to save session information on the server side, reduces the reading pressure on the server side (stored on the client side), and is easy to expand and distributed deployment.
(2). JWT can be supported across languages.
(3) . it's easy to transmit. jwt is very simple in composition and takes up less space in bytes, so it's very easy to transmit.
(4) . it has a payload part, which can store the non sensitive information related to business logic.
Special statement: the biggest advantage of JWT is stateless. Compared with the traditional Session verification, it can reduce the storage pressure on the server side, with higher security, but it is not absolute. For example, for the same interface, after the JWT string is intercepted, and within the validity period, it can also simulate the request to access without tampering with the JWT string. (learn more about the core of JWT with the following contents)
2, Depth analysis of JWT
1. Appearance of JWT
The following string is the display format after JWT encryption. Let's take a closer look. In the middle, the string is divided into three parts by two "points".
eyJ0eXAxIjoiMTIzNCIsImFsZzIiOiJhZG1pbiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiIsImV4cCI6MTU1MjI4Njc0Ni44Nzc0MDE4fQ.pEgdmFAy73walFonEm2zbxg46Oth3dlT02HR9iVzXa8
How does the above long string come from? We need to understand the composition principle of JWT.
2. Composition of JWT
JWT consists of three parts, as shown in the following figure: Header header, Payload, Signature.
(1) . Header
It usually consists of two parts: type (such as "typ": "JWT") and encryption algorithm (such as "alg": "HS256"). Of course, you can also add some other customized parameters, and then code the object model base64 to generate a string,
For example, "eyj0exaxijoimtizcisimfsziioijhzg1pbiisinr5cci6ikpvciszyii6ikhtmju2in0", we can recode it to see the true face of Lushan Mountain.
Note: Base64 is a kind of encoding, that is, it can be translated back to its original appearance. It is not an encryption process.
(2) . load (Payload)
It is usually used to store some information that is needed but not sensitive for business, such as user ID, user account, permission, etc. there are also some default statements in this part, as shown in the following figure, many of which are not commonly used.
- iss: jwt issuer
- Sub: users JWT is targeting
- aud: the party receiving jwt
- Exp: the expiration time of JWT, which must be greater than the issuing time
- nbf: defines when the jwt will not be available
- IAT: issuing time of JWT
- JTI: the unique identity of JWT, which is mainly used as a one-time token to avoid replay attack.
The most commonly used is the expiration time of exp, which should be compared with the point on January 1, 1970. The usage is as follows. The following indicates that the expiration time is 20 minutes after the jwt string is generated.
Finally, base64 code the object assembled by this part, such as "eyjvc2vyswioijyymywivxnlck5hbwuioijhzg1pbiisimv4cci6mtu1mj4njc0ni44nzc0mde4fq". We can recode it to see the true face of Lushan, as shown in the following figure:
Note: this part can also be decoded, so do not store sensitive information.
(3) . signature
This part requires that base64 encrypted header and base64 encrypted payload use the string composed of. Connection, and then use the declared encryption method in header to add salt secret combination encryption, and then constitute the third part of jwt.
The pseudo code is as follows:
1 var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); 2 var signature = HMACSHA256(encodedString, 'sercret secret key')
Note: the key exists on the server side. Do not disclose it. It cannot be decrypted without knowing the key. The signing generation of jwt is also on the server side. Secret is used to sign jwt and verify jwt. Therefore, it is your server's private key, which should not be disclosed in any scenario. Once the client knows the secret, it means that the client can sign the jwt itself.
Special note: even if the information in the payload is tampered with, the server can judge that it is an illegal request through signature, that is, the verification cannot pass.
3. Code taste
The JWT package needs to be installed through Nuget. The new version of JWT is recommended from. Net version 4.6.
1 [HttpGet] 2 public string JiaM() 3 { 4 //Set expiration time (it can not be set, which means 20 minutes after signing) 5 double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds; 6 var payload = new Dictionary<string, object> 7 { 8 { "UserId", 123 }, 9 { "UserName", "admin" }, 10 {"exp",exp } //This parameter can also be left blank 11 }; 12 var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";//Do not leak. 13 IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); 14 15 //Note that this is an extra parameter. The default parameters are typ and alg 16 var headers = new Dictionary<string, object> 17 { 18 { "typ1", "1234" }, 19 { "alg2", "admin" } 20 }; 21 22 IJsonSerializer serializer = new JsonNetSerializer(); 23 IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); 24 IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); 25 var token = encoder.Encode(headers, payload, secret); 26 return token; 27 } 28 29 [HttpGet] 30 public string JieM(string token) 31 { 32 var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; 33 try 34 { 35 IJsonSerializer serializer = new JsonNetSerializer(); 36 IDateTimeProvider provider = new UtcDateTimeProvider(); 37 IJwtValidator validator = new JwtValidator(serializer, provider); 38 IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); 39 IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder); 40 var json = decoder.Decode(token, secret, true); 41 return json; 42 } 43 catch (TokenExpiredException) 44 { 45 //It's out of date. It's automatically here 46 return "Token has expired"; 47 } 48 catch (SignatureVerificationException) 49 { 50 //Check failed to enter here automatically 51 return "Token has invalid signature"; 52 } 53 catch (Exception) 54 { 55 //Other errors, enter here automatically 56 return "other error"; 57 }
The above code is convenient for quick testing through PostMan. Note that three catch es in the decryption method, the token expires, will automatically enter the TokenExpiredException exception. If the token verification fails, it will automatically enter the signaturereferenceexception.
3, Use process of JWT
The overall process is as follows:
1. The client (front end or App end) transmits the user name and password to the login interface through an Http request. It is recommended to adopt the mode of Https to avoid information being sniffed.
2. After the server verifies the login interface to verify the user name and password, put some information needed by the business logic, such as userId and userAccount, into the Payload, and then generate a JWT string in the form of xxx.yyy.zzz to return to the client.
3. The client gets the string of JWT, which can be stored in LocalStorage. Note to delete the value when logging out.
4. The login is successful. The jwt string is carried in the header every time other interfaces are requested. It is recommended to put it in the Authorization bit of HTTP Header. (solve the XSS and XSRF problems) or name it by yourself, such as "auth", to pass the string.
5. The server side needs to write a filter in which to verify the validity of jwt (whether the signature is correct or expired). If the verification passes the business logic of the interface, if the verification fails, it will be returned to the client.
Two problems need to be solved here?
(1) . in the filter of WebApi, if the verification passes, how to pass the decrypted value to action. (it's a bit of a hole after two decryptions)
(2) . in the filter of WebApi, if the verification fails, how to return it to the client, and then how to accept it for the client.
(announced in actual combat).
4, Project practice
1, Overall objective:
The whole verification logic of JWT is simulated by a login interface and an information acquisition interface.
2, Detailed steps
1. Encapsulate the method of JWT encryption and decryption.
The assembly of JWT needs to be installed through Nuget. The latest version of JWT is recommended to use. Net 4.6.
JWTHelp
2. Analog login interface
In the login interface, simulate the database verification, that is, the account and password are admin and 12345, that is, the verification is passed, and then the account and userId (actually should be checked in the database). Here, you can also set the expiration time, such as 20 minutes, stored in PayLoad together, and then generate the JWT string, and return it to the client.
/// <summary> ///Simulated landing /// </summary> /// <param name="userAccount"></param> /// <param name="pwd"></param> /// <returns></returns> [HttpGet] public string Login1(string userAccount, string pwd) { try { //The data operation is simulated here. As long as it is admin and 123456, it will pass the verification if (userAccount == "admin" && pwd == "123456") { //1. Carry out business processing (get userId here) string userId = "0806"; //Expiration time (can not be set, the following indicates 20 minutes after signing) double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds; //Assemble var payload = new Dictionary<string, object> { {"userId", userId }, {"userAccount", userAccount }, {"exp",exp } }; //2. Sign JWT var token = JWTHelp.JWTJiaM(payload); var result = new { result = "ok", token = token }; return JsonConvert.SerializeObject(result); } else { var result = new { result = "error", token = "" }; return JsonConvert.SerializeObject(result); } } catch (Exception) { var result = new { result = "error", token = "" }; return JsonConvert.SerializeObject(result); } }
3. Client calls login interface
Here is just a get request for testing. In the actual project, post request is recommended, and Https is configured. After the request is successful, jwt string is stored in localStorage.
1 //1. login 2 $('#j_jwtLogin').on('click', function () { 3 $.get("/api/Seventh/Login1", { userAccount: "admin", pwd: "123456" }, function (data) { 4 var jsonData = JSON.parse(data); 5 if (jsonData.result == "ok") { 6 console.log(jsonData.token); 7 //Store in local cache 8 window.localStorage.setItem("token", jsonData.token); 9 alert("Login successfully,ticket=" + jsonData.token); 10 } else { 11 alert("Login failed"); 12 } 13 }); 14 });
Operation result:
4. Server filter
The code shares two ways to obtain information in the header. After "auth" is obtained, it is verified. If the verification fails, it is returned to the client through status code 401. If the verification passes, actionContext.RequestContext.RouteData.Values.Add("auth", result) is used. The decryption value is stored to facilitate the direct acquisition of subsequent actions.
1 /// <summary> 2 ///Verify the filter of JWT algorithm 3 /// </summary> 4 public class JWTCheck : AuthorizeAttribute 5 { 6 public override void OnAuthorization(HttpActionContext actionContext) 7 { 8 //Several ways to get the median value of Header 9 //Method 1: 10 //{ 11 // var authHeader2 = from t in actionContext.Request.Headers 12 // where t.Key == "auth" 13 // select t.Value.FirstOrDefault(); 14 // var token2 = authHeader2.FirstOrDefault(); 15 //} 16 17 //Mode two: 18 IEnumerable<string> auths; 19 if (!actionContext.Request.Headers.TryGetValues("auth", out auths)) 20 { 21 //HttpContext.Current.Response.Write("auth in message header is empty"); 22 //Return status code verification failed, and return the reason (the front end captures 401 status code). Note: this sentence can't stage the filter, and it will continue to go down, with the help of if else 23 actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("In the header auth Empty")); 24 } 25 else 26 { 27 var token = auths.FirstOrDefault(); 28 if (token != null) 29 { 30 if (!string.IsNullOrEmpty(token)) 31 { 32 var result = JWTHelp.JWTJieM(token); 33 if (result == "expired") 34 { 35 //Return status code verification failed, and return reason (front end captures 401 status code) 36 actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("expired")); 37 } 38 else if (result == "invalid") 39 { 40 //Return status code verification failed, and return reason (front end captures 401 status code) 41 actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("invalid")); 42 } 43 else if (result == "error") 44 { 45 //Return status code verification failed, and return reason (front end captures 401 status code) 46 actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("error")); 47 } 48 else 49 { 50 //Indicates that the verification is passed, which is used to transfer the value to the controller 51 actionContext.RequestContext.RouteData.Values.Add("auth", result); 52 } 53 } 54 } 55 else 56 { 57 //Return status code verification failed, and return reason (front end captures 401 status code) 58 actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("token empty")); 59 } 60 } 61 62 } 63 }
5. How to get information from the server
The above filter is used in the method in the form of features, and then the decrypted value is obtained through RequestContext.RouteData.Values["auth"], and then other business processing is carried out.
1 /// <summary> 2 ///Access information after encryption 3 /// </summary> 4 /// <returns></returns> 5 [JWTCheck] 6 [HttpGet] 7 public string GetInfor() 8 { 9 var userData = JsonConvert.DeserializeObject<userData>(RequestContext.RouteData.Values["auth"].ToString()); ; 10 if (userData == null) 11 { 12 var result = new { Message = "error", data = "" }; 13 return JsonConvert.SerializeObject(result); 14 } 15 else 16 { 17 var data = new { userId = userData.userId, userAccount = userData.userAccount }; 18 var result = new { Message = "ok", data =data }; 19 return JsonConvert.SerializeObject(result); 20 } 21 }
6. The client calls the method of obtaining information
The front-end obtains the token value in localStorage, and uses the method of "auth" to transfer and call the method of server-side through the way of user-defined header. When the server's verification token is not correct, it is returned in the form of status code, so the error method is used here, and it is judged by xhr.status==401. When entering this 401, it is the token verification that fails, The specific reason can be determined by obtaining detailed values through xhr.responseText.
1 //2. Access to information 2 $('#j_jwtGetInfor').on('click', function () { 3 //Read token value from local cache 4 var token = window.localStorage.getItem("token"); 5 $.ajax({ 6 url: "/api/Seventh/GetInfor", 7 type: "Get", 8 data: {}, 9 datatype: "json", 10 //How to set header 1 11 headers: { "auth": token}, 12 //How to set the header 2 13 //beforeSend: function (xhr) { 14 // xhr.setRequestHeader("auth", token) 15 //}, 16 success: function (data) { 17 console.log(data); 18 var jsonData = JSON.parse(data); 19 if (jsonData.Message == "ok") { 20 var myData = jsonData.data; 21 console.log("Get success"); 22 console.log(myData.userId); 23 console.log(myData.userAccount); 24 } else { 25 console.log("Acquisition failure"); 26 } 27 }, 28 //Enter here when the safety verification fails 29 error: function (xhr) { 30 if (xhr.status == 401) { 31 console.log(xhr.responseText); 32 var jsonData = JSON.parse(xhr.responseText); 33 console.log("privilege grant failed,The reasons are:" + jsonData.Message); 34 } 35 } 36 }); 37 });
Operation result:
Others, such as the expiration of token, can be tested just by changing the computer time. If the token is not correct, the obtained jwt string can be tested, which will not be done here.