文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Token:如何降低用户身份鉴权的流量压力?

2024-11-29 18:06

关注

这种用户鉴权方式的优势在于,所有用户信息都存储在服务端,不会暴露任何敏感数据给客户端,同时每个登录用户都有共享的 Session 缓存空间。但是,随着网站流量的增长,这种设计也会暴露出明显的缺点——用户中心的身份鉴权在高并发下表现不稳定。

具体而言,用户中心需要维护大量的 Session 缓存,并且频繁被各个业务系统访问。如果缓存出现故障,所有依赖它的子系统将无法进行用户身份确认,导致服务中断。这主要是由于 Session 缓存与各子系统的高耦合。每次请求都至少需要访问一次缓存,因此缓存的容量和响应速度直接影响了全站的 QPS 上限,降低了系统的隔离性,使各子系统之间互相影响。

那么,如何降低用户中心与各子系统之间的耦合度,从而提高系统性能呢?接下来我们一起来探讨。

JWT 登陆和 token 校验

常见方式是采用签名加密的 token,这是登录的一个行业标准,即 JWT(JSON Web Token):

图片

上图就是 JWT 的登陆流程,用户登录后会将用户信息放到一个加密签名的 token 中,每次请求都把这个串放到 header 或 cookie 内带到服务端,服务端直接将这个 token 解开即可直接获取到用户的信息,无需和用户中心做任何交互请求。

token 生成代码如下:

import "github.com/dgrijalva/jwt-go"


//签名所需混淆密钥 不要太简单 容易被破解
//也可以使用非对称加密,这样可以在客户端用公钥验签
var secretString = []byte("jwt secret string 137 rick") 


type TokenPayLoad struct {
    UserId   uint64 `json:"userId"` //用户id
    NickName string `json:"nickname"` //昵称
    jwt.StandardClaims //私有部分
}


// 生成JWT token
func GenToken(userId uint64, nickname string) (string, error) {
    c := TokenPayLoad{
        UserId: userId, //uid
        NickName: nickname, //昵称
//这里可以追加一些其他加密的数据进来
//不要明文放敏感信息,如果需要放,必须再加密


//私有部分
        StandardClaims: jwt.StandardClaims{
//两小时后失效
            ExpiresAt: time.Now().Add(2 * time.Hour).Unix(),
//颁发者
            Issuer:    "geekbang",
        },
    }
//创建签名 使用hs256
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
// 签名,获取token结果
return token.SignedString(secretString)
}

可以看出,这种 Token 内部包含了过期时间,接近过期的 Token 会在客户端自动与服务端通信进行更新。这样设计可以大大增加恶意截取客户端 Token 并伪造用户身份的难度。同时,服务端还可以实现与用户中心的解耦,业务服务端只需解析请求中的 Token 就能获取用户信息,而不必每次请求都去访问用户中心。Token 的刷新完全可以由客户端主动向用户中心发起,而无需业务服务端频繁请求用户中心来更换 Token。

那么,JWT(JSON Web Token)是如何保证数据不会被篡改并确保数据完整性的呢?接下来我们来看看它的组成。

图片

JWT token 解密后的数据结构如下图所示:

//header
//加密头
{
"alg": "HS256", // 加密算法,注意检测个别攻击会在这里设置为none绕过签名
"typ": "JWT" //协议类型
}


//PAYLOAD
//负载部分,存在JWT标准字段及我们自定义的数据字段
{
"userid": "9527", //我们放的一些明文信息,如果涉及敏感信息,建议再次加密
"nickname": "Rick.Xu", // 我们放的一些明文信息,如果涉及隐私,建议再次加密
"iss": "geekbang",
"iat": 1516239022, //token发放时间
"exp": 1516246222, //token过期时间
}


//签名
//签名用于鉴定上两段内容是否被篡改,如果篡改那么签名会发生变化
//校验时会对不上

JWT 如何验证 token 是否有效,还有 token 是否过期、是否合法,具体方法如下:

func DecodeToken(token string) (*TokenPayLoad, error) {
    token, err := jwt.ParseWithClaims(token, &TokenPayLoad{}, func(tk *jwt.Token) (interface{}, error) {
return secret, nil
    })
if err != nil {
return nil, err
    }
if decodeToken, ok := token.Claims.(*TokenPayLoad); ok && token.Valid {
return decodeToken, nil
    }
return nil, errors.New("token wrong")
}

JWT(JSON Web Token)的解码相对简单,第一部分和第二部分都是通过 Base64 编码的。解码这两部分即可获取到 payload 中的所有数据,其中包括用户昵称、UID、用户权限和 Token 的过期时间。要验证 Token 是否过期,只需将其中的过期时间与当前时间进行对比,即可确认 Token 是否有效。而验证 Token 的合法性则通过 签名验证来完成。任何对信息的修改都无法通过签名验证。如果 Token 通过了签名验证,就表明它没有被篡改过,是一个合法的 Token,可以直接使用。

这个过程如下图所示:

图片

通过 Token 方式,可以显著减轻用户中心的压力,不再需要频繁访问用户信息接口。各业务服务端只需解码并验证 Token 的合法性,即可直接获取用户信息。然而,这种方式也存在一些缺点。比如,当用户被拉黑后,客户端通常要等到 Token 过期才会自动登出,这会导致管理上的一定延迟。

如果希望实现实时管理,可以在服务端暂存新生成的 Token,并在每次用户请求时与缓存中的 Token 进行对比。不过,这样的操作会影响系统性能,因此少数公司会采用这种方式。为了提高 JWT 系统的安全性,Token 通常设置较短的过期时间,通常为十五分钟左右。Token 过期后,客户端会自动向服务端请求更新。

token 的更换和离线

那么如何对 JWT 的 token 进行更换和离线验签呢?具体的服务端换签很简单,只要客户端检测到当前的 token 快过期了,就主动请求用户中心更换 token 接口,重新生成一个离当前还有十五分钟超时的 token。但是期间如果超过十五分钟还没换到,就会导致客户端登录失败。为了减少这类问题,同时保证客户端长时间离线仍能正常工作,行业内普遍使用双 token 方式,具体你可以看看后面的流程图:

图片

在这个方案中,使用了两种 Token:

  1. Refresh Token:用于更换 Access Token,有效期为 30 天。
  2. Access Token:用于存储当前用户信息和权限信息,每隔 15 分钟进行一次更换。

当客户端尝试请求用户中心进行 Token 更换但失败,且客户端处于离线状态时,只要本地的 Refresh Token 未过期,系统仍然能够正常运作。客户端可以持续使用 Access Token,直到 Refresh Token 到期,此时系统会提示用户重新登录。通过这种方式,即便用户中心出现故障,业务系统也可以正常运转一段时间,提升了系统的健壮性和用户体验。

用户中心检测更换 token 的实现如下:

//如果还有五分钟token要过期,那么换token
if decodeToken.StandardClaims.ExpiresAt < TimestampNow() - 300 {
//请求下用户中心,问问这个人禁登陆没
//....略具体


//重新发放token
  token, err := GenToken(.....)
if err != nil {
return nil, err
  }
//更新返回cookie中token
  resp.setCookie("xxxx", token)
}

安全建议

在使用 JWT 方案时,除了代码注释中提到的内容外,还有一些关键注意事项值得留意:

  1. 确保通讯安全:使用 HTTPS 协议传输数据,以降低 Token 被拦截的风险。
  2. 限制 Token 的更换频率:要控制 Token 的更换次数,并定期刷新 Token。例如,限制用户的 Access Token 每天只能更换 50 次,如果超出次数则要求用户重新登录,同时每 15 分钟更换一次 Token。这样可以减少 Token 被盗后的潜在影响。
  3. 安全存储 Web Token:对于 Web 用户,当 Token 存储在 Cookie 中时,建议设置 HttpOnlySameSite=Strict 标记,以防止 Cookie 被恶意脚本窃取。
来源:二进制跳动内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