一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Golang - 詳解Go-JWT-RESTful身份認證教程

詳解Go-JWT-RESTful身份認證教程

2020-05-28 10:53TechMojotv Golang

這篇文章主要介紹了詳解Go-JWT-RESTful身份認證教程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

1.什么是JWT

JWT(JSON Web Token)是一個非常輕巧的規范,這個規范允許我們使用JWT在用戶和服務器之間傳遞安全可靠的信息,
一個JWT由三部分組成,Header頭部、Claims載荷、Signature簽名,

JWT原理類似我們加蓋公章或手寫簽名的的過程,合同上寫了很多條款,不是隨便一張紙隨便寫啥都可以的,必須要一些證明,比如簽名,比如蓋章,JWT就是通過附加簽名,保證傳輸過來的信息是真的,而不是偽造的,

它將用戶信息加密到token里,服務器不保存任何用戶信息,服務器通過使用保存的密鑰驗證token的正確性,只要正確即通過驗證,

2.JWT構成

一個JWT由三部分組成,Header頭部、Claims載荷、Signature簽名,

  • Header頭部:頭部,表明類型和加密算法
  • Claims載荷:聲明,即載荷(承載的內容)
  • Signature簽名:簽名,這一部分是將header和claims進行base64轉碼后,并用header中聲明的加密算法加鹽(secret)后構成,即:
?
1
2
3
4
let tmpstr = base64(header)+base64(claims)
let signature = encrypt(tmpstr,secret)
//最后三者用"."連接,即:
let token = base64(header)+"."+base64(claims)+"."+signature

3.javascript提取JWT字符串荷載信息

JWT里面payload可以包含很多字段,字段越多你的token字符串就越長.
你的HTTP請求通訊的發送的數據就越多,回到之接口響應時間等待稍稍的變長一點點.

一下代碼就是前端javascript從payload獲取登錄的用戶信息.
當然后端middleware也可以直接解析payload獲取用戶信息,減少到數據庫中查詢user表數據.接口速度會更快,數據庫壓力更小.
后端檢查JWT身份驗證時候當然會校驗payload和Signature簽名是否合法.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let tokenString = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Njc3Nzc5NjIsImp0aSI6IjUiLCJpYXQiOjE1Njc2OTE1NjIsImlzcyI6ImZlbGl4Lm1vam90di5jbiIsImlkIjo1LCJjcmVhdGVkX2F0IjoiMjAxOS0wOS0wNVQxMTo1Njo1OS41NjI1NDcwODYrMDg6MDAiLCJ1cGRhdGVkX2F0IjoiMjAxOS0wOS0wNVQxNjo1ODoyMC41NTYxNjAwOTIrMDg6MDAiLCJ1c2VybmFtZSI6ImVyaWMiLCJuaWNrX25hbWUiOiIiLCJlbWFpbCI6IjEyMzQ1NkBxcS5jb20iLCJtb2JpbGUiOiIiLCJyb2xlX2lkIjo4LCJzdGF0dXMiOjAsImF2YXRhciI6Ii8vdGVjaC5tb2pvdHYuY24vYXNzZXRzL2ltYWdlL2F2YXRhcl8zLnBuZyIsInJlbWFyayI6IiIsImZyaWVuZF9pZHMiOm51bGwsImthcm1hIjowLCJjb21tZW50X2lkcyI6bnVsbH0.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)

復制上面javascript代碼到瀏覽器console中執行就可以解析出用戶信息了! 當然你要可以使用在線工具來解析jwt token的payload荷載
JWT在線解析工具

4. go語言Gin框架實現JWT用戶認證

接下來我將使用最受歡迎的gin-gonic/gin 和 dgrijalva/jwt-go

這兩個package來演示怎么使用JWT身份認證.

4.1 登錄接口

4.1.1 登錄接口路由(login-route)

https://github.com/libragen/felix/blob/master/ssh2ws/ssh2ws.go

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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", internal.LoginCommenter)    //評論用戶登陸
  r.POST("comment-register", internal.RegisterCommenter) //評論用戶注冊
}
 
