【Golang】Golang + jwt 实现简易用户认证

<p>本文已同步发布到我的个人博客:<a href="https://links.jianshu.com/go?to=https%3A%2F%2Fglorin.xyz%2F2019%2F11%2F23%2FGolang-jwt-simple-auth%2F" target="_blank">https://glorin.xyz/2019/11/23/Golang-jwt-simple-auth/</a></p> <h2>前言</h2> <p>在开发app的时候,难免会需要有后台API,Golang是一门非常适合开发后台服务的高性能的语言。在使用Golang开发后台API的时候,经常需要有用户注册、登录的功能,例如为了保存用户数据、为了给不同用户提供不同服务等。本文便是介绍一种基于jwt的Golang的用户认证系统。</p> <h2>目标</h2> <p>我们的模板是实现三个API接口:</p> <ul> <li>/api/account: 支持Post方法,用来注册一个用户</li> <li>/api/account/accesstoken: 登录功能</li> <li>/api/account/me: 获取用户信息,具有认证功能,如果没有用户认证信息(token),则获取失败,否则返回当前用户的信息。</li> </ul> <h2>工具原料</h2> <ul> <li>Golang + Goland IDE(非必须,VSCode等等也可以)</li> <li>jwt-go:一个go语言的jwt实现</li> <li>github.com/gorilla/mux: go语言的一个路由组件,用来提供http路由服务</li> </ul> <h2>实现步骤</h2> <h3>搭建http服务,提供API</h3> <ol> <li>首先我们在自己的go语言目录下建立项目:golang-jwt-simple-auth(名字可自由决定),golang项目一般遵循golang的约定,放在GOPATH(默认是用户目录下的go文件夹)下面,比如我的项目放在github上,那目录就是</li> </ol> <pre><code class="bash">~/go/src/github.com/glorinli/golang-jwt-simple-auth </code></pre> <ol start="2"> <li>新建main.go问,作为程序的主入口,main.go内容如下:</li> </ol> <pre><code class="go">package main

import ( "fmt" "github.com/glorinli/go-jwt-simple-auth/app" "github.com/glorinli/go-jwt-simple-auth/controllers" "log" "net/http" "os"

"github.com/gorilla/mux"

)

func init() { log.SetPrefix("simple-auth") }

func main() { // 新建路由器 router := mux.NewRouter()

1// 注册jwt认证的中间件 2router.Use(app.JwtAuthentication) 3 4// 注册路由 5router.HandleFunc("/api/account", controllers.CreateUser).Methods(http.MethodPost) 6router.HandleFunc("/api/account/accesstoken", controllers.Login).Methods(http.MethodGet) 7router.HandleFunc("/api/account/me", controllers.Me).Methods(http.MethodGet) 8 9// 获取端口号 10port := os.Getenv("golang-jwt-simple-auth-port") 11if port == "" { 12 port = "8001" 13} 14 15fmt.Println("Port is:", port) 16 17// 开始服务 18err := http.ListenAndServe(":"+port, router) 19if err != nil { 20 fmt.Print("Fail to start server", err) 21}

}

</code></pre>

<p>代码的作用在注释中已经说明了,关于mux库的使用,可以参考 <a href="https://links.jianshu.com/go?to=http%3A%2F%2Fwww.gorillatoolkit.org%2Fpkg%2Fmux" target="_blank">http://www.gorillatoolkit.org/pkg/mux</a>, 这里我们只要知道它起到一个路由器的作用,负责把一个api请求映射到一个方法上。</p> <p>关键就在于</p> <pre><code class="go">router.Use(app.JwtAuthentication) </code></pre> <p>这相当与注册了一个中间件,也可以理解为拦截器,就是说所有的请求都会先经过这个中间件拦截处理,于是我们便可以在里面处理认证相关 的逻辑了,接下来就来说说这个JwtAuthentication。</p> <h3>JWT认证实现</h3> <p>首先还是贴上JwtAuthentication的代码:</p> <pre><code class="go">package app

import ( ... 省略 )

var JwtAuthentication = func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

