You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无后端场景下Flutter基于Token的登录授权实现及Token生成咨询

无后端服务器场景下Flutter基于Token的账号密码授权实现方案

核心逻辑说明

无后端场景下,所有用户数据、Token的生成与验证均在客户端完成,依赖本地存储(如Hive、SharedPreferences)持久化数据。需注意:该方案仅适用于非敏感场景,因为客户端存储的密钥和用户数据存在被逆向破解的风险。

1. 本地用户数据存储设计

选择Hive作为对象存储方案(支持直接存储自定义模型,操作简便),先定义用户模型并初始化存储:

依赖配置

在pubspec.yaml中添加依赖:

dependencies:
  hive: ^2.2.3
  hive_flutter: ^1.1.0
dev_dependencies:
  hive_generator: ^1.1.5
  build_runner: ^2.1.11

用户模型定义

import 'package:hive/hive.dart';

part 'user_model.g.dart';

@HiveType(typeId: 0)
class UserModel extends HiveObject {
  @HiveField(0)
  String userId; // 唯一标识,用UUID生成
  @HiveField(1)
  String username;
  @HiveField(2)
  String encryptedPassword; // 存储盐+加密后的密码,格式为"salt:hash"
  @HiveField(3)
  String email;

  UserModel({
    required this.userId,
    required this.username,
    required this.encryptedPassword,
    required this.email,
  });
}

执行命令生成Hive适配器:

flutter packages pub run build_runner build

初始化Hive

void main() async {
  await Hive.initFlutter();
  Hive.registerAdapter(UserModelAdapter());
  await Hive.openBox<UserModel>('users');
  runApp(const MyApp());
}

2. 密码加密处理

采用加盐SHA-256哈希存储密码,避免明文泄露:

依赖配置

dependencies:
  crypto: ^3.0.2

加密工具函数

import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';

// 生成随机盐(16字节)
String generateSalt() {
  return base64Url.encode(List<int>.generate(16, (_) => Random().nextInt(256)));
}

// 加盐哈希密码
String hashPassword(String password, String salt) {
  final bytes = utf8.encode('$password$salt');
  final digest = sha256.convert(bytes);
  return digest.toString();
}

3. 注册流程实现

import 'package:uuid/uuid.dart';

final uuid = Uuid();
final userBox = Hive.box<UserModel>('users');

// 注册:返回Token或null(失败)
Future<String?> register(String username, String password, String email) async {
  // 检查用户名/邮箱是否已存在
  final existingUser = userBox.values.firstWhere(
    (user) => user.username == username || user.email == email,
    orElse: () => UserModel(userId: '', username: '', encryptedPassword: '', email: ''),
  );

  if (existingUser.userId.isNotEmpty) return null;

  // 生成盐和加密密码
  final salt = generateSalt();
  final encryptedPwd = hashPassword(password, salt);
  final userId = uuid.v4();

  // 保存用户数据
  final newUser = UserModel(
    userId: userId,
    username: username,
    encryptedPassword: '$salt:$encryptedPwd',
    email: email,
  );
  await userBox.add(newUser);

  // 生成授权Token
  return generateAuthToken(userId, username);
}

4. Token生成逻辑

采用JWT格式生成Token,用客户端自定义密钥签名:

依赖配置

dependencies:
  jwt_io: ^3.0.1

Token生成函数

import 'package:jwt_io/jwt_io.dart';

// 客户端签名密钥(自定义随机字符串,注意:无后端场景下密钥无法保密)
const _clientSecret = 'your_custom_random_secret_key_here';

String generateAuthToken(String userId, String username) {
  final payload = {
    'sub': userId, // 用户唯一ID
    'username': username,
    'iat': DateTime.now().millisecondsSinceEpoch ~/ 1000, // 签发时间
    'exp': DateTime.now().add(const Duration(days: 7)).millisecondsSinceEpoch ~/ 1000, // 7天有效期
  };

  return JwtEncoder.encode(payload, _clientSecret);
}

5. 登录流程实现

// 登录:返回Token或null(失败)
Future<String?> login(String identifier, String password) async {
  // identifier支持用户名或邮箱
  final user = userBox.values.firstWhere(
    (u) => u.username == identifier || u.email == identifier,
    orElse: () => UserModel(userId: '', username: '', encryptedPassword: '', email: ''),
  );

  if (user.userId.isEmpty) return null;

  // 拆分盐和存储的哈希密码
  final parts = user.encryptedPassword.split(':');
  if (parts.length != 2) return null;
  final salt = parts[0];
  final storedHash = parts[1];

  // 验证密码
  final inputHash = hashPassword(password, salt);
  if (inputHash != storedHash) return null;

  // 生成新Token
  return generateAuthToken(user.userId, user.username);
}

6. Token存储与验证

Token存储(SharedPreferences)

import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveAuthToken(String token) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('auth_token', token);
}

Future<String?> getAuthToken() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getString('auth_token');
}

Future<void> clearAuthToken() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.remove('auth_token');
}

Token验证

// 验证Token有效性(签名+有效期)
bool isTokenValid(String token) {
  try {
    return JwtDecoder.verify(token, _clientSecret) && !JwtDecoder.isExpired(token);
  } catch (_) {
    return false;
  }
}

// 解析Token中的用户信息
Map<String, dynamic>? getTokenUserInfo(String token) {
  try {
    if (isTokenValid(token)) {
      return JwtDecoder.decode(token);
    }
  } catch (_) {}
  return null;
}

7. 页面流程示例

  • 注册页:收集用户名、密码、邮箱,调用register函数,成功则保存Token并跳转首页
  • 登录页:收集账号、密码,调用login函数,成功则保存Token并跳转首页
  • 首页:启动时调用getAuthToken,若Token无效则跳转登录页

注意事项

  1. 安全性局限:客户端存储的密钥和用户数据易被逆向,仅适合非敏感的个人应用或演示项目
  2. 数据备份:可将用户数据备份至本地文件或云存储(如iCloud),避免应用卸载后数据丢失
  3. Token有效期:设置合理的有效期,过期后强制用户重新登录

内容的提问来源于stack exchange,提问作者Aditya Pachchigar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 01:56:04