api := r.Group("api")
api.POST("admin-login", internal.LoginAdmin) //管理后臺登陸

internal.LoginCommenterinternal.LoginAdmin 這兩個方法是一樣的,
只需要關注其中一個就可以了,我們就關注internal.LoginCommenter

4.1.2 登錄login handler

編寫登錄的handler

https://github.com/libragen/felix/blob/master/ssh2ws/internal/h_login.go

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func LoginCommenter(c *gin.Context) {
  var mdl model.User
  err := c.ShouldBind(&mdl)
  if handleError(c, err) {
    return
  }
  //獲取ip
  ip := c.ClientIP()
  //roleId 8 是評論系統的用戶
  data, err := mdl.Login(ip, 8)
  if handleError(c, err) {
    return
  }
  jsonData(c, data)
}

其中最關鍵的是mdl.Login(ip, 8)這個函數
https://github.com/libragen/felix/blob/master/model/m_users.go

  • 1.數據庫查詢用戶
  • 2.校驗用戶role_id
  • 3.比對密碼
  • 4.防止密碼泄露(清空struct的屬性)
  • 5.生成JWT-string
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//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
  }
  //校驗用戶角色
  if (m.RoleId & roleId) != roleId {
    return "", fmt.Errorf("not role of %d", roleId)
  }
  //驗證密碼
  //password is set to bcrypt check
  if err := bcrypt.CompareHashAndPassword([]byte(m.HashedPassword), []byte(inputPassword)); err != nil {
    return "", err
  }
  //防止密碼泄露
  m.Password = ""
  //生成jwt-string
  return jwtGenerateToken(m, time.Hour*24*365)
}

4.1.2 生成JWT-string(核心代碼)

1.自定義payload結構體,不建議直接使用 dgrijalva/jwt-go jwt.StandardClaims結構體.因為他的payload包含的用戶信息太少.

2.實現 type Claims interfaceValid() error 方法,自定義校驗內容

3.生成JWT-string jwtGenerateToken(m *User,d time.Duration) (string, error)

https://github.com/libragen/felix/blob/master/model/m_jwt.go

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package model
 
import (
  "errors"
  "fmt"
  "time"
 
  "github.com/dgrijalva/jwt-go"
  "github.com/sirupsen/logrus"
)
 
var AppSecret = ""//viper.GetString會設置這個值(32byte長度)
var AppIss = "github.com/libragen/felix"//這個值會被viper.GetString重寫
 
//自定義payload結構體,不建議直接使用 dgrijalva/jwt-go `jwt.StandardClaims`結構體.因為他的payload包含的用戶信息太少.
type userStdClaims struct {
  jwt.StandardClaims
  *User
}
//實現 `type Claims interface` 的 `Valid() error` 方法,自定義校驗內容
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, err
}
 
 
//JwtParseUser 解析payload的內容,得到用戶信息
//gin-middleware 會使用這個方法
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.從url-query的_t獲取JWT-string或者從請求頭 Authorization中獲取JWT-string

2.model.JwtParseUser(token)解析JWT-string獲取User結構體(減少中間件查詢數據庫的操作和時間)

3.設置用戶信息到gin.Context 其他的handler通過gin.Context.Get(contextKeyUserObj),在進行用戶Type Assert得到model.User 結構體.

4.使用了jwt-middle之后的handle從gin.Context中獲取用戶信息

https://github.com/libragen/felix/blob/master/ssh2ws/internal/mw_jwt.go

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package internal
 
import (
  "net/http"
  "strings"
 
  "github.com/libragen/felix/model"
  "github.com/gin-gonic/gin"
)
 
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.AbortWithStatusJSON(http.StatusPreconditionFailed, gin.H{"msg": "roleId 沒有權限"})
    return
  }
 
  //store the user Model in the context
  c.Set(contextKeyUserObj, *usr)
  c.Next()
  // after request
}
 
func MwUserAdmin(c *gin.Context) {
  ctxTokenToUser(c, 2)
}
 
