- first edition of auth2

This commit is contained in:
gazebo
2019-03-01 17:43:17 +08:00
parent b90313bd49
commit 421240ac54
17 changed files with 1015 additions and 12 deletions

293
business/auth2/auth2.go Normal file
View File

@@ -0,0 +1,293 @@
package auth2
import (
"errors"
"regexp"
"strings"
"time"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/globals"
"git.rosy.net.cn/jx-callback/globals/api"
)
const (
UserIDEmpty = ""
UserIDID = "userid"
UserIDID2 = "userid2"
UserIDMobile = "mobile"
UserIDEmail = "email"
)
const (
TokenVer = "V2"
TokenTypeSep = "."
TokenUserEmpty = "NULL"
)
const (
AuthTypeNone = ""
AuthTypePassword = "password"
AuthTypeEmail = "email"
AuthTypeMobile = "mobile"
)
const (
DefTokenDuration = 7 * 24 * time.Hour // 7天
)
type IUser interface {
GetID() string // 这个ID是不可变的系统内部使用的唯一标识
GetID2() string // 这个是可改的,唯一的,用户设置的用户名
GetMobile() string
GetEmail() string
GetName() string
}
type IUserProvider interface {
GetUser(authID, authIDType string) (user IUser)
}
type IAuther interface {
SendVerifyCode(authID string) (err error)
// 负责验证secret并找到相应的用户返回password,email,mobile类型的不负责用户查找如果找不到用户UserID为空
VerifySecret(authID, authSecret string) (authBind *model.AuthBind, err error)
AddAuthBind(authBind *model.AuthBind, userName string) (err error)
UnbindAuth(authInfo *AuthInfo, authType string) (err error)
Logout(authInfo *AuthInfo) (err error)
}
var (
authers map[string]IAuther
userProvider IUserProvider
authTypeGuesserMap = map[string]*regexp.Regexp{
AuthTypeEmail: regexp.MustCompile(`^[A-Za-z0-9_\-\.]+\@[A-Za-z0-9_\-]+(\.[A-Za-z]+){1,5}$`),
AuthTypeMobile: regexp.MustCompile(`^1[34578]\d{9}$`),
}
)
var (
ErrInternalErrror = errors.New("内部错误")
ErrTokenIsInvalid = errors.New("Token非法")
ErrUserAlreadyExist = errors.New("用户已经存在")
ErrUserNotExist = errors.New("用户不存在")
ErrIllegalAuthType = errors.New("非法的登录类型")
ErrIllegalAuthTypeAlreadyExist = errors.New("要登录类型已经存在")
)
func init() {
authers = make(map[string]IAuther)
}
func Init(userProvider2 IUserProvider) {
userProvider = userProvider2
}
func RegisterAuther(authType string, handler IAuther) {
authers[authType] = handler
}
func CreateauthInfo(user IUser, authBindInfo *model.AuthBind, userData interface{}) (authInfo *AuthInfo) {
token, tokenType := createToken(user)
authInfo = &AuthInfo{
IUser: user,
AuthBindInfo: authBindInfo,
LoginTime: time.Now(),
ExpiresIn: time.Now().Add(DefTokenDuration).Unix(),
Token: token,
TokenType: tokenType,
UserData: userData,
}
if user != nil {
globals.SugarLogger.Debugf("CreateauthInfo id:%s, id2:%s, authBindInfo:%s, authInfo:%s", authInfo.GetID(), authInfo.GetID2(), authInfo.GetMobile(), utils.Format4Output(authBindInfo, true), utils.Format4Output(authInfo, true))
} else {
globals.SugarLogger.Debugf("CreateauthInfo authBindInfo:%s, authInfo:%s", utils.Format4Output(authBindInfo, true), utils.Format4Output(authInfo, true))
}
SetUserInfo(token, authInfo, DefTokenDuration)
return authInfo
}
// 账号密码时authIDType可能是UserIDID,UserIDID2,UserIDMobile,UserIDEmailauthSecret是密码的sha1
// 邮箱时如果允许authIDType是UserIDEmailauthSecret是验证码的sha1
// 手机时如果允许authIDType是UserIDMobileauthSecret是验证码的sha1
// 公众号登录authIDTypeD是UserIDEmptyauthSecret是code这个函数是被微信的回调调用不是直接被客户端调用
// 微信登录authIDType是UserIDEmptyauthSecret是code这个函数是被微信的回调调用不是直接被客户端调用
// 小程序登录authIDType是UserIDEmptyauthSecret是jsCode
func Login(authType, authID, authIDType, authSecret string) (authInfo *AuthInfo, err error) {
if handler := authers[authType]; handler != nil {
var authBind *model.AuthBind
var user IUser
realauthID := authID
if authIDType == AuthTypePassword {
if user = userProvider.GetUser(authID, authIDType); user == nil {
return nil, ErrUserNotExist
}
realauthID = user.GetID()
}
if authBind, err = handler.VerifySecret(realauthID, authSecret); err == nil {
if authBind == nil { // mobile, email会返回nil表示不会新建AuthBind实体
user = userProvider.GetUser(authID, authIDType)
authBind = &model.AuthBind{
Type: authType,
AuthID: authID,
Status: model.AuthBindStatusNormal,
}
} else {
// 返回authBind中UserID为空表示只是认证但本地没有记录这种情况会返回临时TOKEN
if authBind.UserID != "" {
user = userProvider.GetUser(authBind.UserID, UserIDID)
}
}
authInfo = CreateauthInfo(user, authBind, nil)
}
} else {
err = ErrIllegalAuthType
}
return authInfo, err
}
// 通过临时TOKEN绑定新创建的用户
func BindUser(inauthInfo *AuthInfo, user IUser) (outauthInfo *AuthInfo, err error) {
if inauthInfo == nil || user == nil {
return nil, ErrInternalErrror
}
if inauthInfo.IUser != nil {
return nil, ErrUserAlreadyExist
}
if handler := authers[inauthInfo.AuthBindInfo.Type]; handler != nil {
inauthInfo.AuthBindInfo.UserID = user.GetID()
if err = handler.AddAuthBind(inauthInfo.AuthBindInfo, user.GetName()); err == nil {
RemoveUserInfo(inauthInfo.Token)
outauthInfo = CreateauthInfo(user, inauthInfo.AuthBindInfo, inauthInfo.UserData)
}
} else {
err = ErrIllegalAuthType
}
return outauthInfo, err
}
// 添加新绑定
func AddAuthBind(authInfo *AuthInfo, newAuthInfo *AuthInfo) (err error) {
if authInfo == nil || newAuthInfo == nil {
return ErrInternalErrror
}
if newAuthInfo.IUser != nil {
return ErrIllegalAuthTypeAlreadyExist
}
RemoveUserInfo(newAuthInfo.Token)
newAuthInfo.AuthBindInfo.UserID = authInfo.GetID()
err = authers[newAuthInfo.AuthBindInfo.Type].AddAuthBind(newAuthInfo.AuthBindInfo, authInfo.GetName())
return err
}
// func AddAuthBind(authInfo *AuthInfo, authType, authID, authIDType, authSecret string) (err error) {
// if authInfo == nil {
// return ErrInternalErrror
// }
// // 密码绑定直接绑定
// if authType == AuthTypePassword {
// err = authers[AuthTypePassword].AddAuthBind(&model.AuthBind{
// UserID: authInfo.GetID(),
// Type: AuthTypePassword,
// AuthID: authInfo.GetID(),
// Status: model.AuthBindStatusNormal,
// }, authInfo.GetName())
// } else {
// tmpauthInfo, err := Login(authType, authID, authIDType, authSecret)
// if err == nil {
// RemoveUserInfo(tmpauthInfo.Token)
// tmpauthInfo.AuthBindInfo.UserID = authInfo.GetID()
// err = authers[authType].AddAuthBind(tmpauthInfo.AuthBindInfo, authInfo.GetName())
// }
// }
// return err
// }
func UnbindAuth(authInfo *AuthInfo, authType string) (err error) {
if handler := authers[authType]; handler != nil {
err = handler.UnbindAuth(authInfo, authType)
} else {
err = ErrIllegalAuthType
}
return err
}
func Logout(authInfo *AuthInfo) (err error) {
globals.SugarLogger.Debugf("Logout authInfo:%s", utils.Format4Output(authInfo, true))
if handler := authers[authInfo.AuthBindInfo.Type]; handler != nil {
err = handler.Logout(authInfo)
}
RemoveUserInfo(authInfo.Token)
return err
}
func SendVerifyCode(authID string) (err error) {
authType := GuessAuthTypeFromAuthID(authID)
if handler := authers[authType]; handler == nil {
return ErrIllegalAuthType
} else {
return handler.SendVerifyCode(authID)
}
}
// token缓存相关
func RemoveUserInfo(token string) {
api.Cacher.Del(token)
}
func GetUserInfo(token string) (authInfo *AuthInfo, err error) {
authInfo = new(AuthInfo)
if err = api.Cacher.GetAs(token, authInfo); err == nil {
return authInfo, nil
}
return nil, ErrTokenIsInvalid
}
func SetUserInfo(token string, authInfo *AuthInfo, duration time.Duration) {
api.Cacher.Set(token, authInfo, DefTokenDuration)
}
func createToken(user IUser) (token string, tokenType int) {
userID := TokenUserEmpty
tokenType = TokenTypeOnlyAuth
if user != nil {
userID = "[" + user.GetID2() + "]"
tokenType = TokenTypeNormal
}
return strings.Join([]string{
TokenVer,
time.Now().Format("20060102-150405"),
userID,
userID,
}, TokenTypeSep), tokenType
}
func GetTokenType(token string) (tokenType int) {
tokenType = TokenTypeNone
if token != "" {
tokenPartList := strings.Split(token, TokenTypeSep)
if (len(tokenPartList) == 1) || (len(tokenPartList) == 4 && tokenPartList[2] != TokenUserEmpty) {
tokenType = TokenTypeNormal
} else {
tokenType = TokenTypeOnlyAuth
}
}
return tokenType
}
func IsV2Token(token string) bool {
tokenPartList := strings.Split(token, TokenTypeSep)
return tokenPartList[0] == TokenVer
}
func GuessAuthTypeFromAuthID(authID string) (authType string) {
for k, v := range authTypeGuesserMap {
if v.FindStringSubmatch(authID) != nil {
return k
}
}
return AuthTypeNone
}