1. The background
HTTP is a stateless protocol, which means that after a user provides an account and password for login authentication, the next request still needs authentication, because the server does not know who sent the request and does not know that the user has been authenticated once. Therefore, in order to solve this problem and maintain the session state between the client and the server, each user needs to be allocated a storage space in the server cache to store the user’s personal login information, and each storage space has a unique ID as its ID card. In response, so the ID is returned to the browser, the browser stored locally, so that subsequent requests again can carry the ID, the server can according to the ID to the corresponding buffer space to find whether there is a corresponding storage area, to find, said before the user has visited, storage area to store the login information, etc. Also can be used directly, You don’t have to log in again.
This is the traditional session persistence technology based on session+cookie. A session is a cache that exists on the server. It is equivalent to a map data structure that stores data. Each session corresponds to its own SESSION ID. A cookie, on the other hand, is a data store that exists in the client browser.
The process described above is that the browser allocates session space and generates the sessionID when accessing the server for the first time, and stores the sessionID in the set-cookie attribute of the response header. The browser specifically processes the Cookie data and stores it. The next time the browser initiates a request, it carries the previously stored Cookie data, called the sessionID, in the Cookie field of the request header. After receiving the request, the server obtains the sessionID in the Cookie in the request header so that it can subsequently locate and manipulate data stores that are specific to that user.
Therefore, the common login authentication process is to put the basic information of the user into the session storage area after the user logs in. When the user requests again, the user does not need to log in again. The server will directly get your ID and other data from Seesion, and then query and process the data in the database according to the specific business logic.
However, this session-based Session persistence technology has many disadvantages, such as:
- As the number of users increases, the cost of the server will obviously increase;
- In the context of distributed applications, the capacity of the load balancer is correspondingly limited;
- Cookies are stored on the client. If intercepted and stolen, they are vulnerable to CSRF cross-domain forged request attacks.
- Cookie support on devices other than browsers is not friendly and cross-platform support is not good;
Summary of 2.
The emergence of JWT is to solve the problems of traditional Session+Cookie technology. In fact, with the development of front-end separation and the establishment of data center, more and more companies will create a central server to serve a variety of product lines, such as: unified identity authentication platform; Products in these product lines may have a variety of end-devices, including but not limited to browsers, desktop apps, mobile apps, tablet apps, and even smart homes.JWT full nameJson Web Token
, translated into JSON format network token, often referred to asToken
To solve the problem of providing a uniform and secure token format for a variety of terminal devices. Therefore, JWT is just a token format, == you can store it in a Cookie, or you can store it in localstorage==, no restrictions! Again, for transport, you can use any transport to transport JWT, == In general, we will use the HTTP header to transport it ==. For example, after a successful login, the server can respond to the client with a JWT:
HTTP/1.1 200 OK
...
Set- cookies: token = JWT tokenAuthorization: JWT tokenToken: JWT token... {... .token: JWT token}Copy the code
As you can see, the JWT token can appear anywhere in the response, by client and server convention.
When the client gets the token, it has only one thing to do: store it. You can store to any location, such as mobile phone files, PC files, localstorage, cookies and so on. When a subsequent request occurs, you simply send it to the server as part of the request.
In this way, the server can receive the token and verify whether the token is valid or not. Their complete interaction process is very simple and clear:
Of 3.
To ensure the security of the token, the JWT token consists of three parts:
- Header: The token header, which records the type and signature algorithm of the entire token
- Payload: The token payload, which records the body information saved, such as the user information you want to save
- Signature: The entire token is signed according to the signature algorithm with a fixed header. The signature is used to ensure that the token is not forged or tampered with
The complete format is: header.Payload. Signature, and the three parts pass. The connection. For example, a complete JWT token looks like this:
eyJhbGdvcml0aG0iOiJIU0EyNTYiLCJKV1QiOiJKV1QifQ==.eyJpc3N1ZXIiOiJ3aGVyZWFib3V0cy5pY3UiLCJvd25lciI6ImtvcmJpbiIsInB1cnBvc2U iOiJBdXRoZW50aWNhdGlvbiIsInJlY2lwaWVudCI6IkJyb3dzZXIiLCJ0aW1lIjoxNjE0MDc0Nzc2LCJleHBpcmUiOjE2MTQwNzgzNzYsImR1cmF0aW9uIjo xODAwMDAwMDAwMDAwLCJleHRlcm5hbCI6bnVsbH0=.xJLdLSUZvSRkCNw4q5I-OS1yaUvbIRhKzgD92FOc470=Copy the code
The values of its parts are:
eyJhbGdvcml0aG0iOiJIU0EyNTYiLCJKV1QiOiJKV1QifQ==
eyJpc3N1ZXIiOiJ3aGVyZWFib3V0cy5pY3UiLCJvd25lciI6ImtvcmJpbiIsInB1cnBvc2UiOiJBdXRoZW50aWNhdGlvbiIsInJlY2lwaWVudCI6IkJyb3dz ZXIiLCJ0aW1lIjoxNjE0MDc0Nzc2LCJleHBpcmUiOjE2MTQwNzgzNzYsImR1cmF0aW9uIjoxODAwMDAwMDAwMDAwLCJleHRlcm5hbCI6bnVsbH0=
xJLdLSUZvSRkCNw4q5I-OS1yaUvbIRhKzgD92FOc470=
3.1 the Header
The header is the token header, which records the type and signature algorithm of the entire token. It is formatted as a JSON object, as follows:
{
"alg":"HSA256"."typ":"JWT"
}
Copy the code
- Alg: The signature algorithm used in the signature section. It can usually take two values
- HS256: A symmetric encryption algorithm that uses the same secret key for signature encryption and decryption
- RS256: An asymmetric encryption algorithm using a private key for encryption and a public key for decryption
- Typ: Specifies the type of the entire token
After setting the structure of the header, we need to Base64 URL encode the header’s JSON object, and the resulting string is the final header part:
eyJhbGdvcml0aG0iOiJIU0EyNTYiLCJ0eXBlIjoiSldUIn0=
Copy the code
Base64 URL is not an encryption algorithm, but an encoding method. It is a variant algorithm based on the Base64 algorithm that makes special treatment for the characters +, =, and /. Base64 uses 64 printable characters to represent binary data. For details, see Baidu Encyclopedia
3.2 content
This section is the body information of JWT. It is still a JSON object and can contain the following:
{
"ss":"Issuer"."iat":"Release Time"."exp":"Due time"."sub":"Theme"."aud":"The audience"."nbf":"Not available until then."."jti":"JWT ID"
}
Copy the code
All or none of the above attributes can be written, it is just a specification, and even if it is written, you need to manually handle it when verifying the JWT token in the future. The meanings expressed by the above attributes are:
- Ss: Who publishes the JWT, either the name of the company or the name of the service
- Iat: The issuing time of the JWT, usually the timestamp of the current time
- Exp: The expiration time of the JWT, usually with a write timestamp
- Sub: What is the JWT for
- Aud: Specifies the terminal to which the JWT is issued. It can be a terminal type or a user name, whichever is optional
- NBF: a point in time before which this token is unavailable
- Jti: The unique number of the JWT. The purpose of this item is to prevent replay attacks (in some scenarios, the user sends the previous token to the server, which is correctly recognized by the server, resulting in unexpected behavior).
In fact, many times we use a custom payload. To be clear, payload is just a JSON object. For example, the following structure I often use is also a valid payload:
{
/ / issue
"issuer":"whereabouts.icu".// The token owner, where ID and other identifiers are stored
"owner":"korbin".// Usage. The default value authentication indicates login authentication
"purpose":"Authentication".// Recipient, indicating the source of the device applying for the token, such as browser, Android, etc
"recipient":"Browser".// Issue time of the token
"time":1614074776.// Expiration time
"expire":1614078376.// The duration of the token is the life cycle
"duration":1800000000000.// Other extended custom parameters
"external":{}
}
Copy the code
Then, the payload part, like the header, needs to be encoded using the Base64 URL:
eyJpc3N1ZXIiOiJ3aGVyZWFib3V0cy5pY3UiLCJvd25lciI6ImtvcmJpbiIsInB1cnBvc2UiOiJBdXRoZW50aWNhdGlvbiIsInJlY2lwaWVudCI6IkJyb3dz ZXIiLCJ0aW1lIjoxNjE0MDkwNDY2LCJleHBpcmUiOjE2MTQwOTIyNjYsImR1cmF0aW9uIjoxODAwMDAwMDAwMDAwLCJleHRlcm5hbCI6bnVsbH0=Copy the code
3.3 Signature
This part is the SIGNATURE of the JWT, and its very existence ensures that the entire JWT is not tampered with. This section is generated in a way that is different from directly encoding the JSON object in the previous two sections. It requires == to pass the encoding results of the first two sections. Connect and then encrypt == as specified in the header. For example, the encryption method specified in the header is HSA256, and the encoding results of the first two parts are:
eyJhbGdvcml0aG0iOiJIU0EyNTYiLCJ0eXBlIjoiSldUIn0=.eyJpc3N1ZXIiOiJ3aGVyZWFib3V0cy5pY3UiLCJvd25lciI6ImtvcmJpbiIsInB1cnBvc2U iOiJBdXRoZW50aWNhdGlvbiIsInJlY2lwaWVudCI6IkJyb3dzZXIiLCJ0aW1lIjoxNjE0MDkwNDY2LCJleHBpcmUiOjE2MTQwOTIyNjYsImR1cmF0aW9uIjo xODAwMDAwMDAwMDAwLCJleHRlcm5hbCI6bnVsbH0=Copy the code
The third part is to use the symmetric encryption algorithm HS256 to encrypt the string, and specify a key, such as: HeZebin
HS256(`header.payload`."HeZebin")
/ / get: N3Fxlh1Nh - FBT9uL59axUhXoUv87FpFoJnZgBtSLyqg =
Copy the code
Finally, three parts will be passed. Together, you get the full JWT. And since the secret key used for the signature is stored on the server, the client cannot forge the signature because it does not have access to the secret key. In other words, the reason JWT cannot be faked is because of the third part, Signature. The first two parts are not encrypted, just a result of encoding, can be considered almost plain text transmission, paste into the online decoding site inside the decoding is a JSON object.
This shouldn’t be too much of a problem, because once the user is logged in, it certainly has the right to see its own user information; Even on some sites, a user’s basic information can be viewed by anyone. Make sure you don’t store sensitive information in JWT, such as passwords.
4. Token verification
JWT signature guarantees that tokens cannot be forged, so how do you guarantee that tokens cannot be tampered with?
For example, a user successfully logs in and gets JWT, but others tamper with payload. For example, the user changes his account balance to double the original amount, and then recodes the payload to the server. How can the server know that such information is tampered? That’s where token validation comes in!!
To verify that the token has been tampered with, the server can do so in a simple way:
- right
header
+payload
Re-encrypt with the same secret key and encryption algorithm; - Then put the encrypted result and pass in
JWT
In thesignature
Compare, if they are identical, it means that the first two parts have not been moved, is issued by themselves, if they are different, it must be tampered with.
Once the token has verified that it has not been tampered with, the server can perform other validations: whether it has expired, whether the listener meets the requirements, and so on, depending on the circumstances.
Note: This is all done manually by the server. No server will do it for you automatically. Of course, you can use third-party libraries to do this.
5. Renewal program
The generated JWT has an expiration time, for example, the token is valid for half an hour, and if the user uses the site for more than 30 minutes, it will be blocked by the filter and suddenly prompted that the login has expired. To log in again, the user must think MDZZ….
Therefore, after a new request by the user, or before the token expires, the user should be unwittingly renewed to avoid the embarrassing scenario of the poor user experience mentioned above.
To be honest, I have read some dual-token renewal schemes on the Internet and I do not understand them. At present, I think there are two simple schemes:
5.1 plan 1
Put the token into the Redis cache and set the expiration time. If the token is accessed within the validity period, the expiration time will be updated. If it is expired, it cannot be found in Redis.
5.2 plan 2
Use the expire and duration fields of payload. If the current time and expiration time calculated in the filter of a request is less than 5 minutes, add another 30 minutes to the current time, and then re-encode and encrypt the token to generate a new token and return it in the response header. The front-end axios responds to the interceptor by checking the token field in the header. If there is a value, it indicates that a new token has been issued, and then updates the token in localStorage. After the update, it will carry the new token next time.
6. Summary Token
Finally, summarize the characteristics of JWT:
- JWT is essentially a token format. It’s not about the terminal, it’s not about the server, it’s not even about how it’s transmitted, it’s just about the format of the token;
- JWT consists of three parts:
header
,payload
,signature
, the body information is inpayload
; - JWT is difficult to tamper with and forge because of the presence of a third part signature.
In addition, it is possible to generate tokens that do not follow the JWT specification. As long as you generate your own strings using an algorithm that is not easy to crack, you can use them as validation tokens. Developers can customize tokens according to business logic, but using the official JWT specification is more user-friendly.
7.Golang code implementation
My own library implements a tool: github.com/whereabouts/utils, JWT under the library package provides some basic functions of JWT.
go get github.com/whereabouts/utils
Copy the code
Example to use after downloading the library:
package main
import (
"fmt"
"github.com/whereabouts/utils/jwt"
)
func main(a) {
// example of jwt
fmt.Println("# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #")
token := jwt.NewToken().SetOwner("korbin").String()
fmt.Println(token)
fmt.Println(jwt.Check(token))
fmt.Println(jwt.Check(token + "1"))
fmt.Println(jwt.IsExpire(token))
fmt.Printf("%+v\n", jwt.GetPayload(token))
token = jwt.Refresh(token)
fmt.Println(token)
fmt.Printf("%+v\n", jwt.GetPayload(token))
}
Copy the code
In addition, the library also provides a slice package for quick deletion of slice elements, interested friends can also see.