diff --git a/README.md b/README.md index 7d1654e..5a8fb54 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # OneAuth -统一验证服务 \ No newline at end of file +统一验证服务 + +## 用户验证思路 + + diff --git a/api/api.go b/api/api.go index 5888df8..9a25ec4 100644 --- a/api/api.go +++ b/api/api.go @@ -15,7 +15,7 @@ func Router(r OneBD.Router) { r.SetInternalErrorFunc(func(m core.Meta) { m.Write([]byte("{\"status\": 0}")) }) - user.Router(r.SubRouter("/auth/user")) + user.Router(r.SubRouter("/user")) wx.Router(r.SubRouter("wx")) app.Router(r.SubRouter("app")) //message.Router(r.SubRouter("/message")) diff --git a/api/app/app.go b/api/app/app.go index 0c009b6..4e950be 100644 --- a/api/app/app.go +++ b/api/app/app.go @@ -31,7 +31,7 @@ func (h *appHandler) Get() (interface{}, error) { } h.query = &models.App{} h.query.UUID = id - err := cfg.DB().Where(h.query).Preload("Wx").First(h.query).Error + err := cfg.DB().Where(h.query).First(h.query).Error if err != nil { return nil, err } diff --git a/api/user/user.go b/api/user/user.go index 51ebe37..4115131 100644 --- a/api/user/user.go +++ b/api/user/user.go @@ -20,7 +20,7 @@ import ( func Router(r OneBD.Router) { pool := OneBD.NewHandlerPool(func() OneBD.Handler { h := &handler{} - h.Ignore(rfc.MethodHead) + h.Ignore(rfc.MethodHead, rfc.MethodPost) return h }) r.Set("/", pool, rfc.MethodGet, rfc.MethodPost) // list @@ -68,8 +68,8 @@ func (h *handler) Get() (interface{}, error) { // Post register user func (h *handler) Post() (interface{}, error) { - if !h.CheckAuth("user").CanCreate() { - return nil, oerr.NoAuth + if !cfg.CFG.EnableRegister { + return nil, oerr.NoAuth.AttachStr("register disabled.") } var userdata = struct { Username string `json:"username"` @@ -138,7 +138,7 @@ func (h *handler) Patch() (interface{}, error) { if err := cfg.DB().Where(&target).First(&target).Error; err != nil { return nil, err } - if target.ID != h.Payload.ID && !h.CheckAuth("admin").CanDoAny() { + if target.ID != h.Payload.ID { return nil, oerr.NoAuth } if len(opts.Password) >= 6 { @@ -187,6 +187,10 @@ func (h *handler) Head() (interface{}, error) { if len(uid) == 0 || len(password) == 0 { return nil, oerr.ApiArgsError } + appID, err := strconv.Atoi(h.Meta().Query("app_id")) + if err != nil || appID <= 0 { + return nil, oerr.ApiArgsMissing + } h.User = new(models.User) uidType := h.Meta().Query("uid_type") switch uidType { @@ -199,6 +203,12 @@ func (h *handler) Head() (interface{}, error) { default: h.User.Username = uid } + app := &models.App{} + app.ID = uint(appID) + err = cfg.DB().Where(app).Find(app).Error + if err != nil { + return nil, oerr.DBErr.Attach(err) + } if err := cfg.DB().Preload("Roles").Where(h.User).First(h.User).Error; err != nil { if err.Error() == gorm.ErrRecordNotFound.Error() { // admin 登录自动注册 @@ -221,7 +231,7 @@ func (h *handler) Head() (interface{}, error) { } } else { log.HandlerErrs(err) - return nil, err + return nil, oerr.DBErr.Attach(err) } } isAuth, err := h.User.CheckLogin(password) @@ -231,7 +241,7 @@ func (h *handler) Head() (interface{}, error) { if h.User.Status == "disabled" { return nil, oerr.DisableLogin } - token, err := h.User.GetToken(cfg.CFG.Key) + token, err := h.User.GetToken(app.Key, app.ID) if err != nil { log.HandlerErrs(err) return nil, oerr.Unknown.Attach(err) diff --git a/api/user/user_role.go b/api/user/user_role.go index 05f86d7..be0673c 100644 --- a/api/user/user_role.go +++ b/api/user/user_role.go @@ -19,7 +19,7 @@ type userRoleHandler struct { } func (h *userRoleHandler) Post() (interface{}, error) { - if !h.CheckAuth("role").CanCreate() { + if !h.GetAuth("role").CanCreate() { return nil, oerr.NoAuth } uid := h.Meta().ParamsInt("user_id") @@ -67,7 +67,7 @@ func (h *userRoleHandler) Post() (interface{}, error) { } func (h *userRoleHandler) Delete() (interface{}, error) { - if !h.CheckAuth("role").CanDelete() { + if !h.GetAuth("role").CanDelete() { return nil, oerr.NoAuth } uid := h.Meta().ParamsInt("user_id") diff --git a/cfg/cfg.go b/cfg/cfg.go index d7a2d6c..ba2d52a 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -11,15 +11,16 @@ import ( var Path = cmd.GetCfgPath("OneAuth", "oa") var CFG = &struct { - AdminUser string - Host string - LoggerPath string - LoggerLevel string - Key string - TimeFormat string - Debug bool - EXEDir string - DB struct { + AdminUser string + Host string + LoggerPath string + LoggerLevel string + Key string + TimeFormat string + Debug bool + EXEDir string + EnableRegister bool + DB struct { Type string Addr string User string @@ -27,13 +28,14 @@ var CFG = &struct { DB string } }{ - AdminUser: "admin", - Host: "0.0.0.0:19528", - LoggerPath: "", - LoggerLevel: "debug", - TimeFormat: "2006/01/02 15:04:05", - Debug: true, - EXEDir: "./", + AdminUser: "admin", + Host: "0.0.0.0:4001", + LoggerPath: "", + LoggerLevel: "debug", + TimeFormat: "2006/01/02 15:04:05", + Debug: true, + EXEDir: "./", + EnableRegister: true, DB: struct { Type string Addr string @@ -41,9 +43,9 @@ var CFG = &struct { Pass string DB string }{ - Type: "sqlite", - //Addr: "127.0.0.1:3306", - Addr: "oa.db", + //Type: "sqlite", + Addr: "127.0.0.1:3306", + //Addr: "oa.db", User: "root", Pass: "123456", DB: "one_auth", diff --git a/libs/auth/auth.go b/libs/auth/user_handler.go similarity index 70% rename from libs/auth/auth.go rename to libs/auth/user_handler.go index bed7ada..6be1042 100644 --- a/libs/auth/auth.go +++ b/libs/auth/user_handler.go @@ -8,12 +8,12 @@ import ( "github.com/veypi/OneBD/rfc" ) -type Auth struct { +type UserHandler struct { Payload *models.PayLoad ignoreMethod map[rfc.Method]bool } -func (a *Auth) Init(m OneBD.Meta) error { +func (a *UserHandler) Init(m OneBD.Meta) error { if a.ignoreMethod != nil && a.ignoreMethod[m.Method()] { return nil } @@ -29,7 +29,7 @@ func (a *Auth) Init(m OneBD.Meta) error { return oerr.NotLogin.Attach(err) } -func (a *Auth) Ignore(methods ...rfc.Method) { +func (a *UserHandler) Ignore(methods ...rfc.Method) { if a.ignoreMethod == nil { a.ignoreMethod = make(map[rfc.Method]bool) } @@ -38,6 +38,6 @@ func (a *Auth) Ignore(methods ...rfc.Method) { } } -func (a *Auth) CheckAuth(name string, tags ...string) models.AuthLevel { - return a.Payload.CheckAuth(name, tags...) +func (a *UserHandler) GetAuth(ResourceID string, ResourceUUID ...string) models.AuthLevel { + return a.Payload.GetAuth(ResourceID, ResourceUUID...) } diff --git a/libs/auth/user_key.go b/libs/auth/user_key.go new file mode 100644 index 0000000..4f6fbf3 --- /dev/null +++ b/libs/auth/user_key.go @@ -0,0 +1,27 @@ +package auth + +import ( + "OneAuth/models" + "github.com/veypi/utils" + "sync" +) + +var keyCache = sync.Map{} + +func GetUserKey(uid uint, app *models.App) string { + if app.ID == 1 { + key, _ := keyCache.LoadOrStore(uid, utils.RandSeq(16)) + return key.(string) + } + // TODO: 获取其他应用user_key + return "" +} + +func RefreshUserKey(uid uint, app *models.App) string { + if app.ID == 1 { + key := utils.RandSeq(16) + keyCache.Store(uid, key) + return key + } + return "" +} diff --git a/libs/base/api_handler.go b/libs/base/api_handler.go index 7cf34f6..464b787 100644 --- a/libs/base/api_handler.go +++ b/libs/base/api_handler.go @@ -17,11 +17,11 @@ var json = jsoniter.ConfigFastest type ApiHandler struct { OneBD.BaseHandler - auth.Auth + auth.UserHandler } func (h *ApiHandler) Init(m OneBD.Meta) error { - return tools.MultiIniter(m, &h.BaseHandler, &h.Auth) + return tools.MultiIniter(m, &h.BaseHandler, &h.UserHandler) } func (h *ApiHandler) OnResponse(data interface{}) { diff --git a/models/app.go b/models/app.go index 76392e3..c34951d 100644 --- a/models/app.go +++ b/models/app.go @@ -1,16 +1,34 @@ package models +var AppKeys = map[string]string{} + type App struct { BaseModel - Name string `json:"name"` - UUID string `json:"uuid"` - Host string `json:"host"` - WxID string `json:"wx_id" gorm:""` - Wx *Wechat `json:"wx" gorm:"association_foreignkey:ID"` + Name string `json:"name"` + Icon string `json:"icon"` + UUID string `json:"uuid"` + // 认证成功跳转链接 + Host string `json:"host"` + // 加解密用户token (key+key2) + // 两个key都是请求获取时刷新 + // key oa发放给app 双方保存 针对app生成 每个应用有一个 + // key2 app发放给oa app保存 oa使用一次销毁 针对当个用户生成 每个用户有一个 + // 获取app用户加密秘钥key2 + UserRefreshUrl string `json:"user_refresh_url"` + // app 校验用户token时使用 + Key string `json:"key"` + // 是否允许用户注册 + EnableRegister string `json:"enable_register"` + EnableUser bool `json:"enable_user"` + EnableWx bool `json:"enable_wx"` + EnablePhone bool `json:"enable_phone"` + EnableEmail bool `json:"enable_email"` + Wx *Wechat `json:"wx" gorm:"foreignkey:AppID;references:ID"` } type Wechat struct { BaseModel + AppID uint `json:"app_id"` // 网页授权登录用 WxID string `json:"wx_id"` AgentID string `json:"agent_id"` diff --git a/models/role.go b/models/role.go index ed68db9..751fe82 100644 --- a/models/role.go +++ b/models/role.go @@ -1,74 +1,44 @@ package models -import ( - "OneAuth/cfg" - "github.com/veypi/utils/log" -) - -var GlobalRoles = make(map[uint]*Role) - -func SyncGlobalRoles() { - roles := make([]*Role, 0, 10) - err := cfg.DB().Preload("Auths").Find(&roles).Error - if err != nil { - log.Warn().Msgf("sync global roles error: %s", err.Error()) - return - } - for _, r := range roles { - GlobalRoles[r.ID] = r - } -} - type UserRole struct { BaseModel UserID uint `json:"user_id"` RoleID uint `json:"role_id"` } -type RoleAuth struct { - BaseModel - RoleID uint `json:"role_id"` - AuthID uint `json:"auth_id"` -} - type Role struct { BaseModel Name string `json:"name"` // 角色类型 - // 0: 系统角色 1: 用户角色 - Category uint `json:"category" gorm:"default:0"` + // 1: 系统定义角色 2: 用户自定义角色 + Category uint `json:"category" gorm:"default:1"` // 角色标签 Tag string `json:"tag" gorm:"default:''"` Users []*User `json:"users" gorm:"many2many:user_role;"` // 具体权限 - Auths []*Auth `json:"auths" gorm:"many2many:role_auth;"` + Auths []*Auth `json:"auths" gorm:"foreignkey:RoleID;references:ID"` IsUnique bool `json:"is_unique" gorm:"default:false"` } -func (r Role) CheckAuth(name string, tags ...string) AuthLevel { - res := AuthNone - tag := "" - if len(tags) > 0 { - tag = tags[0] - } - for _, a := range r.Auths { - if a.Name == "admin" && a.Tag == "" || (a.Name == "admin" && a.Tag == tag) || (a.Name == name && a.Tag == tag) { - if a.Level > res { - res = a.Level - } - } - } - return res -} - +// AuthLevel 权限等级 +// 0 相当于没有 +// 1 有限读权限 +// 2 读权限 +// 3 创建权限 +// 4 修改权限 +// 5 删除权限 +// 6 赋予其余人权限 type AuthLevel uint const ( - AuthNone AuthLevel = 0 - AuthRead AuthLevel = 1 - AuthCreate AuthLevel = 2 - AuthUpdate AuthLevel = 3 - AuthDelete AuthLevel = 4 + AuthNone AuthLevel = 0 + // AuthPart TODO: 临时权限 + AuthPart AuthLevel = 1 + AuthRead AuthLevel = 2 + AuthCreate AuthLevel = 3 + AuthUpdate AuthLevel = 4 + AuthDelete AuthLevel = 5 + AuthAll AuthLevel = 6 ) func (a AuthLevel) CanRead() bool { @@ -88,17 +58,25 @@ func (a AuthLevel) CanDelete() bool { } func (a AuthLevel) CanDoAny() bool { - return a >= AuthDelete + return a >= AuthAll } // 资源权限 type Auth struct { BaseModel - Name string `json:"name"` - AppID uint `json:"app_id"` + Name string `json:"name"` + // 该权限作用的应用 + AppID uint `json:"app_id"` + // 权限绑定只能绑定一个 + RoleID uint `json:"role_id"` + UserID uint `json:"user_id"` + // 资源id + RID string `json:"rid" gorm:""` + // 具体某个资源的id + RUID string `json:"ruid"` // 权限标签 - Tag string `json:"tag"` - // 权限等级 0 相当于没有 1 读权限 2 创建权限 3 修改权限 4 删除权限 + Tag string `json:"tag"` Level AuthLevel `json:"level"` + Des string `json:"des"` } diff --git a/models/user.go b/models/user.go index c1a1419..e1faf70 100644 --- a/models/user.go +++ b/models/user.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" "github.com/veypi/utils" - "github.com/veypi/utils/log" "strings" "time" ) @@ -27,33 +26,47 @@ type User struct { Icon string `json:"icon"` Roles []*Role `json:"roles" gorm:"many2many:user_role;"` + Auths []*Auth `json:"auths" gorm:"foreignkey:UserID;references:ID"` +} + +type simpleAuth struct { + RID string `json:"rid"` + // 具体某个资源的id + RUID string `json:"ruid"` + Level AuthLevel `json:"level"` } // TODO:: roles 是否会造成token过大 ? type PayLoad struct { - ID uint `json:"id"` - Username string `json:"username"` - Nickname string `json:"nickname"` - Icon string `json:"icon"` - Iat int64 `json:"iat"` //token time - Exp int64 `json:"exp"` - Roles []uint `json:"roles"` + ID uint `json:"id"` + Iat int64 `json:"iat"` //token time + Exp int64 `json:"exp"` + Auth map[uint]*simpleAuth `json:"auth"` } -func (p *PayLoad) CheckAuth(name string, tags ...string) AuthLevel { +// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖 +func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) AuthLevel { res := AuthNone - if p == nil || p.Roles == nil { + if p == nil || p.Auth == nil { return res } - for _, id := range p.Roles { - r := GlobalRoles[id] - if r == nil { - log.Warn().Msgf("not found role id: %d", id) - continue - } - t := r.CheckAuth(name, tags...) - if t > res { - res = t + ruid := "" + if len(ResourceUUID) > 0 { + ruid = ResourceUUID[0] + } + for _, a := range p.Auth { + if a.RID == ResourceID { + if a.RUID != "" { + if a.RUID == ruid { + if a.Level > res { + res = a.Level + } + } else { + continue + } + } else if a.Level > res { + res = a.Level + } } } return res @@ -63,26 +76,7 @@ func (u *User) String() string { return u.Username + ":" + u.Nickname } -func (u *User) CheckAuth(name string, tags ...string) AuthLevel { - res := AuthNone - if u == nil || u.Roles == nil { - return res - } - for _, t := range u.Roles { - r := GlobalRoles[t.ID] - if r == nil { - log.Warn().Msgf("not found role id: %d", t.ID) - continue - } - t := r.CheckAuth(name, tags...) - if t > res { - res = t - } - } - return res -} - -func (u *User) GetToken(key string) (string, error) { +func (u *User) GetToken(key string, appID uint) (string, error) { header := map[string]string{ "typ": "JWT", "alg": "HS256", @@ -90,15 +84,30 @@ func (u *User) GetToken(key string) (string, error) { //header := "{\"typ\": \"JWT\", \"alg\": \"HS256\"}" now := time.Now().Unix() payload := PayLoad{ - ID: u.ID, - Username: u.Username, - Nickname: u.Nickname, - Icon: u.Icon, - Iat: now, - Exp: now + 60*60*24, + ID: u.ID, + Iat: now, + Exp: now + 60*60*24, + Auth: map[uint]*simpleAuth{}, } for _, r := range u.Roles { - payload.Roles = append(payload.Roles, r.ID) + for _, a := range r.Auths { + if appID == a.AppID { + payload.Auth[a.ID] = &simpleAuth{ + RID: a.RID, + RUID: a.RUID, + Level: a.Level, + } + } + } + } + for _, a := range u.Auths { + if appID == a.AppID { + payload.Auth[a.ID] = &simpleAuth{ + RID: a.RID, + RUID: a.RUID, + Level: a.Level, + } + } } a, err := json.Marshal(header) if err != nil { diff --git a/oaf/public/index.html b/oaf/public/index.html index bc51465..642a583 100644 --- a/oaf/public/index.html +++ b/oaf/public/index.html @@ -4,7 +4,7 @@ - +