Manually implement a JSON webtoken

Time:2020-10-31

Write it at the front

This article will let you knowjwtAndbase64The principle of coding. At the same time, I also simple realizationjwtTo generate, click here.

What is JWT

It’s essentially a signed piece of data in JSON format. Since it is signed, the receiver can verify its authenticity. At the same time, because it is in JSON format, it is also very small.
JSON web token (JWT) is an open standard (RFC 7519), which defines a compact and self-contained way to transfer information between multiple parties in the form of JSON objects. Information can be verified and trusted because it is digitally signed. JWT can use either the key (using HMAC algorithm) or public-private key (using RSA algorithm) for signature.

Why JWT is needed

httpThe protocol is stateless, all for a long time, we usesession/cookieTo customer service, server-side storagesession, the client stores onesessionidWhen accessing, the client takes thesessionidAccording to the correspondingsessionTo determine whether the user has the corresponding permissions and how to display the page; however, with the increase of terminal devices, the more popular development mode is to separate the front and rear end, that is to say, the back-end tends to be service-oriented, providing corresponding operation interface,RESTful APIIt is a relatively mature set of interface specifications at present; andRESTful APIWhat we advocate is no state, and no state can make use of what we have said todayjwtTo achieve.sessionThis stateful way takes up a lot of memory of the server, and when the project is very large, it may need the help ofredisCluster to storesessionutilizejwtYou can put the user status permission to the client, and the server can use thetokenTo determine whether you have access to this resource.

Composition of JWT

jwtIt contains three parts.To separate:

  • Head

  • Load (payload)

  • Signature

Let’s take this step by steptokenFirst, use an array to save the three departments and declare an arrayconst res = [];After getting the three parts, we use theres.join('.')To generate the requiredtokenJust fine.

head

There are usually two parts in the head,tokenThe type and encryption algorithm adopted, such as:

const header = {
    alg: 'HS256',
    typ: 'JWT'
};

And then serialize this and convert it tobase64Coding, it should be noted thatjwtCorresponding tobase64Not in a strict sensebase64Due totokenCould be used asurl, andbase64Medium+/=Three characters will be escaped, causing the URL to become longer, sotokenOfbase64Will+Into-/Into_, delete=
According to this rule, we can generate thebase64Function of:

const getBase64UrlEscape = str => (
  str.replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '')
);

const getBase64Url = data => getBase64UrlEscape(
  new Buffer(JSON.stringify(data)).toString('base64')
);

After the public function is implemented, theheaderIt becomes very simple:

res.push(getBase64Url(header));

load

The payload contains a declaration, which is a description of the user and other metadata. There are three types of declarations:

  • A reservation statement, such asexpsubetc.

  • Public statement

  • Private declaration, private declaration is custom

For the payload, individuals tend to store some unimportant things in it, such as user name, user permission, user ID, etc

const payload = {
    username: 'zp1996',
    id: 1,
    authority: 32
};

The second part is topayloadIntobase64code:

res.push(getBase64Url(payload));

autograph

Signature is to encrypt the encoded header and payload with corresponding key and corresponding encryption algorithm

sha256(
   `${base64UrlEncode(header)}.${base64UrlEncode(payload)}`,
    secret
)

Generally, the supported algorithms are as follows:

const algorithmMap = {
  HS256: 'sha256',
  HS384: 'sha384',
  HS512: 'sha512',
  RS256: 'RSA-SHA256'
};

const typeMap = {
  HS256: 'hmac',
  HS384: 'hmac',
  HS512: 'hmac',
  RS256: 'sign'
};

First of all, let’s learn about several encryption algorithms:

  • Hash encryptioncrypto.createHash()

Any length input can be converted into a fixed length output by hashing, and the output value is a hash value. If two hash values are not the same, then the original input must be different; but the two hash values are the same, it can only be said that the original input is likely to be the same; because the hash function may have “collision”.hashSoon, the problem is fast. Although encryption can’t be reversed, it can be cracked by rainbow tablehashIs very fast, according to the rainbow table to compare with the encrypted characters to be cracked, we will soon get the results we want. Of course, salt is usually added to increase the time cost. Back to what I said todayjwtjwtThe requirements for safety are still very high,jwtThe first two parts of is a processed onebase64If the signature part of our last one is decrypted, thentokenIt can be arbitrarily forged, and the application has no security to speak of. thereforejwtIt is not taken when the signature is generatedHashEncryption.

  • HMAC encryption –crypto.createHmac()

Let’s take a look firsthmacKey related hash operation message authentication code. The message authentication code can ensure that the message is not forged by others, and the message authentication code is with keyhashFunction, becausehmacThere is onekeySo it’s better than thathashBetter security.

  • Sign encryption –crypto.createSign()

In addition to encrypting and decrypting the data, we also need to ensure the integrity and security of the data transmission process. So what we need to do isSignThe algorithm mainly uses asymmetric encryption algorithm, uses private key to sign and public key to verify the integrity of data. The whole process can be seen in the following figure:
Manually implement a JSON webtoken

Private key and public key utilizationopensslHere is an example:

const fs  = require('fs'),
  crypto = require('crypto'),
  data = 'zp1996',
  alg = 'RSA-SHA256';

