用JWT打造无感登录体验,用户和开发者都点赞!

191 阅读4分钟

引言:为什么要登录凭证

还记得小时候进游乐场要盖章吗?盖了章,随时进出没人拦你。Web世界也一样,用户登录后,服务器要给你个“通行证”。JWT(JSON Web Token)就像发你一张防伪二维码,走哪都能扫,轻便又安全!


一、JWT是什么?

JWT,全称JSON Web Token,是一种基于JSON的开放标准(RFC 7519),用于在网络应用环境间安全地传递声明信息。说人话:它就是一张数字签名的“身份证”,你带着它,服务器就能确认你是谁。

1.1 结构大揭秘

JWT长这样:

header.payload.signature
  • Header:声明类型(JWT)和算法(如HS256)
  • Payload:装你要传的信息(比如用户ID、昵称、权限等)
  • Signature:用密钥签名,防篡改

举个栗子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJqYWNrIiwiaWF0IjoxNjg4ODg4ODg4fQ.abc123xyz456

是不是像极了超市小票后面那串神秘字符?


二、JWT在项目中的实战应用与代码详解

2.1 登录流程全解析

  1. 用户输入用户名和密码,发起登录请求
  2. 服务器校验通过后,生成JWT(短token+长token)
  3. 前端收到token,存到localStorage
  4. 之后每次请求都带上token,后端校验通过才放行
  5. token过期?用长token刷新短token,无感体验,用户丝滑如初恋!

2.2 代码实战逐行详解

2.2.1 登录接口(后端)

router.post('/login', async (ctx) => {
    // 1. 获取请求体中的账号和密码
    let { username, password } = ctx.request.body
    // 2. 防SQL注入,转义特殊字符
    username = escape(username)
    password = escape(password)
    try {
        // 3. 查询数据库,校验账号密码
        const res = await userLogin(username, password)
        if (res.length) {
            // 4. 组装用户数据
            let data = { id: res[0].id, username: res[0].username, nickname: res[0].nickname, createTime: res[0].create_time }
            // 5. 生成短token(1小时有效)和长token(7天有效)
            const access_token = sign(data, '1h')
            const refresh_token = sign(data, '7d')
            // 6. 返回token和用户信息
            ctx.body = { code: '1', msg: '登录成功', data, access_token, refresh_token }
        } else {
            ctx.body = { code: '0', msg: '账号或密码错误', data: {} }
        }
    } catch (error) {
        ctx.body = { code: '-1', msg: '服务器异常', data: error }
    }
})

重点解析:

  • escape函数防止SQL注入,安全第一!
  • sign方法用密钥对用户数据签名,生成JWT。
  • 返回两个token,短token用于日常鉴权,长token用于刷新。

2.2.2 前端登录逻辑

axios.post('/user/login', values).then(res => {
  toast.success('登录成功')
  // 1. 保存短token和长token到localStorage
  localStorage.setItem('access_token', res.access_token)
  localStorage.setItem('refresh_token', res.refresh_token)
  // 2. 保存用户信息
  localStorage.setItem('userInfo', JSON.stringify(res.data))
  // 3. 跳转到主页面
  navigate('/noteClass')
})

重点解析:

  • 登录成功后,token和用户信息本地持久化,页面跳转。
  • 这样后续所有请求都能自动带上token。

2.2.3 请求拦截与token自动刷新

axios.interceptors.request.use(request => {
  // 1. 每次请求自动携带access_token
  const token = localStorage.getItem('access_token')
  if (token) {
    request.headers.Authorization = token
  }
  return request
})

axios.interceptors.response.use(
  (response) => { /* ... */ },
  (res) => {
    // 2. 如果短token失效,自动用refresh_token刷新
    if (res.response.status === 401) {
      const refresh_token = localStorage.getItem('refresh_token')
      if (refresh_token) {
        axios.post('./user/refresh', { refresh_token }).then(res => {
          if (res.code === '1') {
            // 3. 刷新成功,更新token并重发原请求
            localStorage.setItem('access_token', res.access_token)
            localStorage.setItem('refresh_token', res.refresh_token)
            originalRequest.headers.Authorization = res.access_token
            return axios(originalRequest)
          }
        })
      } else {
        toast.error('请先登录');
        setTimeout(() => { window.location.href = '/login'; }, 2000);
      }
    }
  }
)

重点解析:

  • 请求拦截器自动加token,响应拦截器自动处理token过期。
  • 刷新机制让用户体验无缝衔接。

2.2.4 后端token校验中间件

function verify() {
    return async (ctx, next) => {
        // 1. 从请求头获取token
        const token = ctx.headers.authorization
        if (token) {
            try {
                // 2. 校验token有效性
                const decoded = jwt.verify(token, '666')
                if (decoded.id) {
                    // 3. 校验通过,挂载用户ID,放行
                    ctx.userId = decoded.id
                    await next()
                }
            } catch (error) {
                // 4. token无效,返回401
                ctx.status = 401
                ctx.body = { code: '0', msg: '登录失效' }
            }
        } else {
            ctx.status = 401
            ctx.body = { code: '0', msg: '请先登录' }
        }
    }
}

重点解析:

  • 校验token合法性,防止未授权访问。
  • 校验失败直接返回401,安全兜底。

2.2.5 注册接口安全细节

router.post('/register', async (ctx) => {
    let { nickname, username, password } = ctx.request.body
    if (!nickname || !username || !password) {
        ctx.body = { code: '0', msg: '账号密码昵称不能为空' }
    }
    // 1. 所有字段escape防注入
    username = escape(username)
    nickname = escape(nickname)
    password = escape(password)
    // 2. 检查账号是否已存在
    // 3. 数据库写入
    // ...
})

重点解析:

  • 注册同样要防注入,所有字段都escape。
  • 账号唯一性校验,防止重复注册。

三、JWT的优缺点大起底

优点

  • 无需服务器存储会话,扩展性强,适合分布式架构
  • 前后端分离,移动端、Web端通用
  • 支持自定义载荷,灵活扩展

缺点

  • token一旦泄露,风险极大(所以要用HTTPS!)
  • 无法主动失效(除非黑名单机制)
  • 载荷过大影响性能

四、实战Tips与安全建议

  1. 签名密钥要复杂,别用123456或password(不然黑客都笑了😀)
  2. token只走HTTPS,明文传输等于裸奔
  3. 设置合理过期时间,短token+长token组合拳
  4. 敏感信息别放payload,JWT不是保险箱
  5. 刷新机制要完善,体验和安全两手抓
  6. escape防注入,注册和登录都要做!

总结:JWT让Web认证更优雅

JWT就像你出门带的身份证,轻便、通用、还能防伪。配合合理的刷新机制和安全措施,既能提升用户体验,又能保证系统安全。希望本文能让你对JWT有更深刻的理解,项目实战也能用得得心应手!

(๑•̀ㅂ•́)و✧

既然已经看到这了吧, 请动动发财小手, 点点小赞吧👍