1 // 只针对这个me接口开启认证 2 needAuthPaths := []string{"/api/account/me"} 3 requestPath := r.URL.Path 4 5 var needAuth = false 6 // 判断是否是需要认证的api,省略 7 8 // 不需要认证,直接走下一步 9 if !needAuth { 10 next.ServeHTTP(w, r) 11 return 12 } 13 14 // 从Header读取Token 15 tokenHeader := r.Header.Get("Authorization") 16 17 // Token is missing 18 if tokenHeader == "" { 19 sendInvalidTokenResponse(w, "Missing auth token") 20 return 21 } 22 23 tk := &amp;models.Token{} 24 25 token, err := jwt.ParseWithClaims(tokenHeader, tk, func(token *jwt.Token) (interface{}, error) { 26 return []byte(os.Getenv("token_password")), nil 27 }) 28 29 fmt.Println("Parse token error:", err) 30 31 if err != nil { 32 sendInvalidTokenResponse(w, "Invalid auth token: "+err.Error()) 33 return 34 } 35 36 // Token is invalid 37 if !token.Valid { 38 sendInvalidTokenResponse(w, "Token is not valid") 39 return 40 } 41 42 // Auth ok 43 fmt.Println("User:", tk.UserId) 44 ctx := context.WithValue(r.Context(), "user", tk.UserId) 45 r = r.WithContext(ctx) 46 next.ServeHTTP(w, r) 47})

}

func sendInvalidTokenResponse(w http.ResponseWriter, message string) { response := u.Message(false, message) w.WriteHeader(http.StatusForbidden) w.Header().Set("Content-Type", "application/json") u.Respond(w, response) }

</code></pre>

<p>这个函数的作用就是解析客户端传递过来的token,将其解析为一个Token对象,Token对象的格式如下:</p> <pre><code class="go">/* JWT claims struct */ type Token struct { UserId uint jwt.StandardClaims } </code></pre> <p>可以看到,除了jwt标准的数据,我们还添加了一个UserId字段,这是为了方便从Token确定用户的id。从这里我们也可以看出,Jwt Token是可以包含额外信息的。</p> <p>关于这个Token是如何生程的,我们下面分析。</p> <h3>注册功能</h3> <p>返回去看main.go,我们发现注册接口被绑定到一个函数上:</p> <pre><code class="go">router.HandleFunc("/api/account", controllers.CreateUser).Methods(http.MethodPost) </code></pre> <p>来看这个CreateUser函数,位于authController.go中:</p> <pre><code class="go">var CreateUser = func(w http.ResponseWriter, r *http.Request) { account := &amp;models.Account{}
1err := json.NewDecoder(r.Body).Decode(account) 2 3if err != nil { 4 utils.Respond(w, utils.Message(false, "Invalid info: "+err.Error())) 5 return 6} 7 8utils.Respond(w, account.Create())

} </code></pre>

<p>最终的实现是在account.Create()函数,位于account.go中:</p> <pre><code class="go">func (account *Account) Create() map[string]interface{} { // 校验 省略
1// 将密码做一个加密 2hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(account.Password), bcrypt.DefaultCost) 3account.Password = string(hashedPassword) 4 5// 创建数据库数据 6err := GetDB().Create(account).Error 7 8// ... 9 10// 创建Token 11tk := &amp;Token{UserId: account.ID} 12token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk) 13tokenString, _ := token.SignedString([]byte(os.Getenv("token_password"))) 14account.Token = tokenString 15 16account.Password = "" 17response := u.MessageWithData(true, "Account has been created", account) 18return response

} </code></pre>

<p>关键就在于创建Token这一步,我们调用jwt.NewWithClaims来生成Token,参数有两个,第一个是签名方法,采用HS256,第二个就是一个Token对象,这个对象即将被编码到Token中,这也是我们在进行认证的时候,从Token解析出来的对象。</p> <h3>登录功能</h3> <p>登录功能与注册功能类似,只是把创建数据改为校验用户名密码。</p> <h2>运行部署</h2> <p>我们可以直接在Goland中运行程序,默认会运行在8081端口,然后便可以用Postman或者curl调试相应接口,这部分内容请读者自行研究。</p> <p>注:笔者在mac os 10.15上,发现编译时需要添加-ldflags "-w"参数,否则会运行失败。</p> <h2>小结</h2> <p>本文介绍了如何使用Golang + jwt构建一个建议的认证系统,可以让大家对用户认证有一个基本的概念,详细的代码也已经同步到github上,读者们可以clone下来参考:<a href="https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fglorinli%2Fgo-jwt-simple-auth" target="_blank">https://github.com/glorinli/go-jwt-simple-auth</a></p>
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )