This is the first day of my participation in the Gwen Challenge in November. Check out the details: the last Gwen Challenge in 2021

1. What is JWT

JWT (JSON Web Token) is a lightweight specification that allows us to use JWT to deliver secure and reliable information between users and servers. A JWT consists of three parts,

Header Claims payload Signature

The principle of JWT is similar to the process of affiking official seal or handwritten signature. There are a lot of clauses in the contract, not just on a piece of paper, but some proof, such as signature or seal, is required. JWT guarantees that the information transmitted is true, not forged, by attaching signatures.

It encrypts the user information into the token. The server does not save any user information. The server verifies the correctness of the token by using the saved key.

2. JWT

A JWT consists of three parts,

Header Claims payload Signature

  • Header: Indicates the Header, indicating the type and encryption algorithm

  • 1) Claims: a claim or a load

  • Signature: A Signature formed after a base64 transcoding of header and claims is performed and the encryption algorithm declared in the header is salted with secret.

    Let TMPSTR = base64(header)+ Base64 (Claims) let signature = encrypt(TMPSTR,secret)  let token = base64(header)+"."+base64(claims)+"."+signatureCopy the code

3. Javascript extracts load information from JWT string

The payload in the JWT can contain many fields. The more fields you have, the longer your token string will be. The more data your HTTP request communication sends, the longer the response time back to the interface waits.

The following code is the front-end javascript that retrieves the user information from payload. Back-end middleware can also parse the payload to retrieve user information, reducing the need to query the user table data in the database. The interface is faster and the database is less stressed. The backend checks whether the payload and Signature Signature are valid during JWT authentication.

let tokenString = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Njc3Nzc5NjIsImp0aSI6IjUiLCJpYXQiOjE1Njc2OTE1NjIsImlzcyI6ImZlbGl4Lm1vam 90di5jbiIsImlkIjo1LCJjcmVhdGVkX2F0IjoiMjAxOS0wOS0wNVQxMTo1Njo1OS41NjI1NDcwODYrMDg6MDAiLCJ1cGRhdGVkX2F0IjoiMjAxOS0wOS0wNV QxNjo1ODoyMC41NTYxNjAwOTIrMDg6MDAiLCJ1c2VybmFtZSI6ImVyaWMiLCJuaWNrX25hbWUiOiIiLCJlbWFpbCI6IjEyMzQ1NkBxcS5jb20iLCJtb2JpbG UiOiIiLCJyb2xlX2lkIjo4LCJzdGF0dXMiOjAsImF2YXRhciI6Ii8vdGVjaC5tb2pvdHYuY24vYXNzZXRzL2ltYWdlL2F2YXRhcl8zLnBuZyIsInJlbWFyay I6IiIsImZyaWVuZF9pZHMiOm51bGwsImthcm1hIjowLCJjb21tZW50X2lkcyI6bnVsbH0.tGjukvuE9JVjzDa42iGfh_5jIembO5YZBZDqLnaG6KQ' function parseTokenGetUser(jwtTokenString) { let base64Url = jwtTokenString.split('.')[1]; let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); let jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); let user = JSON.parse(jsonPayload); localStorage.setItem("token", jwtTokenString); localStorage.setItem("expire_ts", user.exp); localStorage.setItem("user", jsonPayload); return user; } parseTokenGetUser(tokenString)Copy the code

Copy the javascript code above to the browser console to parse out the user information! You can use the JWT online parsing tool to parse the payload of the JWT token

4. Go language Gin framework to achieve JWT user authentication

Next I will demonstrate how to use JWT authentication using the two most popular packages gIN-GONic/GIN and Dgrijalva/JWT-Go.

4.1 Login Interface

4.1.1 Login Interface Route (login-route)

r := gin.New() r.MaxMultipartMemory = 32 << 20 //sever static file in http's root path binStaticMiddleware, err := felixbin.NewGinStaticBinMiddleware("/") if err ! New(cers. Config{AllowOrigins: []string{"*"}, AllowMethods: []string{"PUT", "PATCH", "POST", "GET", "DELETE"}, AllowHeaders: []string{"Origin", "Authorization", "Content-Type"}, ExposeHeaders: []string{"Content-Type"}, AllowCredentials: true, AllowOriginFunc: func(origin string) bool { return true }, MaxAge: 2400 * time.Hour, }) r.Use(binStaticMiddleware, mwCORS) { r.POST("comment-login", R.post ("comment-register", Internal. RegisterCommenter) / / comment on user registration} API: r. gutierrez roup = (" API ") API. POST (" admin - login ", internal LoginAdmin)/landing/management backgroundCopy the code

