@@ -0,0 +1,350 @@
|
||||
package authweb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"91porn-server/common"
|
||||
"91porn-server/common/constant"
|
||||
"91porn-server/common/constant/redisconst"
|
||||
"91porn-server/common/crypt"
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/common/stderr"
|
||||
"91porn-server/models/v/adminmod"
|
||||
"91porn-server/models/v/ipwhitemod"
|
||||
"91porn-server/web/webg"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
// 白名单,不需要验证token的api
|
||||
whitelist = map[string]bool{
|
||||
"/swagger": true,
|
||||
"/api/web/admin/vid/pullFileInfoFromAws": true,
|
||||
"/api/web/admin/vid/listDetail": true,
|
||||
"/api/web/admin/vid/uploadStaticBatch": true,
|
||||
"/api/web/channel/admin/login": true,
|
||||
"/api/web/channel/admin/sms/captcha": true,
|
||||
"/api/web/admin/cdn/sync": true,
|
||||
"/api/web/admin/extend/syncServerFile": true,
|
||||
"/api/web/admin/login": true,
|
||||
"/api/web/admin/slogin": true,
|
||||
"/api/web/admin/verify": true,
|
||||
"/api/web/admin/vid/sec": true,
|
||||
"/api/web/admin/vid/pms/sec": true,
|
||||
"/api/web/admin/vid/pms/mt_sec": true,
|
||||
"/api/web/admin/vid/sp/sec": true,
|
||||
"/api/web/admin/vid/sp/lsjsec": true,
|
||||
"/api/web/admin/vid/sp/m3u8sec": true,
|
||||
"/api/web/admin/export/rechargeOrder": true,
|
||||
"/api/web/admin/export/withdrawOrder": true,
|
||||
"/api/web/admin/export/goldTurnover": true,
|
||||
"/api/web/admin/export/videoIncome": true,
|
||||
"/api/web/admin/export/orderStat": true,
|
||||
"/api/web/admin/export/exchangecode": true,
|
||||
"/api/web/district/agent/captcha": true,
|
||||
"/api/web/district/agent/login": true,
|
||||
"/api/web/admin/vid/syncNewVideo": true,
|
||||
"/api/web/admin/newactivity/importModels": true,
|
||||
"/api/web/admin/vid/spiderSyncSubmit": true,
|
||||
"/api/web/admin/stats/export": true,
|
||||
"/api/web/admin/ai/undress/callback": true,
|
||||
"/api/web/admin/ai/changeface/callback": true,
|
||||
"/api/web/admin/ai/change_face_img/callback": true,
|
||||
"/api/web/admin/ai/image_to_video/callback": true,
|
||||
"/api/web/admin/ai/text_to_image/callback": true,
|
||||
"/api/web/admin/ai/text_to_novel/callback": true,
|
||||
"/api/web/laosiji/post/search": true,
|
||||
"/api/web/laosiji/post/detail": true,
|
||||
"/api/web/laosiji/comics/search": true,
|
||||
"/api/web/laosiji/comics/detail": true,
|
||||
"/api/web/laosiji/novel/search": true,
|
||||
"/api/web/laosiji/novel/detail": true,
|
||||
}
|
||||
InvalidTokenErr = errors.New(" invalid token err")
|
||||
AccessForbidErr = errors.New(stderr.ErrAccessForbid.Msg())
|
||||
noTokenMsg = gin.H{
|
||||
"code": stderr.ErrNoToken,
|
||||
"msg": stderr.ErrNoToken.Msg(),
|
||||
}
|
||||
)
|
||||
|
||||
const WarnTokenExpire = 10 * time.Minute
|
||||
|
||||
type ActType = string
|
||||
|
||||
const (
|
||||
Admin ActType = "admin"
|
||||
Channel ActType = "channel"
|
||||
District ActType = "district"
|
||||
)
|
||||
|
||||
type AdminRole = string
|
||||
|
||||
const (
|
||||
JuShang AdminRole = "聚商"
|
||||
NudeChatMerchant AdminRole = "裸聊商家"
|
||||
)
|
||||
|
||||
func GetTokenSecret() string {
|
||||
return webg.Conf.Base.JwtKey
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
Type ActType
|
||||
Act string `json:"act"`
|
||||
Role string `json:"role"`
|
||||
CID string `json:"cid"`
|
||||
}
|
||||
|
||||
type claimsWithExp struct {
|
||||
Claims
|
||||
Exp int64 `json:"exp"`
|
||||
}
|
||||
|
||||
func genToken(claims *Claims) (string, error) {
|
||||
c := claimsWithExp{Claims: *claims, Exp: time.Now().Add(redisconst.WebTokenExpire).Unix()}
|
||||
secret := GetTokenSecret()
|
||||
args, _ := common.JSONStruct2Map(c)
|
||||
token, err := crypt.CreateToken(secret, args)
|
||||
if err != nil {
|
||||
log.Error("auth web GenToken error", log.Any("claims", claims), log.E(err))
|
||||
}
|
||||
return token, err
|
||||
}
|
||||
|
||||
func GenAndSaveToken(claims *Claims) (string, error) {
|
||||
token, err := genToken(claims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err = saveToken(claims.Type, claims.Act, token); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func ParseToken(token string) (*claimsWithExp, error) {
|
||||
secret := GetTokenSecret()
|
||||
claims, err := crypt.ParseToken(secret, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := claimsWithExp{}
|
||||
return &c, common.Map2JSONStruct(&c, claims)
|
||||
}
|
||||
|
||||
func tokenRedisKey(typ ActType, act string) string {
|
||||
return redisconst.WebTokenKey(typ, act)
|
||||
}
|
||||
|
||||
func saveToken(typ ActType, act string, token string) error {
|
||||
key := tokenRedisKey(typ, act)
|
||||
return webg.Redis.Set(key, token, redisconst.WebTokenExpire)
|
||||
}
|
||||
|
||||
func RevokeDistrictToken(acts ...string) {
|
||||
keys := make([]string, len(acts))
|
||||
for i, act := range acts {
|
||||
keys[i] = tokenRedisKey(District, act)
|
||||
}
|
||||
_, _ = webg.Redis.Del(keys...)
|
||||
}
|
||||
|
||||
func RevokeChannelToken(acts ...string) {
|
||||
keys := make([]string, len(acts))
|
||||
for i, act := range acts {
|
||||
keys[i] = tokenRedisKey(Channel, act)
|
||||
}
|
||||
_, _ = webg.Redis.Del(keys...)
|
||||
}
|
||||
|
||||
func RevokeAdminToken(acts ...string) {
|
||||
keys := make([]string, len(acts))
|
||||
for i, act := range acts {
|
||||
keys[i] = tokenRedisKey(Admin, act)
|
||||
}
|
||||
_, _ = webg.Redis.Del(keys...)
|
||||
}
|
||||
|
||||
func auth(token string) (string, AdminRole, ActType, string, bool, error) {
|
||||
claims, err := ParseToken(token)
|
||||
if err != nil {
|
||||
return "", "", "", "", false, InvalidTokenErr
|
||||
}
|
||||
act := claims.Act
|
||||
typ := claims.Type
|
||||
role := claims.Role
|
||||
cid := claims.CID
|
||||
exp := time.Unix(claims.Exp, 0)
|
||||
redisKey := tokenRedisKey(typ, act)
|
||||
redisToken, err := webg.Redis.Get(redisKey)
|
||||
if err != nil || redisToken == nil {
|
||||
return "", "", "", "", false, InvalidTokenErr
|
||||
}
|
||||
if token != *redisToken { //Redis token过期,或者错误
|
||||
return "", "", "", "", false, InvalidTokenErr
|
||||
}
|
||||
now := time.Now()
|
||||
if now.After(exp) { //token过期
|
||||
return "", "", "", "", false, InvalidTokenErr
|
||||
}
|
||||
warn := exp.Sub(now) < WarnTokenExpire
|
||||
return cid, role, typ, act, warn, nil
|
||||
}
|
||||
|
||||
var accessForbidMsg = gin.H{
|
||||
"code": stderr.ErrAccessForbid,
|
||||
"msg": stderr.ErrAccessForbid.Msg(),
|
||||
}
|
||||
|
||||
func Auth(ctx *gin.Context) {
|
||||
for url, ok := range whitelist {
|
||||
if ok && strings.HasPrefix(ctx.Request.URL.Path, url) {
|
||||
return
|
||||
}
|
||||
}
|
||||
//检查ip是否在白名单中
|
||||
// ipFlag, err := webg.Redis.SISMember(constant.IPWhiteRedisKey, constant.CtxIP)
|
||||
// if webg.Conf.EnableIPWhite.IsEnable && (err != nil || !ipFlag) {
|
||||
if webg.Conf.EnableIPWhite.IsEnable {
|
||||
iPWhite, err := ipwhitemod.FindOneByIp(ctx.ClientIP())
|
||||
if err != nil || iPWhite.IP == "" {
|
||||
ctx.AbortWithStatusJSON(http.StatusOK, accessForbidMsg)
|
||||
return
|
||||
}
|
||||
}
|
||||
var token string
|
||||
t1 := ctx.Request.Header.Get("Authorization")
|
||||
t2 := ctx.Query("token") //为兼容m3u8
|
||||
if t1 != "" {
|
||||
token = t1
|
||||
}
|
||||
if t2 != "" {
|
||||
token = t2
|
||||
}
|
||||
if token == "" {
|
||||
ctx.AbortWithStatusJSON(http.StatusOK, noTokenMsg)
|
||||
return
|
||||
}
|
||||
cid, role, typ, act, warn, err := auth(token)
|
||||
if err != nil {
|
||||
ctx.Writer.Header().Set("Refresh-Authorization", "false")
|
||||
ctx.AbortWithStatusJSON(http.StatusOK, gin.H{
|
||||
"code": stderr.ErrAuthInvalid,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// 检查管理员账号是否被删除或禁用
|
||||
adm, err := adminmod.FindOneByName(act)
|
||||
if err != nil || adm.ID.IsZero() || adm.HasLocked {
|
||||
RevokeAdminToken(act)
|
||||
ctx.Writer.Header().Set("Refresh-Authorization", "false")
|
||||
ctx.AbortWithStatusJSON(http.StatusOK, gin.H{
|
||||
"code": stderr.ErrAuthInvalid,
|
||||
"msg": "账号已被禁用或删除",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if warn {
|
||||
ctx.Writer.Header().Set("Refresh-Authorization", "true")
|
||||
}
|
||||
ctx.Set(constant.CtxAdminRole, role)
|
||||
switch typ {
|
||||
case District:
|
||||
ctx.Set(constant.CtxDistrictName, act)
|
||||
default:
|
||||
ctx.Set(constant.CtxAdminAct, act)
|
||||
switch role {
|
||||
case JuShang:
|
||||
if cid == "" {
|
||||
ctx.AbortWithStatusJSON(http.StatusOK, gin.H{
|
||||
"code": stderr.ErrAuthInvalid,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
ctx.Set(constant.CtxJuShangCID, cid)
|
||||
if !strings.Contains(ctx.Request.URL.Path, "/jushang") && !strings.Contains(ctx.Request.URL.Path, "/api/web/admin/refresh") {
|
||||
ctx.AbortWithStatusJSON(http.StatusOK,
|
||||
gin.H{
|
||||
"code": stderr.ErrAccessForbid,
|
||||
"msg": "权限出错",
|
||||
})
|
||||
}
|
||||
case NudeChatMerchant:
|
||||
if cid == "" {
|
||||
ctx.AbortWithStatusJSON(http.StatusOK, gin.H{
|
||||
"code": stderr.ErrAuthInvalid,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
}
|
||||
ctx.Set(constant.CtxNudeChatMerchant, cid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tokenAppRedisKey(uid uint64) string {
|
||||
return redisconst.UserTokenKey(uid)
|
||||
}
|
||||
|
||||
// RevokeTokenCache 吊销redis用户token
|
||||
func RevokeTokenCache(uids ...uint64) {
|
||||
keys := make([]string, len(uids))
|
||||
for i, uid := range uids {
|
||||
keys[i] = tokenAppRedisKey(uid)
|
||||
}
|
||||
_, _ = webg.Redis.Del(keys...)
|
||||
}
|
||||
|
||||
func getWebTokenSecret() string {
|
||||
return webg.Conf.Base.WebJwtKey
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7519#section-4.1
|
||||
// See examples for how to use this with your own claim types
|
||||
type WebClaims struct {
|
||||
//用户UID
|
||||
UID uint64 `json:"uid,omitempty"`
|
||||
|
||||
//The "exp" (expiration time) claim identifies the expiration time on
|
||||
//or after which the JWT MUST NOT be accepted for processing. The
|
||||
//processing of the "exp" claim requires that the current date/time
|
||||
//MUST be before the expiration date/time listed in the "exp" claim.
|
||||
//Implementers MAY provide for some small leeway, usually no more than
|
||||
//a few minutes, to account for clock skew. Its value MUST be a number
|
||||
//containing a NumericDate value. Use of this claim is OPTIONAL.
|
||||
ExpiresAt int64 `json:"exp,omitempty"`
|
||||
|
||||
//The "iat" (issued at) claim identifies the time at which the JWT was
|
||||
//issued. This claim can be used to determine the age of the JWT. Its
|
||||
//value MUST be a number containing a NumericDate value. Use of this
|
||||
//claim is OPTIONAL.
|
||||
IssuedAt int64 `json:"iat,omitempty"`
|
||||
}
|
||||
|
||||
func GenWebToken(claims WebClaims) (string, error) {
|
||||
secret := getWebTokenSecret()
|
||||
m, _ := common.JSONStruct2Map(claims)
|
||||
token, err := crypt.CreateToken(secret, m)
|
||||
if err != nil {
|
||||
log.Error("GenWebToken error", log.Any("claims", claims), log.E(err))
|
||||
}
|
||||
return token, err
|
||||
}
|
||||
|
||||
// ParseWebClaims
|
||||
// 如果token过期会返回error
|
||||
func ParseWebClaims(token string) (WebClaims, error) {
|
||||
secret := getWebTokenSecret()
|
||||
claims, err := crypt.ParseToken(secret, token)
|
||||
if err != nil {
|
||||
return WebClaims{}, err
|
||||
}
|
||||
webClaims := WebClaims{}
|
||||
return webClaims, common.Map2JSONStruct(&webClaims, claims)
|
||||
}
|
||||
Reference in New Issue
Block a user