- 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
}

View File

@@ -0,0 +1,32 @@
package auth2
import (
"time"
"git.rosy.net.cn/jx-callback/business/model"
)
const (
TokenTypeNone = 0
TokenTypeNormal = 1
TokenTypeOnlyAuth = 2
)
type AuthInfo struct {
IUser
AuthBindInfo *model.AuthBind
LoginTime time.Time
ExpiresIn int64
Token string
TokenType int // TOKEN类型
UserData interface{}
}
func (a *AuthInfo) GetAuthID() string {
return a.AuthBindInfo.AuthID
}
func (a *AuthInfo) GetAuthType() string {
return a.AuthBindInfo.Type
}

View File

@@ -0,0 +1,86 @@
package mobile
import (
"errors"
"fmt"
"math/rand"
"time"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/auth2"
"git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/globals"
"git.rosy.net.cn/jx-callback/globals/api"
"github.com/KenmyZhang/aliyun-communicate"
)
const (
DefVerifyCodeDuration = 5 * time.Minute
TestMobile = "91112345678"
TestVerifyCode = "123456"
)
const (
AuthType = auth2.AuthTypeMobile
)
var (
ErrVerifyCodeIsWrong = errors.New("验证码错")
)
type Auther struct {
auth2.DefAuther
}
var (
AutherObj *Auther
)
func init() {
AutherObj = new(Auther)
auth2.RegisterAuther(AuthType, AutherObj)
}
// 特殊接口
func (a *Auther) SendVerifyCode(mobileNumber string) error {
code := fmt.Sprintf("%06d", rand.Intn(1000000))
globals.SugarLogger.Debugf("SendVerifyCode mobileNumber:%s, code:%s", mobileNumber, code)
smsClient := aliyunsmsclient.New("http://dysmsapi.aliyuncs.com/")
_, err := smsClient.Execute(globals.AliKey, globals.AliSecret, mobileNumber, "京西菜市", "SMS_84655036", string(utils.MustMarshal(map[string]interface{}{
"code": code,
})))
if err == nil {
api.Cacher.Set(mobileNumber, code, DefVerifyCodeDuration)
} else {
globals.SugarLogger.Infof("SendVerifyCode mobileNumber:%s failed with error:%v", mobileNumber, err)
}
return err
}
func (a *Auther) VerifySecret(mobileNumber, code string) (authBind *model.AuthBind, err error) {
globals.SugarLogger.Debugf("VerifySecret mobileNumber:%s, code:%s", mobileNumber, code)
err = ErrVerifyCodeIsWrong
if mobileNumber == TestMobile && code == TestVerifyCode {
err = nil
} else {
if value := api.Cacher.Get(mobileNumber); value != nil {
if code == value.(string) {
api.Cacher.Del(mobileNumber)
err = nil
}
}
}
return nil, err
}
// 此函数为空
func (a *Auther) AddAuthBind(authBind *model.AuthBind, userName string) (err error) {
return err
}
// 此函数为空
func (a *Auther) UnbindAuth(authInfo *auth2.AuthInfo, authType string) (err error) {
return err
}

View File

@@ -0,0 +1,55 @@
package password
import (
"errors"
"git.rosy.net.cn/jx-callback/business/auth2"
"git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/business/model/dao"
)
const (
AuthType = auth2.AuthTypePassword
)
type Auther struct {
auth2.DefAuther
}
var (
ErrUserAndPassNotMatch = errors.New("用户名密码不匹配")
)
func init() {
auth2.RegisterAuther(AuthType, new(Auther))
}
func (a *Auther) VerifySecret(userID, passMD5 string) (authBind *model.AuthBind, err error) {
if authBind, err = dao.GetAuthBind(nil, "", AuthType, userID, ""); err == nil {
err = a.checkPassword(authBind, passMD5)
} else if dao.IsNoRowsError(err) {
err = auth2.ErrUserNotExist
}
return authBind, err
}
// 特殊接口
func (a *Auther) ChangePassword(userID, oldPassMD5, newPassMD5 string) (err error) {
var authBind *model.AuthBind
if authBind, err = dao.GetAuthBind(nil, "", AuthType, userID, ""); err == nil {
if err = a.checkPassword(authBind, oldPassMD5); err == nil || authBind.AuthSecret == "" {
authBind.AuthSecret = newPassMD5
_, err = dao.UpdateEntity(nil, authBind, "AuthSecret")
}
} else if dao.IsNoRowsError(err) {
err = auth2.ErrUserNotExist
}
return err
}
func (a *Auther) checkPassword(authBind *model.AuthBind, passMD5 string) (err error) {
if authBind.AuthSecret != passMD5 {
return ErrUserAndPassNotMatch
}
return nil
}