The internal.LoginCommenter and internal.LoginAdmin methods are the same. You only need to focus on one of them

4.1.2 Logging In to the Login Handler

Write the login handler

func LoginCommenter(c *gin.Context) { var mdl model.User err := c.ShouldBind(&mdl) if handleError(c, Err) {return} // Obtain the IP address IP := c.clientip () //roleId 8 Is the user of the comment system Data, err := mdl.login (IP, 8) if handleError(c, err) { return } jsonData(c, data) }Copy the code

The most critical of these is the mdL.login (IP, 8) function

  • 1. The database queries users

  • 2. Verify user role_id

  • 3. Compare passwords

  • 4. Prevent password leakage (clear struct attributes)

  • 5. Generate JWT – string

    //Login func (m User) Login(ip string, roleId uint) (string, error) { m.Id = 0 if m.Password == “” { return “”, Errors. New(“password is required”)} inputPassword := m.password err := db.Where(“username =? or email = ?” , m.Username, m.Username).First(&m).Error if err ! = nil {return “”, err} // Check user role if (m.leid & roleId)! = roleId { return “”, fmt.Errorf(“not role of %d”, RoleId)} / / verification password / / password set to bcrypt check if err: = bcrypt.Com pareHashAndPassword (byte [] (m.H ashedPassword), []byte(inputPassword)); err ! = nil {return “”, err} // prevent password leakage m.password = “” // generate jwt-string return jwtGenerateToken(m, time.hour24 *365)}

4.1.2 Generating JWT-String (Core code)

  • 1. Customize the payload structure. It is not recommended to use the dgrijalva/ jwT-go jwt.StandardClaims structure. Because his payload contains too little user information.

  • 2. Implement the Valid() error method of Type Claims Interface to customize the verification content

  • Jwt-string jwtGenerateToken(m *User,d time.duration) (string, error)

    package model

    import ( “errors” “fmt” “time”

    "github.com/dgrijalva/jwt-go"
    "github.com/sirupsen/logrus"
    Copy the code

    )

    Var AppSecret = “” / / 9. Get string will be setting this value (32 byte length) var AppIss =” github.com/libragen/felix “/ / this value will be 9. Get string rewriting

    // Customize the payload structure. Dgrijalva /jwt-go jwt.StandardClaims is not recommended. Type userStdClaims struct {jwt.StandardClaims *User} // Implement the Valid() error of type Claims interface Func (c userStdClaims) Valid() (err Error) {if c.verifyExpiresat (time.now ().unix (), true) == false { return errors.New(“token is expired”) } if ! c.VerifyIssuer(AppIss, true) { return errors.New(“token’s issuer is wrong”) } if c.User.Id < 1 { return errors.New(“invalid user in jwt”) } return }

    func jwtGenerateToken(m *User,d time.Duration) (string, error) { m.Password = “” expireTime := time.Now().Add(d) stdClaims := jwt.StandardClaims{ ExpiresAt: expireTime.Unix(), IssuedAt: time.Now().Unix(), Id: fmt.Sprintf(“%d”, m.Id), Issuer: AppIss, }

    uClaims := userStdClaims{ StandardClaims: stdClaims, User: m, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, uClaims) // Sign and get the complete encoded token as a string using the secret tokenString, err := token.SignedString([]byte(AppSecret)) if err ! = nil { logrus.WithError(err).Fatal("config is wrong, can not generate jwt") } return tokenString, errCopy the code

    }

    Func JwtParseUser(tokenString string) (*User, error) { if tokenString == “” { return nil, errors.New(“no token is found in Authorization Bearer”) } claims := userStdClaims{} _, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); ! ok { return nil, fmt.Errorf(“unexpected signing method: %v”, token.Header[“alg”]) } return []byte(AppSecret), nil }) if err ! = nil { return nil, err } return claims.User, err }

