gin 框架基于中间件的身份验证和权限验证

本模块创作基于gin 框架路由匹配和Django 的身份验证和权限验证创作的,目前主要实现了身份验证,基于路由配置自动匹配需要验证的路由,另外可以通过配置实现不同路由匹配不同身份验证方式 .
git地址: https://github.com/xxxxxxming/authtest
后续有时间持续更新该模块
模块主要由三个文件组成,分别是路由处理,身份认证,中间件拦截.  
代码如下:

1. 路由处理,包含路由数创建和路由解析
package utils

import (
	"bytes"
	"strings"
)

const (
	static nodeType = iota // default
	root
	param
	query
)

type nodeType uint8

type node struct {
	// 节点路径,比如上面的s,earch,和upport
	path string
	// 儿子节点
	children []*node
	nType    nodeType
	// 完整路径
	tokenAuth       string // token 校验
	permissionsAuth string // 权限校验
}

type methodTree struct {
	method string
	root   *node
}

type Engine struct {
	trees methodTrees
}

type methodTrees []methodTree

// 通过mothod 去获取该mothod 的路由树
func (trees methodTrees) get(method string) *node {
	for _, tree := range trees {
		if tree.method == method {
			return tree.root
		}
	}
	return nil
}

func assert1(guard bool, text string) {
	if !guard {
		panic(text)
	}
}

// 构建路由树
func (n *node) addRoute(path, tokenAuth, permissionsAuth string) {
	pathList := strings.Split(path, "/") // 获取url
	s := []byte(path)
	countSlash := uint16(bytes.Count(s, []byte("/"))) // 获取'/'的个数
	if countSlash == 0 {
		return
	}
	// 如果路由中只有一个'/',那么就直接赋值给根路径
	if countSlash == 1 && len(pathList) == 0 {
		n.nType = root
		n.path = "/"
		n.tokenAuth = tokenAuth
		n.permissionsAuth = permissionsAuth
	} else {
		// 构建子路由树
		n.insertChild(path, tokenAuth, permissionsAuth)
	}

}

// 获取url的列表,将位置参数和关键字参数都添加到列表中
func getUrlList(path string) []string {
	pathList := strings.Split(path, "/") // 获取url
	list := []string{}
	for _, p := range pathList { // 重新构造一个列表,其中包含位置参数和关键字参数
		if p == "" {
			continue
		}
		// 判断路径里面是否存在 关键字参数
		index := bytes.IndexByte([]byte(p), '?')
		if index == -1 {
			list = append(list, p)
		} else {
			// 将关键字参数作为一层子树放进列表中
			list = append(list, p[:index], p[index:])
		}
	}
	return list
}

func (n *node) insertChild(path, tokenAuth, permissionsAuth string) {
	list := getUrlList(path)
	head := n // 指向头节点的children

	llen := len(list)
	// 遍历路由列表和每层路由树
	for index1, l := range list {
		findflag := false
		// 开始遍历子树,从跟路由的子树开始遍历
		for index2, n1 := range head.children {
			// 当前子树中存在该路由
			if n1.path == l {
				if llen == index1+1 { // 遍历到了最后一个路径,将tokenAuth和permissionsAuth 赋值给该节点的tokenAuth和permissionsAuth
					n1.tokenAuth = tokenAuth
					n1.permissionsAuth = permissionsAuth
				}
				head = head.children[index2] // 指向当前节点的下一个children,继续遍历
				findflag = true
				break
			}
		}
		if findflag {
			// 表示子树中存在路由,且路由还没有匹配完
			continue
		}
		// 该层子树中没有该路径,那么就添加该路径到该层子树下面,并且以新建的子树创建下层子树
		var nType nodeType
		// 识别当前是什么类型的节点
		if l[0] != ':' && l[0] != '?' {
			nType = static
		} else if l[0] == ':' {
			nType = param
		} else if l[0] == '?' {
			nType = query
			l = l[1:] // 去掉字符串中的?
		}
		if llen == index1+1 { // 遍历到了最后一个路径,将tokenAuth和permissionsAuth 赋值给该节点的tokenAuth和permissionsAuth
			head.children = append(head.children, &node{path: l, nType: nType, tokenAuth: tokenAuth, permissionsAuth: permissionsAuth})
		} else {
			head.children = append(head.children, &node{path: l, nType: nType})
		}
		hlen := len(head.children)
		head = head.children[hlen-1] // 指向当前节点的下一个children,创建下层子树
	}
}

// 构建路由树
func (engine *Engine) addRoute(method, path, tokenAuth, permissionsAuth string) {
	assert1(path[0] == '/', "path must begin with '/'")
	assert1(method != "", "HTTP method can not be empty")

	root := engine.trees.get(method)
	if root == nil {
		root = new(node)
		root.path = "/"
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
	}
	root.addRoute(path, tokenAuth, permissionsAuth)
}

