什么是JWT?
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,也是目前前后端分离项目中普遍使用的认证技术. 本文介绍如何在Golang Gin Web框架中使用JWT认证中间件以及模拟测试, 以供参考, 关于JWT详细原理可以参考:
JWT RFC: https://tools.ietf.org/html/rfc7519
JWT IETF: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
JSON Web Token入门教程: http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
主要流程
初始化Gin引擎
定义获取Token的接口, 访问该接口, 内部自动生成JWT令牌, 并返回给前端
定义需要认证的路由接口, 使用JWT中间件进行认证, 中间件由
利用GoConvey(Golang的测试框架,集成go test, 支持终端和浏览器模式), 构造客户端, 填写Token, 模拟前端访问
JWT中间件进行认证, 认证通过则返回消息体, 否则直接返回401或其他错误
流程图
该流程图描述了服务端代码中的Token构造, 以及认证流程.
服务端代码
main.go中填充以下代码, 运行go run main.go, 开启Web服务.
package mainimport ( jwt_lib "github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go/request" "github.com/gin-gonic/gin" "log" "time")var ( mysupersecretpassword = "unicornsAreAwesome")func Auth(secret string) gin.HandlerFunc { return func(c *gin.Context) { //log.Printf("Request:\n%+v", c.Request) // ParseFromRequest方法提取路径请求中的JWT令牌, 并进行验证 token, err := request.ParseFromRequest(c.Request, request.OAuth2Extractor, func(token *jwt_lib.Token) (interface{}, error) { b := ([]byte(secret)) //log.Printf("b:%+v", b) return b, nil }) log.Printf("token:%+v", token) if err != nil { c.AbortWithError(401, err) } }}func main() { r := gin.Default() public := r.Group("/api") // 定义根路由, 访问http://locahost:8080/api/可以获取到token public.GET("/", func(c *gin.Context) { // Create the token New方法接受一个签名方法的接口类型(SigningMethod)参数, 返回一个Token结构指针 // GetSigningMethod(签名算法algorithm) token := jwt_lib.New(jwt_lib.GetSigningMethod("HS256")) //默认是签名算法是HMAC SHA256(写成 HS256) log.Printf("token:%+v", token) //2020/12/10 22:32:02 token:&{Raw: Method:0xc00000e2a0 Header:map[alg:HS256 typ:JWT] Claims:map[] Signature: Valid:false} // Set some claims 设置Id和过期时间字段, MapClaims实现了Clainms接口 token.Claims = jwt_lib.MapClaims{ "Id": "Christopher", "exp": time.Now().Add(time.Hour * 1).Unix(), } // Sign and get the complete encoded token as a string // 签名并得到完整编码后的Token字符串 tokenString, err := token.SignedString([]byte(mysupersecretpassword)) //{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJZCI6IkNocmlzdG9waGVyIiwiZXhwIjoxNjA3NjE0MzIyfQ.eQd7ztDn3706GrpitgnikKgOtzx-RHnq7cr2eqUlsZo"} if err != nil { c.JSON(500, gin.H{"message": "Could not generate token"}) } c.JSON(200, gin.H{"token": tokenString}) }) // 定义需要Token验证通过才能访问的私有接口组http://localhost:8080/api/private private := r.Group("/api/private") private.Use(Auth(mysupersecretpassword)) // 使用JWT认证中间件(带参数) /* Set this header in your request to get here. Authorization: Bearer `token` */ // 定义具体的私有根接口:http://localhost:8080/api/private/ private.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{"message": "Hello from private"}) }) r.Run("localhost:8080")}
客户端代码
新建jwt_test.go文件, 填充以下代码, 运行go test执行单元测试.
package test_testimport ( "encoding/json" . "github.com/smartystreets/goconvey/convey" //https://github.com/smartystreets/goconvey GoConvey是Golang的测试框架,集成go test, 支持终端和浏览器模式. "io/ioutil" "log" "net/http" "strings" "testing")type User struct { Username string `json:"username"` Password string `json:"password"`}type Response struct { Token string `json:"token"`}func createNewsUser(username, password string) *User { return &User{username, password}}func TestLogin(t *testing.T) { Convey("Should be able to login", t, func() { user := createNewsUser("jonas", "1234") jsondata, _ := json.Marshal(user) userData := strings.NewReader(string(jsondata)) log.Printf("userData:%+v", userData) // 这里模拟用户登录, 实际上后台没有使用用户名和密码, 该接口直接返回内部生成的Token req, _ := http.NewRequest("GET", "http://localhost:8080/api/", userData) req.Header.Set("Content-Type", "application/json") client := &http.Client{} res, _ := client.Do(req) //log.Printf("res:%+v", res) So(res.StatusCode, ShouldEqual, 200) //对响应码进行断言, 期望得到状态码为200 Convey("Should be able to parse body", func() { //解析响应体 body, err := ioutil.ReadAll(res.Body) defer res.Body.Close() So(err, ShouldBeNil) Convey("Should be able to get json back", func() { responseData := new(Response) err := json.Unmarshal(body, responseData) So(err, ShouldBeNil) log.Printf("responseData:%s", responseData) Convey("Should be able to be authorized", func() { token := responseData.Token //提取Token log.Printf("token:%s", token) // 构造带Token的请求 req, _ := http.NewRequest("GET", "http://localhost:8080/api/private", nil) req.Header.Set("Authorization", "Bearer "+token) //设置认证头 client = &http.Client{} res, _ := client.Do(req) body, err := ioutil.ReadAll(res.Body) if err != nil { log.Printf("Read body failed, %s", err.Error()) } log.Printf("Body:%s", string(body)) So(res.StatusCode, ShouldEqual, 200) }) }) }) })}
参考文档
gin-gonic/contrib/jwt中间件: https://github.com/gin-gonic/contrib/tree/master/jwt
END已结束
欢迎大家留言, 订阅, 交流哦!
往期回顾
[翻译自官方]什么是RDB和AOF? 一文了解Redis持久化!
Golang GinWeb框架9-编译模板/自定义结构体绑定/http2/操作Cookie/完结
Golang GinWeb框架8-重定向/自定义中间件/认证/HTTPS支持/优雅重启等
Golang GinWeb框架7-静态文件/模板渲染
Golang GinWeb框架6-XML/JSON/YAML/ProtoBuf等渲染
Golang GinWeb框架5-绑定请求字符串/URI/请求头/复选框/表单类型
Golang GinWeb框架4-请求参数绑定和验证
Golang GinWeb框架3-自定义日志格式和输出方式/启禁日志颜色
Golang GinWeb框架2-文件上传/程序panic崩溃后自定义处理方式
Golang GinWeb框架-快速入门/参数解析
Golang与亚马逊对象存储服务AmazonS3快速入门
Golang+Vue实现Websocket全双工通信入门
GolangWeb编程之控制器方法HandlerFunc与中间件Middleware
Golang连接MySQL执行查询并解析-告别结构体
Golang的一种发布订阅模式实现
Golang 并发数据冲突检测器(Data Race Detector)与并发安全
Golang"驱动"MongoDB-快速入门("快码加鞭")