4.2 JWT Middleware

  • 1. Get the JwT-string from _t of urL-query or the Jwt-string from request header Authorization

  • 2. Model.jwtparseuser (token) parse jwT-string to obtain User structure (reduce the operation and time of middleware query database)

  • Get(contextKeyUserObj) to obtain the model.User structure after User Type Assert.

  • 4. Handle after JWT-MIDDLE is used to obtain user information from gin.Context

    package internal

    import ( “net/http” “strings”

    "github.com/libragen/felix/model"
    "github.com/gin-gonic/gin"
    Copy the code

    )

    const contextKeyUserObj = “authedUserObj” const bearerLength = len(“Bearer “)

    func ctxTokenToUser(c *gin.Context, roleId uint) { token, ok := c.GetQuery(“_t”) if ! ok { hToken := c.GetHeader(“Authorization”) if len(hToken) < bearerLength { c.AbortWithStatusJSON(http.StatusPreconditionFailed, gin.H{“msg”: “header Authorization has not Bearer token”}) return } token = strings.TrimSpace(hToken[bearerLength:]) } usr, err := model.JwtParseUser(token) if err ! = nil { c.AbortWithStatusJSON(http.StatusPreconditionFailed, gin.H{“msg”: err.Error()}) return } if (usr.RoleId & roleId) ! RoleId = {c.a. bortWithStatusJSON (HTTP StatusPreconditionFailed, gin. H {” MSG “:” roleId without permission “}) return}

    //store the user Model in the context
    c.Set(contextKeyUserObj, *usr)
    c.Next()
    // after request
    Copy the code

    }

    func MwUserAdmin(c *gin.Context) { ctxTokenToUser(c, 2) }

    func MwUserComment(c *gin.Context) { ctxTokenToUser(c, 8) }

Handle after JwT-middle is used to retrieve user information from gin.Context,

func mWuserId(c *gin.Context) (uint, error) { v,exist := c.Get(contextKeyUserObj) if ! exist { return 0,errors.New(contextKeyUserObj + " not exist") } user, ok := v.(model.User) if ok { return user.Id, nil } return 0,errors.New("can't convert to user struct") }Copy the code

4.2 Using JWT Middleware

