add rbac
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
[App]
|
[App]
|
||||||
Mode = "debug"
|
Mode = "debug" # develop, production
|
||||||
|
|
||||||
[Captcha]
|
[Captcha]
|
||||||
KeyLong = 6
|
KeyLong = 6
|
||||||
|
|||||||
41
middleware/jwt.go
Normal file
41
middleware/jwt.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"atom/providers/jwt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rogeecn/gen"
|
||||||
|
)
|
||||||
|
|
||||||
|
func JWTAuth(jwt *jwt.JWT) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
|
||||||
|
token := c.Request.Header.Get("Authorization")
|
||||||
|
if token == "" {
|
||||||
|
gen.NewBusError(http.StatusBadRequest, http.StatusBadRequest, "未登录或非法访问").JSON(c, false)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseToken 解析token包含的信息
|
||||||
|
claims, err := jwt.ParseToken(token)
|
||||||
|
if err != nil {
|
||||||
|
gen.NewBusError(http.StatusBadRequest, http.StatusBadRequest, err.Error()).JSON(c, false)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已登录用户被管理员禁用 需要使该用户的jwt失效 此处比较消耗性能 如果需要 请自行打开
|
||||||
|
// 用户被删除的逻辑 需要优化 此处比较消耗性能 如果需要 请自行打开
|
||||||
|
|
||||||
|
//if user, err := userService.FindUserByUuid(claims.UUID.String()); err != nil || user.Enable == 2 {
|
||||||
|
// _ = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: token})
|
||||||
|
// response.FailWithDetailed(gin.H{"reload": true}, err.Error(), c)
|
||||||
|
// c.Abort()
|
||||||
|
//}
|
||||||
|
|
||||||
|
c.Set("claims", claims)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
44
middleware/rbac.go
Normal file
44
middleware/rbac.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"atom/providers/config"
|
||||||
|
"atom/providers/jwt"
|
||||||
|
"atom/providers/rbac"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rogeecn/gen"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Permission 拦截器
|
||||||
|
func CheckPermission(config *config.Config, rbac rbac.IRbac, jwt *jwt.JWT) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if config.App.Mode != "production" {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claim, err := jwt.GetClaims(c)
|
||||||
|
if err != nil {
|
||||||
|
gen.NewBusError(http.StatusBadRequest, http.StatusBadRequest, "Token 获取失败").JSON(c, false)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取请求的PATH
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
|
||||||
|
// 获取请求方法
|
||||||
|
method := c.Request.Method
|
||||||
|
|
||||||
|
// 获取用户的角色
|
||||||
|
role := strconv.Itoa(int(claim.RoleID))
|
||||||
|
|
||||||
|
if rbac.Can(role, method, path) == false {
|
||||||
|
gen.NewBusError(http.StatusForbidden, http.StatusForbidden, "未登录或非法访问").JSON(c, false)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"atom/providers/config"
|
"atom/providers/config"
|
||||||
"atom/providers/log"
|
"atom/providers/log"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -26,6 +27,8 @@ type CustomClaims struct {
|
|||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TOKEN_PREFIX = "Bearer "
|
||||||
|
|
||||||
type BaseClaims struct {
|
type BaseClaims struct {
|
||||||
UUID string
|
UUID string
|
||||||
UserID uint64
|
UserID uint64
|
||||||
@@ -85,6 +88,7 @@ func (j *JWT) CreateTokenByOldToken(oldToken string, claims CustomClaims) (strin
|
|||||||
|
|
||||||
// 解析 token
|
// 解析 token
|
||||||
func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) {
|
func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) {
|
||||||
|
tokenString = strings.TrimPrefix(tokenString, TOKEN_PREFIX)
|
||||||
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
|
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
|
||||||
return j.SigningKey, nil
|
return j.SigningKey, nil
|
||||||
})
|
})
|
||||||
@@ -114,10 +118,10 @@ func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j *JWT) GetClaims(c *gin.Context) (*CustomClaims, error) {
|
func (j *JWT) GetClaims(c *gin.Context) (*CustomClaims, error) {
|
||||||
token := c.Request.Header.Get("x-token")
|
token := c.Request.Header.Get("Authorization")
|
||||||
claims, err := j.ParseToken(token)
|
claims, err := j.ParseToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("从Gin的Context中获取从jwt解析信息失败, 请检查请求头是否存在x-token且claims是否为规定结构")
|
log.Error("从Gin的Context中获取从jwt解析信息失败, 请检查请求头是否存在 Authorization 且 Claims 为规定结构")
|
||||||
}
|
}
|
||||||
return claims, err
|
return claims, err
|
||||||
}
|
}
|
||||||
|
|||||||
135
providers/rbac/casbin.go
Normal file
135
providers/rbac/casbin.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package rbac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"atom/container"
|
||||||
|
"atom/database/query"
|
||||||
|
"atom/providers/log"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/casbin/casbin/v2"
|
||||||
|
"github.com/casbin/casbin/v2/model"
|
||||||
|
gormadapter "github.com/casbin/gorm-adapter/v3"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := container.Container.Provide(NewCasbin); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Casbin struct {
|
||||||
|
query *query.Query
|
||||||
|
enforcer *casbin.CachedEnforcer
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasbinInfo struct {
|
||||||
|
Path string `json:"path"` // 路径
|
||||||
|
Method string `json:"method"` // 方法
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCasbin(
|
||||||
|
query *query.Query,
|
||||||
|
db *gorm.DB,
|
||||||
|
) (IRbac, error) {
|
||||||
|
cb := &Casbin{
|
||||||
|
query: query,
|
||||||
|
}
|
||||||
|
|
||||||
|
a, _ := gormadapter.NewAdapterByDB(db)
|
||||||
|
text := `
|
||||||
|
[request_definition]
|
||||||
|
r = sub, obj, act
|
||||||
|
|
||||||
|
[policy_definition]
|
||||||
|
p = sub, obj, act
|
||||||
|
|
||||||
|
[role_definition]
|
||||||
|
g = _, _
|
||||||
|
|
||||||
|
[policy_effect]
|
||||||
|
e = some(where (p.eft == allow))
|
||||||
|
|
||||||
|
[matchers]
|
||||||
|
m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act
|
||||||
|
`
|
||||||
|
m, err := model.NewModelFromString(text)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "字符串加载模型失败!")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cb.enforcer, _ = casbin.NewCachedEnforcer(m, a)
|
||||||
|
|
||||||
|
cb.enforcer.SetExpireTime(60 * 60)
|
||||||
|
_ = cb.enforcer.LoadPolicy()
|
||||||
|
return cb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Casbin) Can(role, method, path string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Casbin) Reload() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Casbin) Update(roleID uint, infos []CasbinInfo) error {
|
||||||
|
roleIdStr := strconv.Itoa(int(roleID))
|
||||||
|
cb.Clear(0, roleIdStr)
|
||||||
|
|
||||||
|
rules := [][]string{}
|
||||||
|
for _, v := range infos {
|
||||||
|
rules = append(rules, []string{roleIdStr, v.Path, v.Method})
|
||||||
|
}
|
||||||
|
|
||||||
|
success, _ := cb.enforcer.AddPolicies(rules)
|
||||||
|
if !success {
|
||||||
|
return errors.New("存在相同api,添加失败,请联系管理员")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cb.enforcer.InvalidateCache()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *Casbin) UpdateApi(before, after CasbinInfo) error {
|
||||||
|
rule := cb.query.CasbinRule
|
||||||
|
|
||||||
|
_, err := rule.WithContext(context.Background()).
|
||||||
|
Where(rule.V1.Eq(before.Path)).
|
||||||
|
Where(rule.V1.Eq(before.Method)).
|
||||||
|
UpdateSimple(
|
||||||
|
rule.V1.Value(after.Path),
|
||||||
|
rule.V2.Value(after.Method),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb.enforcer.InvalidateCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取权限列表
|
||||||
|
func (cb *Casbin) GetPolicyPathByRoleID(roleID uint) (pathMaps []CasbinInfo) {
|
||||||
|
roleIdStr := strconv.Itoa(int(roleID))
|
||||||
|
list := cb.enforcer.GetFilteredPolicy(0, roleIdStr)
|
||||||
|
for _, v := range list {
|
||||||
|
pathMaps = append(pathMaps, CasbinInfo{
|
||||||
|
Path: v[1],
|
||||||
|
Method: v[2],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return pathMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除匹配的权限
|
||||||
|
func (cb *Casbin) Clear(v int, p ...string) bool {
|
||||||
|
success, _ := cb.enforcer.RemoveFilteredPolicy(v, p...)
|
||||||
|
return success
|
||||||
|
}
|
||||||
6
providers/rbac/rbac.go
Normal file
6
providers/rbac/rbac.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package rbac
|
||||||
|
|
||||||
|
type IRbac interface {
|
||||||
|
Can(role, method, path string) bool
|
||||||
|
Reload() error
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user