无后端场景下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无效则跳转登录页
注意事项
- 安全性局限:客户端存储的密钥和用户数据易被逆向,仅适合非敏感的个人应用或演示项目
- 数据备份:可将用户数据备份至本地文件或云存储(如iCloud),避免应用卸载后数据丢失
- Token有效期:设置合理的有效期,过期后强制用户重新登录
内容的提问来源于stack exchange,提问作者Aditya Pachchigar
相关产品推荐
相关产品推荐