The following code has two uses for JWT middleware

  • MwUserAdmin manages the back-end user middleware

  • MwUserCommenter reviews user middleware

    package ssh2ws

    import ( “time”

    "github.com/libragen/felix/felixbin"
    "github.com/libragen/felix/model"
    "github.com/libragen/felix/ssh2ws/internal"
    "github.com/libragen/felix/wslog"
    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
    Copy the code

    )

    func RunSsh2ws(bindAddress, user, password, secret string, expire time.Duration, verbose bool) error { err := model.CreateGodUser(user, password) if err ! = nil { return err } //config jwt variables model.AppSecret = secret model.ExpireTime = expire model.AppIss = “felix.mojotv.cn” if ! verbose { gin.SetMode(gin.ReleaseMode) } r := gin.New() r.MaxMultipartMemory = 32 << 20 //sever static file in http’s root path binStaticMiddleware, err := felixbin.NewGinStaticBinMiddleware(“/”) if err ! = nil { return err }

    mwCORS := cors.New(cors.Config{ AllowOrigins: []string{"*"}, AllowMethods: []string{"PUT", "PATCH", "POST", "GET", "DELETE"}, AllowHeaders: []string{"Origin", "Authorization", "Content-Type"}, ExposeHeaders: []string{"Content-Type"}, AllowCredentials: true, AllowOriginFunc: func(origin string) bool { return true }, MaxAge: 2400 * time.Hour, }) r.Use(binStaticMiddleware, mwCORS) { r.POST("comment-login", R.post ("comment-register", Internal. RegisterCommenter) / / comment on user registration} API: r. gutierrez roup = (" API ") API. POST (" admin - login ", GET("meta", internal.Meta) //terminal log hub := wslog.NewHub() go hub.Run() { //websocket r.GET("ws/hook", internal.MwUserAdmin, Wslog(hub) r.gate ("ws/ SSH /:id", internal.MwUserAdmin, internal.WsSsh)} internal.JwtMiddlewareWslog, internal.WsLogHookApi(hub)) api.GET("wslog/hook", internal.MwUserAdmin, internal.WslogHookAll) api.POST("wslog/hook", internal.MwUserAdmin, internal.WslogHookCreate) api.PATCH("wslog/hook", internal.MwUserAdmin, internal.WslogHookUpdate) api.DELETE("wslog/hook/:id", internal.MwUserAdmin, internal.WslogHookDelete) api.GET("wslog/msg", internal.MwUserAdmin, internal.WslogMsgAll) api.POST("wslog/msg-rm", } // comment {api.get ("comment", internal.CommentAll) api.GET("comment/:id/:action", internal.MwUserComment, internal.CommentAction) api.POST("comment", internal.MwUserComment, internal.CommentCreate) api.DELETE("comment/:id", internal.MwUserAdmin, internal.CommentDelete) } { api.GET("hacknews",internal.MwUserAdmin, internal.HackNewAll) api.PATCH("hacknews", internal.HackNewUpdate) api.POST("hacknews-rm", internal.HackNewRm) } authG := api.Use(internal.MwUserAdmin) { //create wslog hook authG.GET("ssh", internal.SshAll) authG.POST("ssh", internal.SshCreate) authG.GET("ssh/:id", internal.SshOne) authG.PATCH("ssh", internal.SshUpdate) authG.DELETE("ssh/:id", internal.SshDelete) authG.GET("sftp/:id", internal.SftpLs) authG.GET("sftp/:id/dl", internal.SftpDl) authG.GET("sftp/:id/cat", internal.SftpCat) authG.GET("sftp/:id/rm", internal.SftpRm) authG.GET("sftp/:id/rename", internal.SftpRename) authG.GET("sftp/:id/mkdir", internal.SftpMkdir) authG.POST("sftp/:id/up", internal.SftpUp) authG.POST("ginbro/gen", internal.GinbroGen) authG.POST("ginbro/db", internal.GinbroDb) authG.GET("ginbro/dl", internal.GinbroDownload) authG.GET("ssh-log", internal.SshLogAll) authG.DELETE("ssh-log/:id", internal.SshLogDelete) authG.PATCH("ssh-log", internal.SshLogUpdate) authG.GET("user", internal.UserAll) authG.POST("user", internal.RegisterCommenter) //api.GET("user/:id", internal.SshAll) authG.DELETE("user/:id", internal.UserDelete) authG.PATCH("user", internal.UserUpdate) } if err := r.Run(bindAddress); err ! = nil { return err } return nilCopy the code

    }

5. Cookie-Session VS JWT

JWT is different from Session. The session needs to be generated on the server. The server saves the session and only returns the sessionID to the client Synchronous multiple hosts of information, or it will appear in the request to A server can access to information, but the request B server identity information can’t through, JWT can be A very good solution to this problem, the server side don’t have to save the JWT, only need to save encrypted with A secret, when A user logs in to JWT encryption generated and sent to the client, the client storage, later The JWT is parsed and verified by the server so that the server does not waste space to store login information and time to synchronize.

5.1 What is a Cookie

Cooky-based authentication is stateful, which means that authenticated records or sessions must be stored on both the server and the client. The server needs to keep track of the session and store it in the database. At the same time, the front-end needs to save a sessionID in the cookie as the unique identifier of the session, which can be regarded as the “ID card” of the session.

Cookie, in short, is to save some historical information about user operations (including login information, of course) on the client (browser, etc.), and when the user visits the site again, the browser sends the local cookie content to the server through HTTP protocol, so as to complete authentication, or continue with the previous step.

5.2 What is Session

The session, the session, in a nutshell is the historical information on the server to save the user action, after the user login server to store information about the user’s session, and specify an access credentials for the client, if there is a client with this certificate request, is on the server storage of information, take out the user login information, And the credentials returned by the server are usually stored in cookies. You can also rewrite the URL and put the ID in the URL. Generally speaking, the access credentials are SessionID.

5.3 Cookie-session Authentication Process

The purpose of session and cookie is the same, both of which are to overcome the defect of HTTP stateless protocol, but the methods of completion are different. Session can be accomplished through cookies. The session ID is saved on the client and other session messages of the user are saved in the session object on the server. In contrast, cookies require all information to be saved on the client. Therefore, cookies have certain security risks, such as the user name and password saved in local cookies being deciphered, or cookies being collected by other websites (for example: 1. AppA takes the initiative to set domain B cookies for domain B cookies to obtain; 2. 2. XSS, obtain document.cookie through javascript on appA and pass it to appB),

  1. The user enters the login information
  2. The server verifies that the login information is correct, creates a session if correct, and stores the session to the database
  3. The server returns a cookie with a sessionID to the client
  4. On the next request, the server matches the sessionID to the database and processes the request if it is valid
  5. If the user logs out of the app, the session is destroyed on both the client and server side

5.4 Cookie-session and JWT Usage Scenarios

Cookie-session authentication is recommended for back-end HTML pages

After the render page can be very convenient to write/clear cookies to the browser, very convenient permission control. You rarely need to worry about cross-domain AJAX authentication.

JWT authentication is recommended for APPS, Web single-page applications, and APIs

With the rise of apps, Web APIs, and so on, token-based authentication has become popular. When we talk about using Tokens,we generally talk about using JSON Web Tokens (JWTs) to authenticate. In fact,JWTs has become the standard, so in this article we will swap tokens with JWTs,

The End

Online communication tools: in your terminal type SSH [email protected]

Type SSH mojotv.cn hn in your terminal to view the latest Hacknews