对于一些付费会员或者一些商业项目,为了保证单个用户的账号权益不会被滥用,并且提高系统的安全性,我们会限制单个账号在同一时间内只能有一台设备登录,
来给系统添加共享账号的检测能能力
这里是根据jwt实现的,要实现单设备登录限制,这里最简单的方法也就是通过存入redis的方法,
生成jwt令牌之后,将令牌反馈前端的同时将其存入到redis中,而后在拦截器校验的时候,先校验jwt是否正确,再校验jwt是否和redis中的jwt一致,如果一致则说明是同一个用户。
上代码:
func LoginPwd(context *gin.Context) {var user entity.Userdb := common.GetDB()username := context.PostForm("username")password := context.PostForm("password")if len(username) < 5 || len(username) > 16 {response.Fail(context, "用户名长度在5-16之间", nil)return}if len(password) < 5 || len(password) > 16 {response.Fail(context, "密码长度在5-16之间", nil)return}if db.Where("name = ?", username).First(&user).RecordNotFound() {response.Fail(context, "用户名不存在", nil)return}if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {response.Fail(context, "密码错误", nil)return}client := common.CreateClient()生成唯一id//uuid := generateId()//common.SetKeyValue(client, UserField, strconv.FormatInt(uuid, 20))//返回tokentoken, err := common.ReleaseToken(user)if err != nil {response.Result(context, http.StatusInternalServerError, 500, "系统错误", nil)log.Println(err)return}err = common.SetKeyValueWithExpiration(client, JWT_USER_KEY, token, 1*time.Hour)if err != nil {response.Result(context, http.StatusInternalServerError, 500, "系统错误", nil)log.Println(err)return}// 在登录成功的响应中包含IP地址,或者根据需求进行记录、审计等操作log.Printf("User logged in with IP: %s", ip)response.Success(context, "登录成功", gin.H{"token": token,})}
中间件校验代码:
//连接redisclient := common.CreateClient()//检查redis中是否有redisJwtValue, err := common.GetValue(client, JWT_USER_KEY)if err != nil {fmt.Println("获取jwt令牌失败", err)context.JSON(401, gin.H{"code": 401, "msg": "权限不足"})context.Abort()return}//比较jwt令牌,如果传来的令牌与redis中的令牌不同,则说明令牌已失效if JwtValue != tokenString {fmt.Println("jwt令牌已失效")context.JSON(401, gin.H{"code": 401, "msg": "权限不足"})context.Abort()return}
那么,如何实现在同一设备上登录不被顶掉呢?
比如同一个电脑不同的浏览器的话,登录的话就会有不同的jwt,此时如何让不同的浏览器能实现登录不被顶掉账号嘞(也就是单设备多客户端登录)
这里可以操作一下ip地址
在登录接口这里添加存储ip地址
// 获取客户端IPip := context.ClientIP()// 在登录成功的响应中包含IP地址,或者根据需求进行记录、审计等操作log.Printf("User logged in with IP: %s", ip)err = common.SetKeyValue(client, UserIp, ip)
然后在中间件检测这里,如果ip相同,且jwt可以识别的情况下,就允许其直接通过,这样就可以实现,在同一设备上,登录同一个账号不被顶下去
func AuthMiddleware() gin.HandlerFunc {return func(context *gin.Context) {tokenString := context.GetHeader("Authorization")if tokenString == "" { //|| !strings.HasPrefix(tokenString, "Bearer "context.JSON(401, gin.H{"code": 401, "msg": "权限不足"})context.Abort()return}//tokenString = tokenString[7:]token, claims, err := common.ParseToken(tokenString)log.Println("token:", token, "claims:", claims, "err:", err)if err != nil || !token.Valid { //解析失败或者token无效context.JSON(401, gin.H{"code": 401, "msg": "权限不足"})context.Abort()return}//连接redisclient := common.CreateClient()//检查redis中是否有redisJwtValue, err := common.GetValue(client, JWT_USER_KEY)if err != nil {fmt.Println("获取jwt令牌失败", err)context.JSON(401, gin.H{"code": 401, "msg": "权限不足"})context.Abort()return}ip, _ := common.GetValue(client, UserIp)if ip == context.ClientIP() {//ip相等说明是同一个设备log.Println("ip相同,允许同设备放行")//获取用户信息userId := claims.UserIddb := common.GetDB()var user entity.Userdb.First(&user, userId)if user.ID == 0 { //用户不存在context.JSON(401, gin.H{"code": 401, "msg": "权限不足"})context.Abort()return}//用户存在,将user信息写入下上文context.Set("user", user)context.Next()}//比较jwt令牌,如果传来的令牌与redis中的令牌不同,则说明令牌已失效if JwtValue != tokenString {fmt.Println("jwt令牌已失效")context.JSON(401, gin.H{"code": 401, "msg": "权限不足"})context.Abort()return}//获取用户信息userId := claims.UserIddb := common.GetDB()var user entity.Userdb.First(&user, userId)if user.ID == 0 { //用户不存在context.JSON(401, gin.H{"code": 401, "msg": "权限不足"})context.Abort()return}//用户存在,将user信息写入下上文context.Set("user", user)context.Next()}
}