|
|
package middleware
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
|
|
|
"net/http"
|
|
|
"runtime"
|
|
|
"runtime/debug"
|
|
|
"sort"
|
|
|
"strings"
|
|
|
"time"
|
|
|
|
|
|
"github.com/dgrijalva/jwt-go"
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
"github.com/spf13/viper"
|
|
|
)
|
|
|
|
|
|
// 用户信息类,作为生成token的参数
|
|
|
type UserClaims struct {
|
|
|
ID string `json:"userId"`
|
|
|
Name string `json:"name"`
|
|
|
Phone string `json:"phone"`
|
|
|
//jwt-go提供的标准claim
|
|
|
jwt.StandardClaims
|
|
|
}
|
|
|
|
|
|
var (
|
|
|
//自定义的token秘钥
|
|
|
secret = []byte("16849841325189456f487")
|
|
|
//该路由下不校验token
|
|
|
noVerify = []string{
|
|
|
"/",
|
|
|
"/welcome",
|
|
|
"/wstest",
|
|
|
"/ldapi/v1/websocket/ccuwebsocket",
|
|
|
"/user/login",
|
|
|
"/ldapi/v1/web/api/service/auth/login",
|
|
|
"/ldapi/v1/web/api/service/heartbeat/ping",
|
|
|
"/ldapi/v1/app/api/service/auth/login",
|
|
|
"/ldapi/v1/app/api/service/heartbeat/ping",
|
|
|
"/ldapi/v1/web/api/service/ping",
|
|
|
}
|
|
|
//token有效时间(纳秒)
|
|
|
effectTime = 48 * time.Hour
|
|
|
)
|
|
|
|
|
|
// 生成token
|
|
|
func GenerateToken(claims *UserClaims) string {
|
|
|
//设置token有效期,也可不设置有效期,采用redis的方式
|
|
|
// 1)将token存储在redis中,设置过期时间,token如没过期,则自动刷新redis过期时间,
|
|
|
// 2)通过这种方式,可以很方便的为token续期,而且也可以实现长时间不登录的话,强制登录
|
|
|
//本例只是简单采用 设置token有效期的方式,只是提供了刷新token的方法,并没有做续期处理的逻辑
|
|
|
claims.ExpiresAt = time.Now().Add(effectTime).Unix()
|
|
|
//生成token
|
|
|
sign, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secret)
|
|
|
if err != nil {
|
|
|
//这里因为项目接入了统一异常处理,所以使用panic并不会使程序终止,如不接入,可使用原始方式处理错误
|
|
|
//接入统一异常可参考 https://blog.csdn.net/u014155085/article/details/106733391
|
|
|
panic(err)
|
|
|
}
|
|
|
return sign
|
|
|
}
|
|
|
|
|
|
// 验证token
|
|
|
func JwtVerify(c *gin.Context) {
|
|
|
//过滤是否验证token
|
|
|
log.Print(c.Request.RequestURI)
|
|
|
if UriinArrayWithSort(c.Request.RequestURI, noVerify) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
token := c.GetHeader(viper.GetString("app.tokenKey"))
|
|
|
if token == "" {
|
|
|
panic("token not exist !")
|
|
|
}
|
|
|
//验证token,并存储在请求中
|
|
|
//c.Set("user", parseToken(token))
|
|
|
}
|
|
|
|
|
|
// 解析Token
|
|
|
func parseToken(tokenString string) *UserClaims {
|
|
|
//解析token
|
|
|
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
|
return secret, nil
|
|
|
})
|
|
|
if err != nil {
|
|
|
panic(err)
|
|
|
}
|
|
|
claims, ok := token.Claims.(*UserClaims)
|
|
|
if !ok {
|
|
|
panic("token is valid")
|
|
|
}
|
|
|
return claims
|
|
|
}
|
|
|
|
|
|
// 获取用户ing
|
|
|
func GetUsernameFromToken(tokenString string) string {
|
|
|
//解析token
|
|
|
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
|
return secret, nil
|
|
|
})
|
|
|
if err != nil {
|
|
|
// panic(err)
|
|
|
fmt.Println(err)
|
|
|
return ""
|
|
|
}
|
|
|
claims, ok := token.Claims.(*UserClaims)
|
|
|
if !ok {
|
|
|
panic("token is valid")
|
|
|
}
|
|
|
return claims.Name
|
|
|
}
|
|
|
|
|
|
// 更新token
|
|
|
func Refresh(tokenString string) string {
|
|
|
jwt.TimeFunc = func() time.Time {
|
|
|
return time.Unix(0, 0)
|
|
|
}
|
|
|
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
|
return secret, nil
|
|
|
})
|
|
|
if err != nil {
|
|
|
panic(err)
|
|
|
}
|
|
|
claims, ok := token.Claims.(*UserClaims)
|
|
|
if !ok {
|
|
|
panic("token is valid")
|
|
|
}
|
|
|
jwt.TimeFunc = time.Now
|
|
|
claims.StandardClaims.ExpiresAt = time.Now().Add(2 * time.Hour).Unix()
|
|
|
return GenerateToken(claims)
|
|
|
}
|
|
|
|
|
|
func UriinArrayWithSort(target string, str_array []string) bool {
|
|
|
sort.Strings(str_array)
|
|
|
index := sort.SearchStrings(str_array, target)
|
|
|
log.Printf("index: %v\n", index)
|
|
|
//index的取值:[0,len(str_array)]
|
|
|
if index < len(str_array) && (str_array[index] == target || strings.HasPrefix(target, "/user/login?redirect=")) {
|
|
|
//需要注意此处的判断,先判断 &&左侧的条件,如果不满足则结束此处判断,不会再进行右侧的判断
|
|
|
return true
|
|
|
}
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
func UriinArray(target string, str_array []string) bool {
|
|
|
for _, element := range str_array {
|
|
|
if target == element {
|
|
|
return true
|
|
|
}
|
|
|
}
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
func Recover(c *gin.Context) {
|
|
|
// 先声明map
|
|
|
var resultmap map[string]interface{}
|
|
|
// 再使用make函数创建一个非nil的map,nil map不能赋值
|
|
|
resultmap = make(map[string]interface{})
|
|
|
|
|
|
defer func() {
|
|
|
if r := recover(); r != nil {
|
|
|
|
|
|
// 获取堆栈信息
|
|
|
stackTrace := debug.Stack()
|
|
|
// 获取调用信息
|
|
|
pc, file, line, ok := runtime.Caller(1)
|
|
|
if ok {
|
|
|
fn := runtime.FuncForPC(pc)
|
|
|
log.Printf("panic: %v\n", r)
|
|
|
log.Printf("occurred in %s, %s:%d\nStack trace:\n%s", fn.Name(), file, line, stackTrace)
|
|
|
} else {
|
|
|
log.Printf("panic: %v\nStack trace:\n%s", r, stackTrace)
|
|
|
}
|
|
|
|
|
|
//打印错误堆栈信息
|
|
|
log.Printf("panic: %v\n", r)
|
|
|
debug.PrintStack()
|
|
|
//封装通用json返回
|
|
|
//c.JSON(http.StatusOK, Result.Fail(errorToString(r)))
|
|
|
//Result.Fail不是本例的重点,因此用下面代码代替
|
|
|
resultmap["status"] = http.StatusUnauthorized
|
|
|
resultmap["success"] = true
|
|
|
resultmap["message"] = errorToString(r)
|
|
|
c.JSON(http.StatusUnauthorized, resultmap)
|
|
|
// c.Redirect(http.StatusFound, viper.GetString("app.contextPath"))
|
|
|
//终止后续接口调用,不加的话recover到异常后,还会继续执行接口里后续代码
|
|
|
c.Abort()
|
|
|
}
|
|
|
}()
|
|
|
//加载完 defer recover,继续后续接口调用
|
|
|
c.Next()
|
|
|
}
|
|
|
|
|
|
// recover错误,转string
|
|
|
func errorToString(r interface{}) string {
|
|
|
switch v := r.(type) {
|
|
|
case error:
|
|
|
return v.Error()
|
|
|
default:
|
|
|
return r.(string)
|
|
|
}
|
|
|
}
|