const signer = (method, key, input) =>
  crypto.createSign(method)
    .update(input)
    .sign(key, 'base64');

const verify = (method, pub, sign, input) =>
  crypto.createVerify(method)
    .update(input)
    .verify(pub, sign, 'base64');

const sign = signer(alg, fs.readFileSync('./private.pem'), data);

console.log(verify(
  alg,
  fs.readFileSync('./public.pem'),
  sign,
  data
));   // true
How to generate signature in JWT

jwtIt mainly useshmacAndsignSo we can write the method of generating signature:

const cryptoMethod = {
  hmac: (method, key, input) => crypto.createHmac(method, key).update(input).digest('base64'),
  sign: (method, input) => crypto.createSign(method).update(input).sign(key, 'base64')
};

At this point, we can write a completesignMethods: 1

const sign = (input, key, method, type) => getBase64UrlEscape(
  cryptoMethod[type](method, key, input)
);

/*
 *Payload load
 *Key key
 *Algorithm encryption algorithm
 *What type HMAC or sign is used for type
 */
jwt.sign = (payload, key, algorithm = 'HS256', options = {}) => {
  const signMethod = algorithmMap[algorithm],
    signType = typeMap[algorithm],
    header = {
      typ: 'JWT',
      alg: algorithm
    },
    res = [];
  options && options.header && Object.assign(header, options.header);
  res.push(getBase64Url(header));
  res.push(getBase64Url(payload));
  res.push(sign(res.join('.'), key, signMethod, signType));
  return res.join('.');
};

At this point, a more qualified one can be generatedtokenThe following is the implementation of verification and parsing:

analysis

It is concluded thatheaderandpayloadTo put it bluntly, in fact, it means thatbase64Convert it to a normal string, and then deserialize the string. The difficulty lies intokenOfbase64It is processed, and it is easy to convert the replacement situation back, and it is easy to do it by using regularization, but for the removed ones=How to make up for it? Why remove this=What about it? Is there anything special about it? I think it’s necessary to have a lookbase64Its principle is as follows

Base64 principle

Base64 character set

base64It is a method of representing binary data based on 64 printable characters, includingA-Z、a-z、0-9、+、/And a complement=Actually, it’s 65 characters. Look at onebase64Index table:

Manually implement a JSON webtoken

Basic principle:

Convert every 3 8-byte to 4-6-byte, and then add 2 high-order 6-byte bits after conversion0, making up four 8 bytes, sobase64Is larger than the original string1/3Let’s take an example of howzpyIntobase64code:

First, confirmasciiCode, respectively122,112,121Since there are only two characters, the binary corresponding to the following character is00000000The 24 bits are as follows:

01111010 | 01110000 | 01111001

The segmentation is divided into six parts

011110 | 100111 | 000001 | 111001

Make up the high position0And then convert it to decimal:

30 | 39 | 1 | 57

In combination with the index table in the above figure, it is easy to get the final value ofenB5From this rule, we can easily conclude thatzpbyenAABut usingnew Buffer('zp').toString('base64')The result isenA=Take a closer lookbase64There are two more rules:

  • In the case of two bytes: according to the above rules, the 16 binary bits of the two bytes are converted into three groups. In addition to two zeros in front of the last group, two zeros are added to the back. In this way, a three bit Base64 code is obtained, and an “=” sign is added at the end.

  • In the case of one byte: convert the 8 binary bits of this byte into two groups according to the above rules. In addition to adding two zeros in front of the last group, four zeros are added after the last group. In this way, a two digit Base64 code is obtained, and two “=” signs are added at the end.

Parsing token

base64How do you give ittokenadd to=It is very clear that there is nothing more than adding one or two at the end.base64The encoding length must be4So you only need to be in the%4Then add=The operation is good. There are only two possible results:

  • 2, add two=

  • 3, add a=

So you can write the following code totokenInto a realbase64:

const getBase64UrlUnescape = str => {
  str += new Array(5 - str.length % 4).join('=');
  return str.replace(/\-/g, '+')
    .replace(/\_/g, '/');
};

At this point, the analytic method can be easily come out:

const decodeBase64Url = str => JSON.parse(
  new Buffer(getBase64UrlUnescape(str), 'base64').toString()
);
jwt.decode = (token) => {
  const segments = token.split('.');
  return {
    header: decodeBase64Url(segments[0]),
    payload: decodeBase64Url(segments[1])
  };
};

verification

Basically, we have understood that verification is very simple, that is, we can use the key to compare to see whether the verification is correct

const verifyMethod = {
  hmac: (input, key, method, signStr) => signStr === sign(input, key, method, 'hmac'),
  sign: (input, key, method, sign) => {
    return crypto.createVerify(method)
      .update(input)
      .verify(key, getBase64UrlUnescape(sign), 'base64');
  }
};

Write it at the end

This article is just my humble opinion. If there is any mistake, please point it out.

Recommended Today

Summary of recent use of gin

Recently, a new project is developed by using gin. Some problems are encountered in the process. To sum up, as a note, I hope it can help you. Cross domain problems Middleware: func Cors() gin.HandlerFunc { return func(c *gin.Context) { //Here you can use * or the domain name you specify c.Header(“Access-Control-Allow-Origin”, “*”) //Allow header […]