小程序用户登录:安全性与用户体验的平衡

作者:汪奇超

前言

在移动互联网时代,移动应用作为连接用户和服务的桥梁,扮演着至关重要的角色。随着技术的不断发展和用户需求的日益多样化,小程序以轻量、便捷、快速启动的特点逐渐替代了传统App。

用户的信息对于服务来说也是至关重要,不仅仅只是私域运营以及大数据分析。而服务去保证用户行为是否真实有效,对于用户的认证则是所有后续操作的基础。

用户登录必要性

  • 安全性:只有认证的用户才能访问其相关信息。
  • 个性化定制:对于特定用户的特定行为轨迹,推送更适合用户的内容
  • 跨设备同步:对于用一用户的多设备访问,可以根据用户唯一标识(手机号,微信OpenId等信息)确定用户信息。
  • 数据分析:收集用户行为数据进行分析,确定产品功能的影响,以及分析改进方向。
  • 社交互动:产品可以提供用户之间的联动, 使用户之间更有互动感,也可以增加产品的辐射。

如何保证安全性

我们采用AccessTokenRefreshToken的方式鉴权业务接口。

为了安全,我们的AccessToken有效期一般设置较短,以避免被盗用。但过短的有效期会造成AccessToken经常过期。

所以有了RefreshToken,用来刷新AccessToken, 有效期稍长。

认证流程

用户体验的关注点

在小程序环境中,有他的特殊性,微信则是他的唯一标识,衍生出来的则是UnionId和OpenId。所以在小程序环境中,对于用户登录来说,对于用户的最佳体验来说就是无感。

如果产品有对于用户手机号的需求,那则需要用户操作将手机号与用户关联起来,原因是对于手机号微信等平台对于手机号有严格的把控,需要用户授权才可以获悉。

如何提升用户体验

现在来到了本篇文章的正题,如何来实现用户无感登录从而提升用户体验。

需要解决的问题

  • 自动登录
  • 如遇鉴权异常如何刷新token
  • 如何续接用户行为

思路

  • 通过平台API获取code,再通过服务端换取UnionId和OpenId,在服务端注册/登录用户,
  • 接口鉴权失败后执行refreshToken或者再次执行自动登录操作
  • 通过对request401错误的拦截,等待重置Token后重试被拦截的借口

解决方案

这里都以微信端举例,其他平台大同小异。

自动登录

wx.login(Object object) | 微信开放文档

typescript 复制代码
const login = async() {
  const { code, errMsg } = await wx.login();
  // 通过接口服务端调用微信服务端接口获取用户标识,获取用户信息
  const user = await loginFunc(code);
}

自动刷新Token

在request的错误拦截中判断哪些场景是不需要刷新
  • 401以外的接口错误
  • 用户已经主动退出登录
  • 某些特殊场景(业务决定)
重置用户信息
  • refreshToken
  • 静默登录
重试接口
  • 更新token
  • 重试被401拦截的接口
typescript 复制代码
/**
 * 匹配状态码
 * @param response
 * @param statusCode
 */
const matchStatusCode = (response, statusCode) => {
  return [response.status, response.data.code].includes(statusCode);
};

/**
 * 是否需要重试接口
 * @param error
 * @param updateConfig
 */
const whetherToRetry = async (error, updateConfig) => {
  const { user } = rootStore; // 全局变量
  if (
    !matchStatusCode(error?.response, 401) || // 去除401以外的错误
    user.isLogout || // 用户主动退出登录
    error?.config?.meta?.reTry === false // 某些特殊场景不需要刷新token
  )
    return false;
  await resetUser(); // 重置用户信息(重新获取token)
  if (!user.token) return false; // 获取新token失败则中断
  updateConfig({ // 更新token后重试接口
    headers: {
      Authorization: `Bearer ${user.token}`,
    },
  });
  return true;
}

/**
 * 重置用户
 */
const resetUser = async () => {
  const { user, common } = rootStore;
  if (user.isLogin) {
    await user.refreshToken();
  } else {
    await silentLogin(); // 静默登录
  }
};

现在就完成了第一步,自动刷新了Token。

续接用户行为

要做到用户无感,肯定是需要延续用户之前的操作,完成用户动作。

  • 使用userInited记录用户重置状态
  • 使用requestQuene来记录等待续接的接口请求队列
  • 重置用户完成后执行所有等待队列
typescript 复制代码
/**
 * 重置用户
 */
const resetUser = async () => {
  const { user, common } = rootStore;
  if (!user.userInited)
    return new Promise((resolve) => {
      common.setRequestQueue([...common.requestQueue, resolve]);
    });
  user.setUserInited(false);
  if (user.isLogin) {
    await user.refreshToken();
  } else {
    await silentLogin();
  }
  user.setUserInited(true);
};
typescript 复制代码
constructor() {
    reaction(
      () => this.userInited,
      (arg) => {
        if (arg) {
          const { common } = rootStore;
          common.requestQueue.forEach((item) => item()); // 执行等待队列
          common.setRequestQueue([]);
        }
      }
    );
  }

前置等待用户信息完成

如果用户信息还未完成初始化时,前置将需要鉴权的接口加入等待队列,减少无用的请求执行

typescript 复制代码
/**
 * 等待初始化用户结束
 */
const waitForUserInited = async () => {
  const { user, common } = rootStore;
  if (user.userInited) return Promise.resolve();
  // 未初始化则将请求阻塞在队列中
  return new Promise((resolve) => {
    common.setRequestQueue([...common.requestQueue, resolve]);
  });
};

http.tap('request', async (config) => {
  const { user } = rootStore;

  // 需要授权接口
  if (!NO_AUTH_URL.includes(config.rawURL)) {
    await waitForUserInited();
    config.headers.Authorization = `Bearer ${rootStore.user.token}`;
  }
  // ....
}

总结

  • 对于用户登录来说,安全性是最基础的保证,在这个前提下,需要做到优化用户体验,尽可以达到极致。
  • 减少用户不必要的操作,不仅对于用户体感上更佳,而且对于产品的转化也有更大的帮助。
  • 对于AT/RT都过期的情况,我们的处理方式是静默登录获取token,小程序环境下通过code获取openId关联私域会员信息

最后

📚 小茗文章推荐:

关注公众号「Goodme前端团队」,获取更多干货实践,欢迎交流分享~

相关推荐
ClareXi3 小时前
react项目通过http调用后端springboot服务最简单示例
spring boot·react.js·http
咔咔库奇15 小时前
react动态路由
前端·react.js·前端框架
yqcoder16 小时前
react 中 FC 模块作用
前端·react.js·前端框架
刘志辉17 小时前
react的创建与书写
前端·react.js·前端框架
奔跑草-19 小时前
【前端】深入浅出的React.js详解
前端·react.js·前端框架
小牛itbull20 小时前
ReactPress:深入解析技术方案设计与源码
javascript·react.js·reactpress
秃头女孩y21 小时前
【React】条件渲染——逻辑与&&运算符
前端·react.js·前端框架
小满zs21 小时前
React第十五章(useEffect)
前端·react.js
破浪前行·吴1 天前
使用@react-three/fiber,@mkkellogg/gaussian-splats-3d加载.splat,.ply,.ksplat文件
前端·react.js·three.js
咔咔库奇1 天前
react之了解jsx
前端·javascript·react.js