func MwUserComment(c *gin.Context) {
  ctxTokenToUser(c, 8)
}

使用了jwt-middle之后的handle從gin.Context中獲取用戶信息,
https://github.com/libragen/felix/blob/master/ssh2ws/internal/helper.go

?
1
2
3
4
5
6
7
8
9
10
11
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")
}

4.2 使用JWT中間件

一下代碼有兩個JWT中間件的用法

  • internal.MwUserAdmin 管理后臺用戶中間件
  • internal.MwUserCommenter 評論用戶中間件

https://github.com/libragen/felix/blob/master/ssh2ws/ssh2ws.go

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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"
)
 
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", internal.LoginCommenter)    //評論用戶登陸
    r.POST("comment-register", internal.RegisterCommenter) //評論用戶注冊
  }
 
  api := r.Group("api")
  api.POST("admin-login", internal.LoginAdmin) //管理后臺登陸
  api.GET("meta", internal.Meta)
 
  //terminal log
  hub := wslog.NewHub()
  go hub.Run()
 
  {
    //websocket
    r.GET("ws/hook", internal.MwUserAdmin, internal.Wslog(hub))
    r.GET("ws/ssh/:id", internal.MwUserAdmin, internal.WsSsh)
  }
  //給外部調用
  {
    api.POST("wslog/hook-api", 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", internal.MwUserAdmin, internal.WslogMsgDelete)
  }
 
  //評論
  {
    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 nil
}

5. Cookie-Session VS JWT

JWT和session有所不同,session需要在服務器端生成,服務器保存session,只返回給客戶端sessionid,客戶端下次請求時帶上sessionid即可,因為session是儲存在服務器中,有多臺服務器時會出現一些麻煩,需要同步多臺主機的信息,不然會出現在請求A服務器時能獲取信息,但是請求B服務器身份信息無法通過,JWT能很好的解決這個問題,服務器端不用保存jwt,只需要保存加密用的secret,在用戶登錄時將jwt加密生成并發送給客戶端,由客戶端存儲,以后客戶端的請求帶上,由服務器解析jwt并驗證,這樣服務器不用浪費空間去存儲登錄信息,不用浪費時間去做同步,

5.1 什么是cookie

基于cookie的身份驗證是有狀態的,這意味著驗證的記錄或者會話(session)必須同時保存在服務器端和客戶端,服務器端需要跟蹤記錄session并存至數據庫,
同時前端需要在cookie中保存一個sessionID,作為session的唯一標識符,可看做是session的“身份證”,

cookie,簡而言之就是在客戶端(瀏覽器等)保存一些用戶操作的歷史信息(當然包括登錄信息),并在用戶再次訪問該站點時瀏覽器通過HTTP協議將本地cookie內容發送給服務器,從而完成驗證,或繼續上一步操作,

詳解Go-JWT-RESTful身份認證教程

5.2 什么是session

session,會話,簡而言之就是在服務器上保存用戶操作的歷史信息,在用戶登錄后,服務器存儲用戶會話的相關信息,并為客戶端指定一個訪問憑證,如果有客戶端憑此憑證發出請求,則在服務端存儲的信息中,取出用戶相關登錄信息,
并且使用服務端返回的憑證常存儲于Cookie中,也可以改寫URL,將id放在url中,這個訪問憑證一般來說就是SessionID,

詳解Go-JWT-RESTful身份認證教程

5.3 cookie-session身份驗證機制的流程

session和cookie的目的相同,都是為了克服http協議無狀態的缺陷,但完成的方法不同,
session可以通過cookie來完成,在客戶端保存session id,而將用戶的其他會話消息保存在服務端的session對象中,與此相對的,cookie需要將所有信息都保存在客戶端,
因此cookie存在著一定的安全隱患,例如本地cookie中保存的用戶名密碼被破譯,或cookie被其他網站收集(例如:1. appA主動設置域B cookie,讓域B cookie獲取;2. XSS,在appA上通過javascript獲取document.cookie,并傳遞給自己的appB),

  1. 用戶輸入登錄信息
  2. 服務器驗證登錄信息是否正確,如果正確就創建一個session,并把session存入數據庫
  3. 服務器端會向客戶端返回帶有sessionID的cookie
  4. 在接下來的請求中,服務器將把sessionID與數據庫中的相匹配,如果有效則處理該請求
  5. 如果用戶登出app,session會在客戶端和服務器端都被銷毀

5.4 Cookie-session 和 JWT 使用場景

后端渲染HTML頁面建議使用Cookie-session認證

后按渲染頁面可以很方便的寫入/清除cookie到瀏覽器,權限控制非常方便.很少需要要考慮跨域AJAX認證的問題.

App,web單頁面應用,APIs建議使用JWT認證

App、web APIs等的興起,基于token的身份驗證開始流行,
當我們談到利用token進行認證,我們一般說的就是利用JSON Web Tokens(JWTs)進行認證,雖然有不同的方式來實現token,
事實上,JWTs 已成為標準,因此在本文中將互換token與JWTs,

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流, 謝謝大家對mojotv.cn的支持.喜歡這個網站麻煩幫忙添加到收藏夾,添加我的微信好友: felixarebest 微博賬號: MojoTech 向我提問.

原文地址:Go進階24:Go-jwt RESTful身份認證教程

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:https://segmentfault.com/a/1190000020329813

延伸 · 閱讀

精彩推薦
  • Golanggolang的httpserver優雅重啟方法詳解

    golang的httpserver優雅重啟方法詳解

    這篇文章主要給大家介紹了關于golang的httpserver優雅重啟的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    helight2992020-05-14
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
  • GolangGolang中Bit數組的實現方式

    Golang中Bit數組的實現方式

    這篇文章主要介紹了Golang中Bit數組的實現方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨尊11682021-06-09
  • Golanggolang json.Marshal 特殊html字符被轉義的解決方法

    golang json.Marshal 特殊html字符被轉義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggo日志系統logrus顯示文件和行號的操作

    go日志系統logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
  • GolangGolang通脈之數據類型詳情

    Golang通脈之數據類型詳情

    這篇文章主要介紹了Golang通脈之數據類型,在編程語言中標識符就是定義的具有某種意義的詞,比如變量名、常量名、函數名等等,Go語言中標識符允許由...

    4272021-11-24
  • Golanggolang如何使用struct的tag屬性的詳細介紹

    golang如何使用struct的tag屬性的詳細介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網11352020-05-21
主站蜘蛛池模板: 国产精品对白刺激久久久 | 亚洲精品无码不卡在线观看 | 国产 日韩欧美 | 青柠网在线观看视频 | 99久久免费国内精品 | 亚欧国产 | 校花被老头夺去第一次动图 | 日产乱码卡1卡2卡三卡四在线 | 2020韩国三级理论在线观看 | www.四虎.com| 国内精品久久久久影院中国 | 国产精品玖玖玖影院 | 久草在在线免视频在线观看 | 国产亚洲人成网站天堂岛 | 国产日韩一区二区三区在线播放 | 毛片影院 | 故意短裙公车被强好爽在线播放 | 精品亚洲午夜久久久久 | 国产伦精品一区二区三区女 | 久久国产精品无码视欧美 | 久久五月综合婷婷中文云霸高清 | 香蕉在线精品亚洲第一区 | 久久久亚洲国产精品主播 | 无遮挡免费h肉动漫在线观看 | 精品AV亚洲乱码一区二区 | 日本大尺度动漫在线观看缘之空 | 亚洲欧美一区二区三区不卡 | chaopeng在线视频进入 | 男人视频网 | 特级老女人淫片高清视频 | 欧美国产日韩在线 | 五月天色网站 | 亚洲精品久久玖玖玖玖 | 日韩一区在线播放 | 免费高清www动漫视频播放器 | 色999| 啪啪导航 | 青青在线视频免费 | 91综合精品网站久久 | 91制片厂制作果冻传媒2021 | 侵犯小男生免费视频网站 |