优雅的基于角色的JWT安全性在Go服务器中

huangapple go评论120阅读模式
英文:

Elegant role-based JWT security in Go server

问题

我正在编写一个使用JWT进行令牌/身份验证的Go Web API,并且我想知道是否有一种更优雅的方式来为我的路由处理程序提供不同级别的访问权限,而不是使用以下方法?

在我的真实代码中(只有大约18个路由),我创建了一个Routes结构体,为API提供了所有路由的处理程序。在其中,我正在执行上述的switch逻辑到专门的Route结构体,这样可以确保只有管理员用户可以执行特定的功能,而其他功能可以由多种类型的用户调用,只是具有不同的行为。

我尝试使用基于中间件的身份验证,如下所示:

func roleCheckerMiddleware(roles ...byte) gin.HandlerFunc {
	return func(c *gin.Context) {
		contextRole := getRoleFromContext(c)
		for _, role := range roles {
			if contextRole == role {
				c.Next()
				return
			}
		}

		c.AbortWithStatus(http.StatusForbidden)
	}
}

并将其应用如下:

adminGroup := group.Group("/")
adminGroup.GET("/hello", helloAdmin)
adminGroup.Use(roleCheckerMiddleware(1))

managerGroup := group.Group("/")
managerGroup.GET("/hello", helloManager)
managerGroup.Use(roleCheckerMiddleware(2, 1))

employeeGroup := group.Group("/")
employeeGroup.GET("/hello", helloEmployee)
employeeGroup.Use(roleCheckerMiddleware(3, 2, 1))

但是,明显的/hello路由的重复意味着,如果没有引入/admin、/manager和/employee的分组,这种方法是不可能的(就我所知)。不过,我很愿意看到其他更优雅的方法,因为这种方法比我的switch逻辑更优雅!

英文:

I'm writing a Go web API which uses JWT for tokens/auth etc. and I was wondering if there was a more elegant way of providing varying levels of access to my route handlers than the following?

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	jwt "gopkg.in/appleboy/gin-jwt.v2"
)

var (
	users = map[string]byte{
		"admin":    1,
		"manager":  2,
		"employee": 3,
	}
)

func main() {
	router := gin.Default()
	group := router.Group("/v1")

	jwtMiddleware := &jwt.GinJWTMiddleware{
		Realm:         "realm",
		Key:           []byte("password"),
		Authenticator: authenticate,
		PayloadFunc:   payload,
	}

	group.Use(jwtMiddleware.MiddlewareFunc())
	group.GET("/refreshToken", jwtMiddleware.RefreshHandler)
	group.GET("/hello", hello)

	router.POST("/login", jwtMiddleware.LoginHandler)
	router.Run(":1234")
}

func hello(c *gin.Context) {
	switch getRoleFromContext(c) {
	case 1:
		helloAdmin(c)
	case 2:
		helloManager(c)
	case 3:
		helloEmployee(c)
	default:
		c.AbortWithStatus(http.StatusForbidden)
	}
}

func helloAdmin(c *gin.Context)    { c.String(http.StatusOK, "Hello, admin!") }
func helloManager(c *gin.Context)  { c.String(http.StatusOK, "Hello, manager!") }
func helloEmployee(c *gin.Context) { c.String(http.StatusOK, "Hello, employee!") }

func authenticate(email string, password string, c *gin.Context) (string, bool) {
	if _, ok := users[email]; ok {
		return email, true
	}

	return "", false
}

func payload(email string) map[string]interface{} {
	return map[string]interface{}{
		"role": users[email],
	}
}

func getRoleFromContext(c *gin.Context) (role byte) {
	claims := jwt.ExtractClaims(c)

	rawRole, ok := claims["role"]
	if !ok {
		c.AbortWithStatus(http.StatusForbidden)
	}

	floatRole, ok := rawRole.(float64)
	if !ok {
		c.AbortWithStatus(http.StatusForbidden)
	}

	return byte(floatRole)
}

In my real code (consisting of only 18 or so routes), I've created a Routes struct, which provides handlers for all of the routes my API provides. Within that, I'm performing the above switching logic to specialised Route structs, which ensures only administrative users can perform specific functions, while other functions are callable by multiple types of users, just with different behaviour.

I've tried throwing together middleware-based auth as follows:

func roleCheckerMiddleware(roles ...byte) gin.HandlerFunc {
	return func(c *gin.Context) {
		contextRole := getRoleFromContext(c)
		for _, role := range roles {
			if contextRole == role {
				c.Next()
				return
			}
		}

		c.AbortWithStatus(http.StatusForbidden)
	}
}

...and applying it as follows:

adminGroup := group.Group("/")
adminGroup.GET("/hello", helloAdmin)
adminGroup.Use(roleCheckerMiddleware(1))

managerGroup := group.Group("/")
managerGroup.GET("/hello", helloManager)
managerGroup.Use(roleCheckerMiddleware(2, 1))

employeeGroup := group.Group("/")
employeeGroup.GET("/hello", helloEmployee)
employeeGroup.Use(roleCheckerMiddleware(3, 2, 1))

...but the obvious duplication of the /hello route means this method isn't (as far as I'm aware) possible, without the introduction of groupings for /admin, /manager and /employee. I'd love to be shown otherwise though as this feels more elegant that my switching logic!

答案1

得分: 8

我希望不会太晚,这是我的回答。
Casbin很好地解决了这个问题。
此外,可以在zupzup.orghttps://doc.xuwenliang.com/docs/casbin/188找到一个非常简单的教程,解释了基础知识。

我发现使用JWT和/或基于会话/cookie的身份验证机制与它一起工作也非常简单。希望这可以帮到你。

英文:

I find working with it using JWT and/or Session/Cookie based authentication mechanisms also very simple as. I hope this helps.

答案2

得分: 3

只需使用github.com/casbin/casbin来提高您的授权和身份验证中间件的准确性,因为它对我也帮助很大。

如果需要更详细的使用示例,请使用https://zupzup.org/casbin-http-role-auth/

英文:

Just Use github.com/casbin/casbin for a better precision in ur authorization and authentication middleware because it helped me a lot too.

And for more explanatory example of to use it; Use https://zupzup.org/casbin-http-role-auth/

huangapple
  • 本文由 发表于 2016年11月18日 22:05:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/40679073.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定