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

待兔 等级 1179 0 0

本文已同步发布到我的个人博客:https://glorin.xyz/2019/11/23/Golang-jwt-simple-auth/

前言

在开发app的时候,难免会需要有后台API,Golang是一门非常适合开发后台服务的高性能的语言。在使用Golang开发后台API的时候,经常需要有用户注册、登录的功能,例如为了保存用户数据、为了给不同用户提供不同服务等。本文便是介绍一种基于jwt的Golang的用户认证系统。

目标

我们的模板是实现三个API接口:

  • /api/account: 支持Post方法,用来注册一个用户
  • /api/account/accesstoken: 登录功能
  • /api/account/me: 获取用户信息,具有认证功能,如果没有用户认证信息(token),则获取失败,否则返回当前用户的信息。

工具原料

  • Golang + Goland IDE(非必须,VSCode等等也可以)
  • jwt-go:一个go语言的jwt实现
  • github.com/gorilla/mux: go语言的一个路由组件,用来提供http路由服务

实现步骤

搭建http服务,提供API

  1. 首先我们在自己的go语言目录下建立项目:golang-jwt-simple-auth(名字可自由决定),golang项目一般遵循golang的约定,放在GOPATH(默认是用户目录下的go文件夹)下面,比如我的项目放在github上,那目录就是
~/go/src/github.com/glorinli/golang-jwt-simple-auth
  1. 新建main.go问,作为程序的主入口,main.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()

// 注册jwt认证的中间件
router.Use(app.JwtAuthentication)

// 注册路由
router.HandleFunc("/api/account", controllers.CreateUser).Methods(http.MethodPost)
router.HandleFunc("/api/account/accesstoken", controllers.Login).Methods(http.MethodGet)
router.HandleFunc("/api/account/me", controllers.Me).Methods(http.MethodGet)

// 获取端口号
port := os.Getenv("golang-jwt-simple-auth-port")
if port == "" {
    port = "8001"
}

fmt.Println("Port is:", port)

// 开始服务
err := http.ListenAndServe(":"+port, router)
if err != nil {
    fmt.Print("Fail to start server", err)
}

}

代码的作用在注释中已经说明了,关于mux库的使用,可以参考 http://www.gorillatoolkit.org/pkg/mux, 这里我们只要知道它起到一个路由器的作用,负责把一个api请求映射到一个方法上。

关键就在于

router.Use(app.JwtAuthentication)

这相当与注册了一个中间件,也可以理解为拦截器,就是说所有的请求都会先经过这个中间件拦截处理,于是我们便可以在里面处理认证相关 的逻辑了,接下来就来说说这个JwtAuthentication。

JWT认证实现

首先还是贴上JwtAuthentication的代码:

package app

import ( ... 省略 )

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

    // 只针对这个me接口开启认证
    needAuthPaths := []string{"/api/account/me"}
    requestPath := r.URL.Path

    var needAuth = false
    // 判断是否是需要认证的api,省略

    // 不需要认证,直接走下一步
    if !needAuth {
        next.ServeHTTP(w, r)
        return
    }

    // 从Header读取Token
    tokenHeader := r.Header.Get("Authorization")

    // Token is missing
    if tokenHeader == "" {
        sendInvalidTokenResponse(w, "Missing auth token")
        return
    }

    tk := &models.Token{}

    token, err := jwt.ParseWithClaims(tokenHeader, tk, func(token *jwt.Token) (interface{}, error) {
        return []byte(os.Getenv("token_password")), nil
    })

    fmt.Println("Parse token error:", err)

    if err != nil {
        sendInvalidTokenResponse(w, "Invalid auth token: "+err.Error())
        return
    }

    // Token is invalid
    if !token.Valid {
        sendInvalidTokenResponse(w, "Token is not valid")
        return
    }

    // Auth ok
    fmt.Println("User:", tk.UserId)
    ctx := context.WithValue(r.Context(), "user", tk.UserId)
    r = r.WithContext(ctx)
    next.ServeHTTP(w, r)
})

}

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) }

这个函数的作用就是解析客户端传递过来的token,将其解析为一个Token对象,Token对象的格式如下:

/*
JWT claims struct
*/
type Token struct {
    UserId uint
    jwt.StandardClaims
}

可以看到,除了jwt标准的数据,我们还添加了一个UserId字段,这是为了方便从Token确定用户的id。从这里我们也可以看出,Jwt Token是可以包含额外信息的。

关于这个Token是如何生程的,我们下面分析。

注册功能

返回去看main.go,我们发现注册接口被绑定到一个函数上:

router.HandleFunc("/api/account", controllers.CreateUser).Methods(http.MethodPost)

来看这个CreateUser函数,位于authController.go中:

var CreateUser = func(w http.ResponseWriter, r *http.Request) {
    account := &models.Account{}

err := json.NewDecoder(r.Body).Decode(account)

if err != nil {
    utils.Respond(w, utils.Message(false, "Invalid info: "+err.Error()))
    return
}

utils.Respond(w, account.Create())

}

最终的实现是在account.Create()函数,位于account.go中:

func (account *Account) Create() map[string]interface{} {
    // 校验 省略

// 将密码做一个加密
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(account.Password), bcrypt.DefaultCost)
account.Password = string(hashedPassword)

// 创建数据库数据
err := GetDB().Create(account).Error

// ...

// 创建Token
tk := &Token{UserId: account.ID}
token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)
tokenString, _ := token.SignedString([]byte(os.Getenv("token_password")))
account.Token = tokenString

account.Password = ""
response := u.MessageWithData(true, "Account has been created", account)
return response

}

关键就在于创建Token这一步,我们调用jwt.NewWithClaims来生成Token,参数有两个,第一个是签名方法,采用HS256,第二个就是一个Token对象,这个对象即将被编码到Token中,这也是我们在进行认证的时候,从Token解析出来的对象。