View File

@@ -0,0 +1,80 @@
package weixin
import (
"errors"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/auth2"
"git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/business/model/dao"
"git.rosy.net.cn/jx-callback/globals"
"git.rosy.net.cn/jx-callback/globals/api"
)
const (
AuthTypeWeixin = "weixin"
AuthTypeMP = "weixinmp"
AuthTypeMini = "weixinmini"
)
type Auther struct {
auth2.DefAuther
authType string
}
var (
AutherObjWX *Auther
AutherObjMP *Auther
)
var (
ErrStateIsWrong = errors.New("登录state非法")
)
func init() {
AutherObjWX = &Auther{
authType: AuthTypeWeixin,
}
auth2.RegisterAuther(AuthTypeWeixin, AutherObjWX)
AutherObjMP = &Auther{
authType: AuthTypeMP,
}
auth2.RegisterAuther(AuthTypeMP, AutherObjMP)
}
func (a *Auther) VerifySecret(state, code string) (authBind *model.AuthBind, err error) {
globals.SugarLogger.Debugf("weixin VerifySecret code:%s", code)
if state == "" {
token, err2 := api.WeixinAPI.SNSRetrieveToken(code)
if err = err2; err == nil {
wxUserinfo, err2 := api.WeixinAPI.SNSGetUserInfo(token.AccessToken, token.OpenID)
if err = err2; err == nil {
db := dao.GetDB()
if authBind, err = dao.GetAuthBind(db, "", a.authType, wxUserinfo.OpenID, ""); dao.IsNoRowsError(err) {
var authBindList []*model.AuthBind
if wxUserinfo.UnionID != "" {
if authBindList, err = dao.GetAuthBindsByWXUnionID(db, wxUserinfo.UnionID); err == nil && len(authBindList) > 0 {
authBind = authBindList[0]
authBind.Type = a.authType
authBind.AuthID = wxUserinfo.OpenID
authBind.DetailData = string(utils.MustMarshal(wxUserinfo))
err = a.AddAuthBind(authBind, wxUserinfo.NickName)
}
}
if err == nil && len(authBindList) == 0 {
authBind = &model.AuthBind{
Type: a.authType,
AuthID: wxUserinfo.OpenID,
AuthID2: wxUserinfo.UnionID,
DetailData: string(utils.MustMarshal(wxUserinfo)),
}
}
}
}
}
} else {
err = ErrStateIsWrong
}
return authBind, err
}

View File

@@ -0,0 +1,76 @@
package weixin
import (
"encoding/base64"
"errors"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/auth2"
"git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/business/model/dao"
"git.rosy.net.cn/jx-callback/globals"
"git.rosy.net.cn/jx-callback/globals/api"
)
type MiniAuther struct {
auth2.DefAuther
}
var (
ErrAuthTypeShouldBeMini = errors.New("当前操作要求是小程序登录方式")
)
var (
AutherObjMini *MiniAuther
)
func init() {
AutherObjMini = new(MiniAuther)
auth2.RegisterAuther(AuthTypeMP, AutherObjMini)
}
func (a *MiniAuther) VerifySecret(dummy, jsCode string) (authBind *model.AuthBind, err error) {
globals.SugarLogger.Debugf("weixin mini VerifySecret jsCode:%s", jsCode)
sessionInfo, err := api.WeixinMiniAPI.SNSCode2Session(jsCode)
if err == nil {
db := dao.GetDB()
if authBind, err = dao.GetAuthBind(db, "", AuthTypeMP, sessionInfo.OpenID, ""); dao.IsNoRowsError(err) {
var authBindList []*model.AuthBind
if sessionInfo.UnionID != "" {
if authBindList, err = dao.GetAuthBindsByWXUnionID(db, sessionInfo.UnionID); err == nil && len(authBindList) > 0 {
authBind = authBindList[0]
authBind.Type = AuthTypeMP
authBind.AuthID = sessionInfo.OpenID
authBind.DetailData = string(utils.MustMarshal(sessionInfo))
authBind.UserData = sessionInfo.SessionKey
err = a.AddAuthBind(authBind, "admin")
}
}
if err == nil && len(authBindList) == 0 {
authBind = &model.AuthBind{
Type: AuthTypeMP,
AuthID: sessionInfo.OpenID,
AuthID2: sessionInfo.UnionID,
DetailData: string(utils.MustMarshal(sessionInfo)),
UserData: sessionInfo.SessionKey,
}
}
}
}
return authBind, err
}
// 特殊接口
func (a *MiniAuther) DecryptData(authInfo *auth2.AuthInfo, encryptedData, iv string) (decryptedDataBase64 string, err error) {
globals.SugarLogger.Debugf("weixin mini DecryptData encryptedData:%s, iv:%s", encryptedData, iv)
if authInfo.AuthBindInfo.Type != AuthTypeMini {
return "", ErrAuthTypeShouldBeMini
}
sessionKey := authInfo.AuthBindInfo.UserData.(string)
decryptedData, err := api.WeixinMiniAPI.SNSDecodeMiniProgramData(encryptedData, sessionKey, iv)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(decryptedData), nil
}

View File

@@ -0,0 +1,37 @@
package auth2
import (
"errors"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/model"
"git.rosy.net.cn/jx-callback/business/model/dao"
)
type DefAuther struct {
}
// 此函数为空
func (a *DefAuther) AddAuthBind(authBind *model.AuthBind, userName string) (err error) {
dao.WrapAddIDCULDEntity(authBind, userName)
err = dao.CreateEntity(nil, authBind)
return err
}
func (a *DefAuther) UnbindAuth(authInfo *AuthInfo, authType string) (err error) {
_, err = dao.DeleteEntityLogically(nil, &model.AuthBind{}, nil, authInfo.GetID(), map[string]interface{}{
"UserID": authInfo.GetID(),
"Type": authType,
model.FieldDeletedAt: utils.DefaultTimeValue,
})
return err
}
func (a *DefAuther) SendVerifyCode(authID string) error {
return errors.New("当前登录类型不支持此操作")
}
// 此函数为空
func (a *DefAuther) Logout(authInfo *AuthInfo) error {
return nil
}

View File

@@ -107,3 +107,11 @@ func ConvertErr2NoUser(err error, mobileNum string) error {
}
return err
}
func (a *LoginInfo) GetAuthID() string {
return a.ID
}
func (a *LoginInfo) GetAuthType() string {
return a.LoginType
}

View File