func (engine *Engine) post(relativePath, tokenHandle, persissionHandle string) {
	engine.addRoute("post", relativePath, tokenHandle, persissionHandle)
}

func (engine *Engine) get(relativePath, tokenHandle, persissionHandle string) {
	engine.addRoute("get", relativePath, tokenHandle, persissionHandle)
}

func (engine *Engine) delete(relativePath, tokenHandle, persissionHandle string) {
	engine.addRoute("delete", relativePath, tokenHandle, persissionHandle)
}

func (engine *Engine) patch(relativePath, tokenHandle, persissionHandle string) {
	engine.addRoute("patch", relativePath, tokenHandle, persissionHandle)
}

func (engine *Engine) update(relativePath, tokenHandle, persissionHandle string) {
	engine.addRoute("update", relativePath, tokenHandle, persissionHandle)
}

/*
解析路由树
url: 解析的路径, method: 请求方法, querys: 关键字参数
return  tokenauth,验证身份的函数,为空则表示不需要验证
*/
func (engine *Engine) ParseUrlTree(url, method, querys string) string {
	// 是否存在该方法的路由树
	root := engine.trees.get(method)
	if root == nil {
		return ""
	}
	if url == "/" {
		if root.path == url {
			return root.tokenAuth
		}
		return ""
	}
	// 构建路由的队列
	list := getUrlList(url)
	if querys != "" {
		list = append(list, querys)
	}
	llen := len(list)
	for index1, p := range list {
		compareFlag := false
		index3 := 0
		// 从根路径的子树开始遍历匹配
		for index2, p1 := range root.children {
			index3++
			// 如果类型是关键字参数类型,则比较路由树里面的参数是否与路由列表的参数匹配
			if p1.nType == query {
				qflag := true
				for _, q := range joinUrlQuery(p1.path) {
					if !strings.Contains(p, q) {
						qflag = false
						break
					}
				}
				if qflag { // 如果query参数全部包含则表示匹配成功
					compareFlag = true
				}
			} else {
				if p1.path == p {
					compareFlag = true
				}
			}
			// 匹配到路由了判断是否是最后一个字段,是的话,返回验证token 的函数字符串
			if compareFlag {
				if llen == index1+1 {
					return p1.tokenAuth
				}
				root = root.children[index2]
				break
			}
		}
		if index3 == len(root.children) && !compareFlag { // 当前child全部遍历都没有找到相匹配的节点就直接退出去
			break
		}
	}
	return ""
}

func joinUrlQuery(urlQuery string) []string {
	byteUrlQuery := []byte(urlQuery)
	flag := false
	byteQuey := []byte{}
	for _, b := range byteUrlQuery {
		if b == '=' {
			flag = true
		} else if b == '&' {
			flag = false
		}
		if flag {
			continue
		}
		byteQuey = append(byteQuey, b)
	}
	return strings.Split(string(byteQuey), "&")
}

var Root Engine

// 初始化路由树
func InitTree() {
	// 参数1表示路由,路由规则:如果存在位置参数(param),那么与注册路由时写法保持一致;如果存在query,那么接'?'和query名,有多个则用'&'拼接.
	// 参数2表示的是需要使用哪种身份验证函数,会自动根据你填写的字符串去匹配验证方法
	// 参数3目前还没有写,本意是打算做权限验证
	Root.get("/test/:id", "tokenAuth1", "")
	Root.get("/test/t1/:id", "tokenAuth1", "")
	Root.get("/test/t2", "", "")
	Root.get("/test/t/:id1?name&pwd", "tokenAuth1", "")
	Root.get("/test/t2/:id1/:id2", "", "")
	Root.get("/test", "", "")

	Root.post("/test/:id", "", "")
	Root.post("/test/t1/:id", "", "")
	Root.post("/test/t2", "", "")
	Root.post("/test/t/:id1", "", "")
	Root.post("/test/t2/:id1/:id2", "", "")
	Root.post("/test", "", "")
}

2. 身份认证.基于jwt库创建的模块包含token生成,解析,以及根据字符串匹配身份验证函数的函数
package utils

import (
	"errors"
	"time"

	"github.com/dgrijalva/jwt-go"
)

const (
	SECRETKEY = "243223ffslsfsldf" //私钥
)

type MyClaims struct {
	Uid int `json:"uid"`
	Rid int `json:"rid"`
	jwt.StandardClaims
}

func CreateToken(uid int, rid int) (string, error) {
	maxAge := 60 * 100 // 定义失效时间
	var claims = MyClaims{
		uid,
		rid,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Duration(maxAge) * time.Second).Unix(), // 过期时间
			Issuer:    "yangjia",                                                  // 签发人
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString([]byte(SECRETKEY))
	if err != nil {
		return "", err
	}
	return tokenString, nil
}

