Penny Lens 小程序端认证模块详解

2025年1月27日
8 分钟阅读
作者:Penny Lens Team

认证模块

🔐 概述

认证模块是 Penny Lens 应用的核心安全模块,负责用户身份验证、多平台登录、会话管理等功能。支持用户名/密码、微信、支付宝等多种登录方式。

🏗️ 模块架构

核心组件

src/
├── pages/auth/ # 认证页面
│ ├── index.vue # 身份令牌页面
│ └── qr-login.vue # 二维码登录页面
├── pages/mine/login.vue # 登录/注册页面
├── store/user.ts # 用户状态管理
├── services/api/user.ts # 用户API服务
├── services/api/auth.ts # 认证API服务
├── utils/auth.ts # 认证工具函数
├── utils/uniapi/auth.ts # UniApp认证API
├── utils/uniapi/platformLogin.ts # 平台登录工具
├── types/user.ts # 用户类型定义
├── types/auth.ts # 认证类型定义
└── router/guard.ts # 路由守卫

🔑 认证方式

1. 用户名/密码登录

功能描述: 传统的用户名和密码登录方式

实现流程:

  1. 用户输入用户名和密码
  2. 前端验证输入格式
  3. 发送登录请求到后端
  4. 后端验证用户凭据
  5. 返回JWT令牌和用户信息
  6. 前端保存令牌和用户信息

代码示例:

// 登录请求
interface LoginRequest {
  username: string;      // 用户名或邮箱
  password: string;      // 密码
  deviceId: string;      // 设备ID
  deviceInfo: string;    // 设备信息
}
 
// 登录响应
interface LoginResponse {
  token: string;         // JWT令牌
  userInfo: UserInfo;    // 用户信息
  expiresIn: number;     // 过期时间(秒)
}
 
// 登录实现
async function login(credentials: LoginRequest): Promise<LoginResponse> {
  const response = await userApi.login(credentials)
  if (response.code === 200) {
    // 保存令牌
    setToken(response.data.token)
    // 保存用户信息
    setUserInfo(response.data.userInfo)
    return response.data
  }
  throw new Error(response.message)
}

2. 微信登录

功能描述: 通过微信授权登录

实现流程:

  1. 调用微信授权API获取授权码
  2. 发送授权码到后端
  3. 后端通过授权码获取微信用户信息
  4. 创建或绑定用户账号
  5. 返回JWT令牌和用户信息

代码示例:

// 微信登录
async function wechatLogin(): Promise<LoginResponse> {
  try {
    // 获取微信授权码
    const authResult = await uni.login({
      provider: 'weixin'
    })
    
    if (authResult[1].code) {
      // 发送授权码到后端
      const response = await authApi.wechatLogin({
        code: authResult[1].code,
        deviceId: getDeviceId(),
        deviceInfo: getDeviceInfo()
      })
      
      if (response.code === 200) {
        setToken(response.data.token)
        setUserInfo(response.data.userInfo)
        return response.data
      }
    }
    throw new Error('微信登录失败')
  } catch (error) {
    console.error('微信登录错误:', error)
    throw error
  }
}

3. 支付宝登录

功能描述: 通过支付宝授权登录

实现流程:

  1. 调用支付宝授权API获取授权码
  2. 发送授权码到后端
  3. 后端通过授权码获取支付宝用户信息
  4. 创建或绑定用户账号
  5. 返回JWT令牌和用户信息

代码示例:

// 支付宝登录
async function alipayLogin(): Promise<LoginResponse> {
  try {
    // 获取支付宝授权码
    const authResult = await uni.login({
      provider: 'alipay'
    })
    
    if (authResult[1].authCode) {
      // 发送授权码到后端
      const response = await authApi.alipayLogin({
        authCode: authResult[1].authCode,
        deviceId: getDeviceId(),
        deviceInfo: getDeviceInfo()
      })
      
      if (response.code === 200) {
        setToken(response.data.token)
        setUserInfo(response.data.userInfo)
        return response.data
      }
    }
    throw new Error('支付宝登录失败')
  } catch (error) {
    console.error('支付宝登录错误:', error)
    throw error
  }
}