登录功能

登录功能与注册功能类似,只是把创建数据改为校验用户名密码。

运行部署

我们可以直接在Goland中运行程序,默认会运行在8081端口,然后便可以用Postman或者curl调试相应接口,这部分内容请读者自行研究。

注:笔者在mac os 10.15上,发现编译时需要添加-ldflags "-w"参数,否则会运行失败。

小结

本文介绍了如何使用Golang + jwt构建一个建议的认证系统,可以让大家对用户认证有一个基本的概念,详细的代码也已经同步到github上,读者们可以clone下来参考:https://github.com/glorinli/go-jwt-simple-auth

收藏
评论区

相关推荐

关于Golang的那些事(一) -- Node.js和Golang对比
之前一直用Node.js作为开发语言,用了差不多4年的Node.js,涉及前端和后端,最近看到Golang这个新兴之秀挺火的,于是想探究探究一下这门语言,对比了一下他们的Github repo,截止现在Node.js的repo有72.5K星, issue数量是859个,Golang的repo有75.7K星,issue数量是5K个。从趋势来看,Golang来势
【Golang】Golang + jwt 实现简易用户认证
<p本文已同步发布到我的个人博客:<a href"https://links.jianshu.com/go?tohttps%3A%2F%2Fglorin.xyz%2F2019%2F11%2F23%2FGolangjwtsimpleauth%2F" target"_blank"https://glorin.xyz/2019/11/23/Golang
Mac安装Golang和vscode
Mac第一次安装golang和vscode一起使用,遇到了不少的坑,下面介绍一下正确的安装方式。 1、使用brew安装Golang 如果不知道brew是什么,或怎么安装请看这里 brew官网(https://brew.sh/index_zhcn) brew install golang 安装完成后可以使用
golang 分析调试高阶技巧
layout: post title: “golang 调试高阶技巧” date: 2020603 1:44:09 0800 categories: golang GC 垃圾回收 golang 高阶调试 Golang tools nm compile
Go WEB入门
摘要 由于Golang优秀的并发处理,很多公司使用Golang编写微服务。对于Golang来说,只需要短短几行代码就可以实现一个简单的Http服务器。加上Golang的协程,这个服务器可以拥有极高的性能。然而,正是因为代码过于简单,我们才应该去研究他的底层实现,做到会用,也知道为什么这么用。 在本文中,会以自顶向下的方式,从如何使用,到如何实现,一点点的分
Go语言入门系列(一)之Go的安装和使用
1.安装环境 ====== 1. 进入[Golang官网](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgolang.org),进入下载页面。 (如果打不开可访问[Golang中国](https://www.oschina.net/action/GoToLink?u
Archlinux下Visual Studio Code配置Golang开发环境
一、Golang的安装 ----------- GoLang安装并验证一下: [cox@localhost ~]$ sudo pacman -S go [cox@localhost ~]$ go version go version go1.8.3 linux/amd64s 要注意,Golang的安装要确保两个环境变量,一个是G
Golang Gin实践 番外 请入门 Makefile
<h1>Golang Gin实践 番外 请入门 Makefile</h1> <p>原文地址:<a href="https://github.com/EDDYCJY/blog/blob/master/golang/gin/2018-08-26-Gin%E5%AE%9E%E8%B7%B5-%E7%95%AA%E5%A4%96-%E8%AF%B7%E5%85%A5
Golang In PingCAP
随着 Golang 在后端领域越来越流行,有越来越多的公司选择 Golang 作为主力开发语言。本次 GopherChina Beijing 2016 大会上,看到 Golang 在各家公司从人工智能到自动运维,从 Web 应用到基础架构都发挥着越来越多的作用。可以说 Golang 在这几年间,获得了长足的进步。 PingCAP 是一家由几名 Go
Golang modules 初探
今天天色刚刚亮起,起床看到golang 1.11正式发版了,有着两个重要的特性:modules和WebAssembly。 本博文只要说的是modules,从Java转golang的同学肯定是对golang的包管理充满了无奈之情,我也曾在博客中介绍过[glide](https://my.oschina.net/u/553243/blog/1475626),也
Golang 内存管理源码剖析
Golang 的内存管理基于 tcmalloc,可以说起点挺高的。但是 Golang 在实现的时候还做了很多优化,我们下面通过源码来看一下 Golang 的内存管理实现。下面的源码分析基于 go1.8rc3。 1.tcmalloc 介绍 ------------- 关于 tcmalloc 可以参考这篇文章 [tcmalloc 介绍](https://ww
Golang 开发环境搭建
Golang 是 Google 发布的开发语言,Go 编译的程序速度可以媲美 C/C++。 安装 -- sudo apt-get install golang sudo apt-get install golang-go.tools 使用 -- * 编译运行程序 go run main.go * 查看命令文
Golang 文章正文抽取(readability)
readability for golang Golang版本是根据[readabiliity for node.js](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2Fluin%2Freadability)以及[readability for python](h
Golang依赖管理工具:glide从入门到精通使用
介绍 -- 不论是开发Java还是你正在学习的Golang,都会遇到**依赖管理**问题。Java有牛逼轰轰的Maven和Gradle。 Golang亦有godep、govendor、glide、gvt、gopack等等,本文主要给大家介绍[gilde](https://www.oschina.net/action/GoToLink?url=https%3
Sentinel
![9.28头图.png](https://ucc.alicdn.com/pic/developer-ecology/af7ab6c27c3c4c3aa5dc2cce3c9e8ab9.png) > \*\*导读:\*\*2020年,Sentinel 推出 Go 原生版本[Sentinel-Golang](https://www.oschina.net/ac