@@ -179,14 +179,14 @@ func BindMobile2(openid, secret, mobileNum, verifyCode, nickname string) (loginI
func (a *AutherMiniProgram) BindWeiXin(ctx *jxcontext.Context, code, nickName string) (err error) {
globals.SugarLogger.Debugf("AutherMiniProgram BindWeiXin code:%s, nickName:%s", code, nickName)
loginInfo := ctx.GetLoginInfo()
if loginInfo == nil || loginInfo.LoginType != mobile.LoginType {
if loginInfo == nil || loginInfo.GetAuthType() != mobile.LoginType {
return fmt.Errorf("调用AutherMiniProgram BindWeiXin时必须以手机验证方式登录")
}
sessionInfo, err := api.WeixinMiniAPI.SNSCode2Session(code)
if err != nil {
return err
}
err = dao.UpdateWeiXinUser(dao.GetDB(), loginInfo.ID, nickName, sessionInfo.UnionID, "", sessionInfo.OpenID)
err = dao.UpdateWeiXinUser(dao.GetDB(), loginInfo.GetAuthID(), nickName, sessionInfo.UnionID, "", sessionInfo.OpenID)
return auth.ConvertErr2NoUser(err, "")
}
@@ -234,13 +234,13 @@ func (a *AutherMiniProgram) Login(mobileNum, code string) (userID, LoginType str
func (a *AutherMiniProgram) Logout(loginInfo *auth.LoginInfo) error {
globals.SugarLogger.Debugf("AutherMiniProgram Logout openid:%s", utils.Format4Output(loginInfo, false))
return api.Cacher.Del(composeSessionKeyCacheKey(loginInfo.ID))
return api.Cacher.Del(composeSessionKeyCacheKey(loginInfo.GetAuthID()))
}
func (a *AutherMiniProgram) DecryptData(ctx *jxcontext.Context, encryptedData, iv string) (decryptedDataBase64 string, err error) {
globals.SugarLogger.Debugf("AutherMiniProgram DecryptData encryptedData:%s, iv:%s", encryptedData, iv)
var sessionKey string
if err = api.Cacher.GetAs(composeSessionKeyCacheKey(ctx.GetLoginInfo().ID), &sessionKey); err != nil {
if err = api.Cacher.GetAs(composeSessionKeyCacheKey(ctx.GetLoginInfo().GetAuthID()), &sessionKey); err != nil {
return "", err
}
decryptedData, err := api.WeixinMiniAPI.SNSDecodeMiniProgramData(encryptedData, sessionKey, iv)

View File

@@ -76,7 +76,7 @@ func GetSelfInfo(ctx *jxcontext.Context) (storeUserInfo *StoreUserInfo, err erro
return nil, auth.ErrAPINeedRealLogin
}
fieldName := LoginTypeFieldMap[loginInfo.LoginType]
fieldName := LoginTypeFieldMap[loginInfo.GetAuthType()]
if fieldName == "" {
return nil, auth.ErrIllegalLoginType
}
@@ -90,7 +90,7 @@ func GetSelfInfo(ctx *jxcontext.Context) (storeUserInfo *StoreUserInfo, err erro
GROUP BY 1,2,3,4,5,6,7;
`, fieldName)
storeUserInfo = new(StoreUserInfo)
if err = dao.GetRow(nil, storeUserInfo, sql, loginInfo.ID); err == nil || err == orm.ErrNoRows { // todo
if err = dao.GetRow(nil, storeUserInfo, sql, loginInfo.GetAuthID()); err == nil || err == orm.ErrNoRows { // todo
err = nil
if storeUserInfo.MembersStr != "" {
err = utils.UnmarshalUseNumber([]byte(storeUserInfo.MembersStr), &storeUserInfo.Members)

View File

@@ -1,10 +1,12 @@
package jxcontext
import (
"errors"
"net/http"
"sync"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/auth2"
"git.rosy.net.cn/jx-callback/business/jxcallback/auth"
"git.rosy.net.cn/jx-callback/business/jxutils/tasksch"
"git.rosy.net.cn/jx-callback/business/model"
@@ -12,11 +14,16 @@ import (
"github.com/astaxie/beego"
)
type IAuther interface {
GetAuthID() string
GetAuthType() string
}
type Context struct {
rootTask tasksch.ITask
token string
accessUUID string
userInfo *auth.LoginInfo
userInfo IAuther //*auth.LoginInfo
w http.ResponseWriter
r *http.Request
mapData map[interface{}]interface{}
@@ -56,7 +63,17 @@ func New(rootTask tasksch.ITask, token string, w http.ResponseWriter, r *http.Re
mapData: make(map[interface{}]interface{}),
accessUUID: utils.GetUUID(),
}
ctx.userInfo, err = auth.GetUserInfo(token)
if auth2.IsV2Token(token) {
authInfo, err2 := auth2.GetUserInfo(token)
if err = err2; err == nil {
ctx.userInfo = authInfo
if authInfo.TokenType != auth2.TokenTypeNormal {
err = errors.New("需要正式TOKEN")
}
}
} else {
ctx.userInfo, err = auth.GetUserInfo(token)
}
if err != nil && beego.BConfig.RunMode == "prod" {
globals.SugarLogger.Debugf("token is invalid, token:%s", token)
return nil, model.ErrCodeTokenIsInvalid, err
@@ -67,7 +84,7 @@ func New(rootTask tasksch.ITask, token string, w http.ResponseWriter, r *http.Re
func (ctx *Context) GetUserName() string {
userName := ctx.token
if ctx.userInfo != nil {
userName = ctx.userInfo.ID
userName = ctx.userInfo.GetAuthID()
}
if len(userName) > MaxUserNameLen {
userName = userName[:MaxUserNameLen]
@@ -77,7 +94,7 @@ func (ctx *Context) GetUserName() string {
func (ctx *Context) GetLoginID() string {
if ctx.userInfo != nil {
return ctx.userInfo.ID
return ctx.userInfo.GetAuthID()
}
return ""
@@ -85,7 +102,7 @@ func (ctx *Context) GetLoginID() string {
func (ctx *Context) GetLoginType() string {
if ctx.userInfo != nil {
return ctx.userInfo.LoginType
return ctx.userInfo.GetAuthType()
}
return ""
}
@@ -98,7 +115,7 @@ func (ctx *Context) GetAccessUUID() string {
return ctx.accessUUID
}
func (ctx *Context) GetLoginInfo() *auth.LoginInfo {
func (ctx *Context) GetLoginInfo() IAuther {
return ctx.userInfo
}

30
business/model/auth2.go Normal file
View File

@@ -0,0 +1,30 @@
package model
const (
AuthBindStatusNormal = 1
AuthBindStatusDisabled = 2
)
type AuthBind struct {
ModelIDCULD
UserID string `orm:"size(48);column(user_id)" json:"userID"`
Type string `orm:"size(16)" json:"type"`
Status int8 `json:"status"`
AuthID string `orm:"size(48);index" json:"authID"`
AuthID2 string `orm:"size(48);index" json:"authID2"`
AuthSecret string `orm:"size(48)" json:"authSecret"`
AuthSecret2 string `orm:"size(48)" json:"authSecret2"`
Remark string `orm:"size(255)" json:"remark"`
DetailData string `orm:"type(text)" json:"-"`
UserData interface{} `orm:"-" json:"-"`
}
func (*AuthBind) TableUnique() [][]string {
return [][]string{
[]string{"UserID", "Type", "DeletedAt"},
[]string{"AuthID", "Type", "DeletedAt"},
}
}

View File

@@ -0,0 +1,11 @@
package dao
import "git.rosy.net.cn/jx-callback/business/model"
func GetAuthBind(db *DaoDB, userID, authType, authID, authID2 string) (authBind *model.AuthBind, err error) {
return authBind, err
}
func GetAuthBindsByWXUnionID(db *DaoDB, unionID string) (authBinds []*model.AuthBind, err error) {
return authBinds, err
}

43
business/model/user.go Normal file
View File

@@ -0,0 +1,43 @@
package model
type User struct {
ModelIDCULD
UserID string `orm:"size(48);column(user_id)" json:"userID"` // 内部唯一标识
UserID2 string `orm:"size(48);column(user_id2)" json:"userID2"` // 外部唯一标识(一般用于登录)
Name string `orm:"size(48)" json:"name"` // 外部唯一显示 标识(一般用于显示)
Mobile string `orm:"size(32)" json:"mobile"`
Email string `orm:"size(32)" json:"email"`
Status int8 `json:"status"`
IDCardNo string `orm:"size(18);column(id_card_no)" json:"idCardNo"` // 身份证号
}
func (*User) TableUnique() [][]string {
return [][]string{
[]string{"UserID", "DeletedAt"},
[]string{"UserID2", "DeletedAt"},
[]string{"Name", "DeletedAt"},
[]string{"Mobile", "DeletedAt"},
[]string{"Email", "DeletedAt"},
[]string{"IDCardNo", "DeletedAt"},
}
}
func (user *User) GetID() string {
return user.UserID
}
func (user *User) GetID2() string {
return user.UserID2
}
func (user *User) GetMobile() string {
return user.Mobile
}
func (user *User) GetEmail() string {
return user.Email
}
func (user *User) GetName() string {
return user.Name
}

174
controllers/auth2.go Normal file
View File

@@ -0,0 +1,174 @@
package controllers
import (
"encoding/base64"
"errors"
"fmt"
"net/http"
"git.rosy.net.cn/baseapi/utils"
"git.rosy.net.cn/jx-callback/business/auth2"
"git.rosy.net.cn/jx-callback/business/auth2/authprovider/weixin"
"git.rosy.net.cn/jx-callback/business/jxcallback/auth"
"git.rosy.net.cn/jx-callback/business/jxutils/jxcontext"
"git.rosy.net.cn/jx-callback/business/model"
"github.com/astaxie/beego"
)
var (
ErrNeedV2Token = errors.New("需要V2版的TOKEN")
)
type Auth2Controller struct {
beego.Controller
}
// @Title 发送验证码
// @Description 发送验证码
// @Param authID formData string true "手机号或邮件"
// @Success 200 {object} controllers.CallResult
// @Failure 200 {object} controllers.CallResult
// @router /SendVerifyCode [post]
func (c *Auth2Controller) SendVerifyCode() {
c.callSendVerifyCode(func(params *tAuth2SendVerifyCodeParams) (retVal interface{}, errCode string, err error) {
err = auth2.SendVerifyCode(params.AuthID)
return retVal, "", err
})
}
// @Title 登录接口
// @Description 登录接口(微信与公众号登录不能直接调用此接口)
// @Param authType formData string true "登录类型,当前支持[password本地账号密码mobile手机短信weixin:微信登录weixinmp微信公众号登录weixinmini小程序登录]"
// @Param authSecret formData string true "不同登录类型的登录秘密"
// @Param authID formData string false "登录ID登录类型为password时依赖于authIDType其它为相应登录类型的id"
// @Param authIDType formData string false "只有在登录类型为password时才有意义分别为userID2用户名emailmobile"
// @Success 200 {object} controllers.CallResult
// @Failure 200 {object} controllers.CallResult
// @router /Login [post]
func (c *Auth2Controller) Login() {
c.callLogin(func(params *tAuth2LoginParams) (retVal interface{}, errCode string, err error) {
retVal, err = auth2.Login(params.AuthType, params.AuthID, params.AuthIDType, params.AuthSecret)
return retVal, "", err
})
}
// @Title 微信认证回调接口
// @Description 微信认证回调接口,自己不能直接调用
// @Param code query string true "客户同意后得到的code"
// @Param block query string true "回调地址"
// @Param state query string false "微信回调的登录状态"
// @Success 200 {object} controllers.CallResult
// @Failure 200 {object} controllers.CallResult
// @router /WeixinOAuth2 [get]
func (c *Auth2Controller) WeixinOAuth2() {
var redirectURL string
c.callWeixinOAuth2(func(params *tAuth2WeixinOAuth2Params) (retVal interface{}, errCode string, err error) {
authInfo, err := auth2.Login(weixin.AuthTypeWeixin, params.State, "", params.Code)
var callResult *CallResult
if err == nil {
callResult = &CallResult{
Code: model.ErrCodeSuccess,
Data: string(utils.MustMarshal(authInfo)),
}
} else {
callResult = &CallResult{
Code: model.ErrCodeGeneralFailed,
Desc: err.Error(),
}
}
redirectURL = fmt.Sprintf("%s?info=%s", params.Block, base64.StdEncoding.EncodeToString(utils.MustMarshal(callResult)))
return retVal, model.ErrorCodeIgnore, err
})
c.Redirect(redirectURL, http.StatusTemporaryRedirect)
}
// @Title 微信公众号认证回调接口
// @Description 微信公众号认证回调接口,自己不能直接调用
// @Param code query string true "客户同意后得到的code"
// @Param block query string true "回调地址"
// @Param state query string false "微信回调的登录状态"
// @Success 200 {object} controllers.CallResult
// @Failure 200 {object} controllers.CallResult
// @router /WeixinMPOAuth2 [get]
func (c *Auth2Controller) WeixinMPOAuth2() {
var redirectURL string
c.callWeixinMPOAuth2(func(params *tAuth2WeixinMPOAuth2Params) (retVal interface{}, errCode string, err error) {
authInfo, err := auth2.Login(weixin.AuthTypeMP, params.State, "", params.Code)
var callResult *CallResult
if err == nil {
callResult = &CallResult{
Code: model.ErrCodeSuccess,
Data: string(utils.MustMarshal(authInfo)),
}
} else {
callResult = &CallResult{
Code: model.ErrCodeGeneralFailed,
Desc: err.Error(),
}
}
redirectURL = fmt.Sprintf("%s?info=%s", params.Block, base64.StdEncoding.EncodeToString(utils.MustMarshal(callResult)))
return retVal, model.ErrorCodeIgnore, err
})
c.Redirect(redirectURL, http.StatusTemporaryRedirect)
}
// @Title 登出接口
// @Description 登出接口此接口兼容V1的TOKEN
// @Param token header string true "认证token"
// @Success 200 {object} controllers.CallResult
// @Failure 200 {object} controllers.CallResult
// @router /Logout [delete]
func (c *Auth2Controller) Logout() {
c.callLogout(func(params *tAuth2LogoutParams) (retVal interface{}, errCode string, err error) {
if authInfo, ok := params.Ctx.GetLoginInfo().(*auth2.AuthInfo); ok {
err = auth2.Logout(authInfo)
} else {
err = auth.Logout(params.Token)
}
return nil, "", err
})
}
// @Title 绑定认证方式
// @Description 绑定认证方式
// @Param token header string true "认证token"
// @Param authToken formData string true "之前通过login得到的新认证TOKEN"
// @Success 200 {object} controllers.CallResult
// @Failure 200 {object} controllers.CallResult
// @router /AddAuthBind [post]
func (c *Auth2Controller) AddAuthBind() {
c.callAddAuthBind(func(params *tAuth2AddAuthBindParams) (retVal interface{}, errCode string, err error) {
authInfo, err2 := c.getAuth2Info(params.Ctx)
if err := err2; err == nil {
newAuthInfo, err2 := auth2.GetUserInfo(params.AuthToken)
if err = err2; err == nil {
err = auth2.AddAuthBind(authInfo, newAuthInfo)
}
}
return retVal, "", err
})
}
// @Title 删除认证方式
// @Description 删除认证方式
// @Param token header string true "认证token"
// @Param authType formData string true "登录类型,当前支持[weixin:微信登录weixinmp微信公众号登录weixinmini小程序登录]"
// @Success 200 {object} controllers.CallResult
// @Failure 200 {object} controllers.CallResult
// @router /RemoveAuthBind [post]
func (c *Auth2Controller) RemoveAuthBind() {
c.callRemoveAuthBind(func(params *tAuth2RemoveAuthBindParams) (retVal interface{}, errCode string, err error) {
authInfo, err2 := c.getAuth2Info(params.Ctx)
if err := err2; err == nil {
err = auth2.UnbindAuth(authInfo, params.AuthType)
}
return retVal, "", err
})
}
func (c *Auth2Controller) getAuth2Info(ctx *jxcontext.Context) (authInfo *auth2.AuthInfo, err error) {
if authInfo, ok := ctx.GetLoginInfo().(*auth2.AuthInfo); ok {
return authInfo, err
}
return nil, ErrNeedV2Token
}

View File

@@ -7,6 +7,62 @@ import (
func init() {
beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"],
beego.ControllerComments{
Method: "AddAuthBind",
Router: `/AddAuthBind`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Params: nil})
beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"],
beego.ControllerComments{
Method: "Login",
Router: `/Login`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Params: nil})
beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"],
beego.ControllerComments{
Method: "Logout",
Router: `/Logout`,
AllowHTTPMethods: []string{"delete"},
MethodParams: param.Make(),
Params: nil})
beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"],
beego.ControllerComments{
Method: "RemoveAuthBind",
Router: `/RemoveAuthBind`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Params: nil})
beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"],
beego.ControllerComments{
Method: "SendVerifyCode",
Router: `/SendVerifyCode`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Params: nil})
beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"],
beego.ControllerComments{
Method: "WeixinMPOAuth2",
Router: `/WeixinMPOAuth2`,
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Params: nil})
beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:Auth2Controller"],
beego.ControllerComments{
Method: "WeixinOAuth2",
Router: `/WeixinOAuth2`,
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Params: nil})
beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:AuthController"] = append(beego.GlobalControllerRouter["git.rosy.net.cn/jx-callback/controllers:AuthController"],
beego.ControllerComments{
Method: "BindMiniProgram",

View File

@@ -81,6 +81,11 @@ func init() {
&controllers.MsgController{},
),
),
beego.NSNamespace("/auth2",
beego.NSInclude(
&controllers.Auth2Controller{},
),
),
)
beego.AddNamespace(ns)