4. 二维码登录

功能描述: 通过扫描二维码快速登录

实现流程:

  1. 生成登录二维码
  2. 用户扫描二维码
  3. 确认登录操作
  4. 完成登录流程

代码示例:

// 二维码登录
interface QRLoginSession {
  qrCode: string;        // 二维码内容
  sessionKey: string;    // 会话密钥
  expiresIn: number;     // 过期时间
}
 
// 生成登录二维码
async function generateQRCode(): Promise<QRLoginSession> {
  const response = await authApi.generateQRCode()
  return response.data
}
 
// 检查登录状态
async function checkQRLoginStatus(sessionKey: string): Promise<QRLoginStatus> {
  const response = await authApi.checkQRLoginStatus({ sessionKey })
  return response.data
}
 
// 确认登录
async function confirmQRLogin(sessionKey: string): Promise<LoginResponse> {
  const response = await authApi.confirmQRLogin({
    sessionKey,
    deviceId: getDeviceId(),
    deviceInfo: getDeviceInfo()
  })
  
  if (response.code === 200) {
    setToken(response.data.token)
    setUserInfo(response.data.userInfo)
    return response.data
  }
  throw new Error(response.message)
}

👤 用户管理

用户注册

功能描述: 新用户注册账号

实现流程:

  1. 用户填写注册信息
  2. 前端验证输入格式
  3. 发送注册请求到后端
  4. 后端创建用户账号
  5. 返回JWT令牌和用户信息

代码示例:

// 注册请求
interface RegisterRequest {
  username: string;      // 用户名(唯一)
  password: string;      // 密码
  email?: string;        // 邮箱(可选)
  deviceId: string;      // 设备ID
  deviceInfo: string;    // 设备信息
}
 
// 注册实现
async function register(userData: RegisterRequest): Promise<LoginResponse> {
  const response = await userApi.register(userData)
  if (response.code === 200) {
    setToken(response.data.token)
    setUserInfo(response.data.userInfo)
    return response.data
  }
  throw new Error(response.message)
}

用户信息管理

功能描述: 获取和更新用户信息

代码示例:

// 获取用户信息
async function getUserInfo(): Promise<UserInfo> {
  const response = await userApi.getUserInfo()
  return response.data
}
 
// 更新用户信息
async function updateUserInfo(userData: Partial<UserInfo>): Promise<UserInfo> {
  const response = await userApi.updateUserInfo(userData)
  if (response.code === 200) {
    // 更新本地用户信息
    updateLocalUserInfo(response.data)
    return response.data
  }
  throw new Error(response.message)
}

账号绑定管理

功能描述: 管理多种登录方式的绑定

代码示例:

// 获取绑定状态
async function getBindStatus(): Promise<BindStatus> {
  const response = await userApi.getBindStatus()
  return response.data
}
 
// 邮箱绑定
async function bindEmail(email: string, password: string): Promise<void> {
  const response = await userApi.bindEmail({ email, password })
  if (response.code !== 200) {
    throw new Error(response.message)
  }
}
 
// 邮箱解绑
async function unbindEmail(): Promise<void> {
  const response = await userApi.unbindEmail()
  if (response.code !== 200) {
    throw new Error(response.message)
  }
}
 
// 平台绑定
async function bindPlatform(platform: 'wechat' | 'alipay', authCode: string): Promise<void> {
  const response = await userApi.platformBind({ platform, authCode })
  if (response.code !== 200) {
    throw new Error(response.message)
  }
}
 
// 平台解绑
async function unbindPlatform(platform: 'wechat' | 'alipay'): Promise<void> {
  const response = await userApi.platformUnbind({ platform })
  if (response.code !== 200) {
    throw new Error(response.message)
  }
}

🔒 安全机制

Token管理

功能描述: JWT令牌的生成、验证、刷新和失效

代码示例:

// Token工具函数
export const tokenUtils = {
  // 保存令牌
  setToken(token: string): void {
    uni.setStorageSync('token', token)
  },
  
  // 获取令牌
  getToken(): string | null {
    return uni.getStorageSync('token') || null
  },
  
  // 删除令牌
  removeToken(): void {
    uni.removeStorageSync('token')
  },
  
  // 检查令牌是否过期
  isTokenExpired(token: string): boolean {
    try {
      const payload = JSON.parse(atob(token.split('.')[1]))
      return Date.now() >= payload.exp * 1000
    } catch {
      return true
    }
  },
  
  // 刷新令牌
  async refreshToken(): Promise<string> {
    const refreshToken = uni.getStorageSync('refreshToken')
    if (!refreshToken) {
      throw new Error('没有刷新令牌')
    }
    
    const response = await authApi.refreshToken({ refreshToken })
    if (response.code === 200) {
      setToken(response.data.token)
      return response.data.token
    }
    throw new Error(response.message)
  }
}

会话管理

功能描述: 用户会话的创建、维护和销毁

代码示例:

// 会话管理
export const sessionManager = {
  // 创建会话
  createSession(userInfo: UserInfo, token: string): void {
    tokenUtils.setToken(token)
    setUserInfo(userInfo)
    setLoginTime(Date.now())
  },
  
  // 销毁会话
  destroySession(): void {
    tokenUtils.removeToken()
    removeUserInfo()
    removeLoginTime()
  },
  
  // 检查会话有效性
  isSessionValid(): boolean {
    const token = tokenUtils.getToken()
    if (!token) return false
    
    if (tokenUtils.isTokenExpired(token)) {
      // 尝试刷新令牌
      try {
        await tokenUtils.refreshToken()
        return true
      } catch {
        this.destroySession()
        return false
      }
    }
    
    return true
  }
}

设备管理

功能描述: 设备信息的收集和管理

代码示例:

// 设备管理
export const deviceManager = {
  // 生成设备ID
  generateDeviceId(): string {
    let deviceId = uni.getStorageSync('deviceId')
    if (!deviceId) {
      deviceId = 'device_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
      uni.setStorageSync('deviceId', deviceId)
    }
    return deviceId
  },
  
  // 获取设备信息
  getDeviceInfo(): string {
    const systemInfo = uni.getSystemInfoSync()
    return JSON.stringify({
      platform: systemInfo.platform,
      system: systemInfo.system,
      version: systemInfo.version,
      model: systemInfo.model,
      brand: systemInfo.brand
    })
  },
  
  // 获取设备列表
  async getDeviceList(): Promise<Device[]> {
    const response = await userApi.getDeviceList()
    return response.data
  },
  
  // 退出所有设备
  async logoutAllDevices(): Promise<void> {
    const response = await userApi.logoutAllDevices()
    if (response.code !== 200) {
      throw new Error(response.message)
    }
  }
}

🛡️ 路由守卫

功能描述: 保护需要认证的页面和功能

代码示例:

// 路由守卫
export const authGuard = {
  // 检查认证状态
  checkAuth(): boolean {
    return sessionManager.isSessionValid()
  },
  
  // 需要认证的路由
  requireAuth(path: string): boolean {
    const authRequiredPaths = [
      '/pages/mine/profile-settings',
      '/pages/mine/account-settings',
      '/pages/mine/user-sessions',
      '/pages/auth/index'
    ]
    return authRequiredPaths.includes(path)
  },
  
  // 路由守卫逻辑
  beforeRouteEnter(to: any, from: any, next: any): void {
    if (authGuard.requireAuth(to.path)) {
      if (authGuard.checkAuth()) {
        next()
      } else {
        // 跳转到登录页
        uni.navigateTo({
          url: '/pages/mine/login?redirect=' + encodeURIComponent(to.path)
        })
      }
    } else {
      next()
    }
  }
}

📱 多平台适配

平台检测

功能描述: 检测当前运行平台并适配相应功能

代码示例:

// 平台检测
export const platformDetector = {
  // 获取当前平台
  getCurrentPlatform(): Platform {
    // #ifdef MP-WEIXIN
    return 'weixin'
    // #endif
    
    // #ifdef MP-ALIPAY
    return 'alipay'
    // #endif
    
    // #ifdef H5
    return 'h5'
    // #endif
    
    // #ifdef APP
    return 'app'
    // #endif
    
    return 'unknown'
  },
  
  // 检查平台功能支持
  isFeatureSupported(feature: string): boolean {
    const platform = this.getCurrentPlatform()
    const featureMap = {
      wechat: ['login', 'payment', 'share'],
      alipay: ['login', 'payment', 'share'],
      h5: ['login', 'share'],
      app: ['login', 'push', 'native']
    }
    
    return featureMap[platform]?.includes(feature) || false
  }
}

平台登录适配

功能描述: 根据平台提供相应的登录方式

代码示例:

// 平台登录适配
export const platformLoginAdapter = {
  // 获取支持的登录方式
  getSupportedLoginMethods(): LoginMethod[] {
    const platform = platformDetector.getCurrentPlatform()
    const methods: LoginMethod[] = ['username']
    
    if (platformDetector.isFeatureSupported('wechat')) {
      methods.push('wechat')
    }
    
    if (platformDetector.isFeatureSupported('alipay')) {
      methods.push('alipay')
    }
    
    return methods
  },
  
  // 执行平台登录
  async executePlatformLogin(method: LoginMethod): Promise<LoginResponse> {
    switch (method) {
      case 'username':
        throw new Error('用户名登录需要手动调用')
      case 'wechat':
        return await wechatLogin()
      case 'alipay':
        return await alipayLogin()
      default:
        throw new Error('不支持的登录方式')
    }
  }
}

🔄 状态管理

Pinia Store

功能描述: 使用Pinia管理用户认证状态

代码示例:

// 用户状态管理
export const useUserStore = defineStore('user', () => {
  // 状态
  const userInfo = ref<UserInfo | null>(null)
  const isLoggedIn = computed(() => !!userInfo.value)
  const loginPlatform = ref<LoginPlatform | null>(null)
  
  // 操作
  const login = async (credentials: LoginCredentials): Promise<void> => {
    try {
      const response = await userService.login(credentials)
      userInfo.value = response.userInfo
      loginPlatform.value = response.loginPlatform
      sessionManager.createSession(response.userInfo, response.token)
    } catch (error) {
      console.error('登录失败:', error)
      throw error
    }
  }
  
  const logout = async (): Promise<void> => {
    try {
      await userService.logout()
    } catch (error) {
      console.error('登出失败:', error)
    } finally {
      userInfo.value = null
      loginPlatform.value = null
      sessionManager.destroySession()
    }
  }
  
  const updateUserInfo = async (data: Partial<UserInfo>): Promise<void> => {
    try {
      const updatedInfo = await userService.updateUserInfo(data)
      userInfo.value = updatedInfo
    } catch (error) {
      console.error('更新用户信息失败:', error)
      throw error
    }
  }
  
  const initUserInfo = async (): Promise<UserInfo | null> => {
    try {
      if (sessionManager.isSessionValid()) {
        const info = await userService.getUserInfo()
        userInfo.value = info
        return info
      }
    } catch (error) {
      console.error('初始化用户信息失败:', error)
      sessionManager.destroySession()
    }
    return null
  }
  
  return {
    userInfo,
    isLoggedIn,
    loginPlatform,
    login,
    logout,
    updateUserInfo,
    initUserInfo
  }
})

📚 最佳实践

1. 安全最佳实践

  • 使用HTTPS传输敏感数据
  • 定期刷新JWT令牌
  • 实现设备管理功能
  • 记录登录日志
  • 实现账号锁定机制

2. 用户体验最佳实践

  • 提供多种登录方式
  • 记住用户登录状态
  • 提供快速登录选项
  • 友好的错误提示
  • 支持离线登录

3. 开发最佳实践

  • 统一的错误处理
  • 完整的类型定义
  • 充分的测试覆盖
  • 清晰的代码注释
  • 模块化设计

Penny Lens 认证模块 - 让用户身份管理更安全、更便捷 🔐