//解析token
func ParseToken(tokenString string) (*MyClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) { // 解析token
		return []byte(SECRETKEY), nil
	})
	if err != nil {
		return nil, err
	}
	if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { // 校验token
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

func ParseToken2(tokenString string) (*MyClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) { // 解析token
		return []byte(SECRETKEY), nil
	})
	if err != nil {
		return nil, err
	}
	if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { // 校验token
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

type tokenAuth interface {
	TokenAuth(string) (*MyClaims, error)
}

type PermissionAuth interface {
	permissionAuth(string) bool
}

type tokenAuthHadle func(string) (*MyClaims, error)
type permissionAuthHadle func(string) bool

func (t tokenAuthHadle) TokenAuth(token string) (*MyClaims, error) {
	return t(token)
}

func (t permissionAuthHadle) permissionAuth(permission string) bool {
	return t(permission)
}

// 身份验证方式1
func tokenAuth1(token string) (*MyClaims, error) {
	return ParseToken(token)
}

// 身份验证方式2
func tokenAuth2(token string) (*MyClaims, error) {
	return ParseToken2(token)
}

func permissionAuth(string) bool {
	return true
}

var AuthMap = make(map[string]tokenAuth)

// 将匹配规则添加到map 中, 可以根据 AuthMap["auth"] 去获取对应的解析方法,具体件认证中间件
// 权限验证的还没有写
func InitAuth() {
	AuthMap["tokenAuth1"] = tokenAuthHadle(tokenAuth1)
	AuthMap["tokenAuth2"] = tokenAuthHadle(tokenAuth2)
}

3. 中间件拦截
package middlewares

import (
	"main/utils"
	"strings"

	"github.com/gin-gonic/gin"
)

func Authorization() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := strings.ToLower(c.Request.Method)
		// path := c.Request.URL.Path
		parmas := c.Request.Header
		var code uint
		var msg string
		var errflag = false
		// 将 fullpath,method和query传入解析树函数中
		str := utils.Root.ParseUrlTree(c.FullPath(), method, c.Request.URL.RawQuery)
		if str != "" {
			// 获取需要验证的函数对应的字符串
			tokenHandle, b := utils.AuthMap[str]
			if b {
				token := parmas["Authorization"]
				if token == nil {
					code = utils.ApiCode.Unauthorized
					msg = "缺失Authorization请求头"
					errflag = true
				} else {
					claims, err := tokenHandle.TokenAuth(token[0])
					if err != nil {
						code = utils.ApiCode.Unauthorized
						msg = err.Error()
						errflag = true
					} else if claims == nil {
						code = utils.ApiCode.Unauthorized
						msg = "token校验失败"
						errflag = true
					}
				}

			}
		}
		if errflag == true {
			c.JSON(200, gin.H{
				"code":   code,
				"msg":    msg,
				"result": nil,
			})
			//终止后续接口调用,不加的话recover到异常后,还会继续执行接口里后续代码
			c.Abort()
		}
		c.Next()
	}
}

测试结果:
1. 错误的token:

在这里插入图片描述
2. 没有token
在这里插入图片描述
3. 正确token
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Gin 框架中,使用 `binding` 标签可以对表单输入进行验证。下面是一个 Gin 框架表单输入验证器的使用示例: 1. 导入 gin 包 ``` import "github.com/gin-gonic/gin" ``` 2. 创建一个结构体,定义表单输入 ``` type LoginForm struct { Username string `form:"username" binding:"required"` Password string `form:"password" binding:"required"` } ``` 3. 在路由处理函数中使用验证器 ``` func loginHandler(c *gin.Context) { var form LoginForm if err := c.ShouldBind(&form); err != nil { c.HTML(http.StatusBadRequest, "login.html", gin.H{ "error": err.Error(), }) return } // TODO: 处理登录逻辑 } ``` 4. 创建一个 HTML 模板,定义表单 ``` <!DOCTYPE html> <html> <head> <title>Login</title> </head> <body> {{ if .error }} <p style="color: red;">{{ .error }}</p> {{ end }} <form method="POST" action="/login"> <label for="username">Username:</label> <input type="text" name="username" id="username"><br> <label for="password">Password:</label> <input type="password" name="password" id="password"><br> <input type="submit" value="Login"> </form> </body> </html> ``` 5. 启动服务 ``` func main() { r := gin.Default() r.LoadHTMLGlob("templates/*") r.GET("/login", func(c *gin.Context) { c.HTML(http.StatusOK, "login.html", gin.H{}) }) r.POST("/login", loginHandler) r.Run(":8080") } ``` 这样,当用户提交登录表单时,会先对表单输入进行校验,如果输入不符合要求,则返回错误信息;如果输入符合要求,则继续处理登录逻辑。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值