@@ -0,0 +1,265 @@
|
||||
package authuser
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"91porn-server/app/appg"
|
||||
"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/usermod"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
// 白名单,不需要验证token的api
|
||||
// 在新的Auth中间件逻辑下,配置了白名单后,用户未登录,context中的uid会被设为0;否则,会绑定用户真实uid。
|
||||
// 需要白名单中API的handler自己对用户登录状态进行判断。
|
||||
whitelist = map[string]bool{
|
||||
"/swagger": true,
|
||||
"/api/app/mine/login": true,
|
||||
"/api/app/im/whiteSign": true,
|
||||
"/api/app/im/sign": true,
|
||||
"/api/app/mine/mobileLoginOnly": true,
|
||||
//"/api/app/ping/domain": true,
|
||||
"/api/app/mine/ver": true,
|
||||
"/api/app/ping/check": true,
|
||||
"/api/app/static/faq/html/index": true,
|
||||
"/api/app/static/faq/tmpl/index": true,
|
||||
//"/api/app/vid/m3u8": true, //前端测试 下载m3u8 添加到白名单 ,生产环境中需要从白名单中剔除
|
||||
"/api/app/vid/h5/light/m3u8": true, // 现有 H5 轻量播放接口
|
||||
// H.265 云转码使用 Handler 自己的路径绑定、限时 HMAC 鉴权。
|
||||
"/api/app/vid/transcode/m3u8": true,
|
||||
"/sources": true,
|
||||
"/defray/callback/shark": true,
|
||||
"/defray/callback/goldfish": true,
|
||||
"/api/app/vid/sec": true,
|
||||
"/api/app/vid/pms/sec": true,
|
||||
"/api/app/vid/pms/mt_sec": true,
|
||||
"/api/app/vid/lsjsec": true,
|
||||
"/api/app/vid/m3u8sec": true,
|
||||
//"/api/app/notification/captcha": true,
|
||||
"/api/app/newactivity": true,
|
||||
"/api/app/sge": true,
|
||||
"/api/app/code/webexchange": true,
|
||||
"/api/app/game/userInfo": true,
|
||||
"/api/app/game/deduct": true,
|
||||
"/api/app/game/code": true,
|
||||
"/api/app/userinvite/callback/recharge": true,
|
||||
"/api/app/statcenter/sync": true,
|
||||
"/api/app/avcomment/info": true,
|
||||
"/api/app/ping/v": true,
|
||||
//"/api/app/recommend": true,
|
||||
// "/api/app/modules": true,
|
||||
// "/api/app/tag/conf/list": true,
|
||||
"/api/app/avcomment/list": true,
|
||||
// 兼容登录、未登录状态的API
|
||||
// "/api/app/vid/module/:subModuleID": true,
|
||||
// "/api/app/vid/section/:sectionID": true,
|
||||
"/api/app/mine/resetpassword": true,
|
||||
"/api/app/mine/resetpassword/mobileverify": true,
|
||||
"/api/app/mine/login/h5": true,
|
||||
//"/api/app/comment/list": true,
|
||||
//"/api/app/mine/info": true,
|
||||
"/api/app/recommend/light/vids": true,
|
||||
"/api/app/store_wallet": true,
|
||||
"/api/app/aimate/sync": true,
|
||||
"/api/app/health/ping": true,
|
||||
}
|
||||
)
|
||||
|
||||
func GetTokenSecret() string {
|
||||
return appg.Conf.Base.JwtKey
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
UID uint64 `json:"uid"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Type uint8 `json:"type"`
|
||||
}
|
||||
|
||||
func GenToken(claims *Claims) (string, error) {
|
||||
secret := GetTokenSecret()
|
||||
args, _ := common.JSONStruct2Map(claims)
|
||||
token, err := crypt.CreateToken(secret, args)
|
||||
if err != nil {
|
||||
log.Error("genUserToken error", log.Any("claims", claims), log.E(err))
|
||||
}
|
||||
return token, err
|
||||
}
|
||||
|
||||
func ParseToken(token string) (*Claims, error) {
|
||||
secret := GetTokenSecret()
|
||||
claims, err := crypt.ParseToken(secret, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := Claims{}
|
||||
return &c, common.Map2JSONStruct(&c, claims)
|
||||
}
|
||||
|
||||
func tokenRedisKey(uid uint64) string {
|
||||
return redisconst.UserTokenKey(uid)
|
||||
}
|
||||
|
||||
func cacheToken(uid uint64, token string) {
|
||||
key := tokenRedisKey(uid)
|
||||
_ = appg.Redis.Set(key, token, redisconst.UserTokenExpire)
|
||||
}
|
||||
|
||||
func RevokeTokenCache(uids ...uint64) {
|
||||
keys := make([]string, len(uids))
|
||||
for i, uid := range uids {
|
||||
keys[i] = tokenRedisKey(uid)
|
||||
}
|
||||
_, _ = appg.Redis.Del(keys...)
|
||||
}
|
||||
|
||||
func auth(token string) (uint64, stderr.Code) {
|
||||
claims, err := ParseToken(token)
|
||||
if err != nil {
|
||||
return 0, stderr.InvalidToken
|
||||
}
|
||||
uid := claims.UID
|
||||
redisKey := tokenRedisKey(uid)
|
||||
redisToken, err := appg.Redis.Get(redisKey)
|
||||
if redisToken == nil || err != nil { //redis没有获取到token
|
||||
u, err := usermod.FindUserByUID(uid)
|
||||
if err != nil {
|
||||
log.Warn("[InvalidToken] cause by find user by uid error", log.Any("uid", uid), log.E(err))
|
||||
return 0, stderr.ErrNetWorkBusy
|
||||
}
|
||||
if u == nil {
|
||||
log.Warn("[InvalidToken] cause by not find user info", log.Any("uid", uid))
|
||||
return 0, stderr.UserIsNotExists
|
||||
}
|
||||
if u.Token != token {
|
||||
log.Warn("[InvalidToken] cause by current token not equal user's token", log.Any("uid", uid), log.Any("user's token", u.Token), log.Any("current token", token))
|
||||
return 0, stderr.InvalidToken
|
||||
}
|
||||
if u.HasLocked {
|
||||
return uid, stderr.ErrAccessForbid
|
||||
}
|
||||
cacheToken(uid, u.Token)
|
||||
} else {
|
||||
if *redisToken != token {
|
||||
log.Warn("[InvalidToken] cause by current token not equal redis's token", log.Any("uid", uid), log.Any("current token", token), log.Any("redis token", *redisToken))
|
||||
return 0, stderr.InvalidToken
|
||||
}
|
||||
}
|
||||
return uid, stderr.Success
|
||||
}
|
||||
|
||||
func Auth(ctx *gin.Context) {
|
||||
inWhiteList := false
|
||||
for url, ok := range whitelist {
|
||||
if ok && (strings.HasPrefix(ctx.Request.URL.Path, url) || strings.HasPrefix(ctx.FullPath(), url)) {
|
||||
inWhiteList = true
|
||||
break
|
||||
}
|
||||
}
|
||||
var token string
|
||||
var uid uint64
|
||||
var code stderr.Code
|
||||
t1 := ctx.Request.Header.Get("Authorization")
|
||||
t2 := ctx.Query("token") //为兼容m3u8
|
||||
if t1 != "" {
|
||||
token = t1
|
||||
}
|
||||
if t2 != "" {
|
||||
token = t2
|
||||
}
|
||||
if token == "" && !inWhiteList { // 请求的api未进入白名单且请求未带token,则视为非法请求,直接返回错误
|
||||
ctx.AbortWithStatusJSON(http.StatusOK, stderr.ErrNoToken.Struct())
|
||||
return
|
||||
}
|
||||
if token != "" { // 带token说明用户已登录,则解析并校验其token
|
||||
uid, code = auth(token)
|
||||
if code != stderr.Success {
|
||||
// 白名单接口允许客户端携带失效token继续按未登录态访问,例如用户被删除后重新登录。
|
||||
if inWhiteList {
|
||||
ctx.Set(constant.CtxUserID, 0)
|
||||
return
|
||||
}
|
||||
//兼容用户封禁时调用客服接口
|
||||
if code == stderr.ErrAccessForbid && ctx.Request.URL.Path == "/api/app/im/newSign" {
|
||||
ctx.Set(constant.CtxUserID, uid)
|
||||
return
|
||||
}
|
||||
handleErrCode(ctx, uid, code)
|
||||
return
|
||||
}
|
||||
ctx.Set(constant.CtxUserID, uid)
|
||||
return
|
||||
}
|
||||
ctx.Set(constant.CtxUserID, 0)
|
||||
}
|
||||
|
||||
// 处理返回的错误码
|
||||
func handleErrCode(ctx *gin.Context, uid uint64, code stderr.Code) {
|
||||
var data interface{}
|
||||
switch code {
|
||||
case stderr.ErrAccessForbid:
|
||||
var reason string
|
||||
u, err := usermod.FindUserByUID(uid)
|
||||
if err == nil && u != nil {
|
||||
reason = u.LockReason
|
||||
}
|
||||
data = gin.H{"uid": uid, "reason": reason}
|
||||
}
|
||||
common.ServeJSON(ctx, code, data)
|
||||
ctx.Abort()
|
||||
}
|
||||
|
||||
func getWebTokenSecret() string {
|
||||
return appg.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