Chuyển đến nội dung chính

第 12 課:身分驗證與授權

Firebase 驗證:電子郵件/密碼、Google 登入、Apple 登入。 JWT 令牌管理。身份驗證狀態持久性。基於角色的存取控制。生物特徵認證。

💻 程式設計 — 第 12 課 第 12 課:身分驗證與授權

Flutter 和 Dart:從基礎到高級

第 3 部分:網路與數據

亞洲開發網

1.Firebase 身份驗證

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  Stream<User?> get authStateChanges => _auth.authStateChanges();

  // Email/Password
  Future<UserCredential> register(String email, String password) async {
    return _auth.createUserWithEmailAndPassword(email: email, password: password);
  }

  Future<UserCredential> login(String email, String password) async {
    return _auth.signInWithEmailAndPassword(email: email, password: password);
  }

  // Google Sign-In
  Future<UserCredential> signInWithGoogle() async {
    final googleUser = await GoogleSignIn().signIn();
    final googleAuth = await googleUser!.authentication;
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );
    return _auth.signInWithCredential(credential);
  }

  Future<void> signOut() async {
    await GoogleSignIn().signOut();
    await _auth.signOut();
  }
}

2. 使用 Riverpod 驗證狀態

final authServiceProvider = Provider<AuthService>((ref) => AuthService());

final authStateProvider = StreamProvider<User?>((ref) {
  return ref.watch(authServiceProvider).authStateChanges;
});

// Router guard
final routerProvider = Provider<GoRouter>((ref) {
  final authState = ref.watch(authStateProvider);

  return GoRouter(
    redirect: (context, state) {
      final isLoggedIn = authState.valueOrNull != null;
      final isLoginRoute = state.matchedLocation == '/login';

      if (!isLoggedIn && !isLoginRoute) return '/login';
      if (isLoggedIn && isLoginRoute) return '/home';
      return null;
    },
    routes: [/* ... */],
  );
});

3. JWT 代幣管理

class TokenManager {
  static const _storage = FlutterSecureStorage();

  Future<void> saveTokens({required String access, required String refresh}) async {
    await _storage.write(key: 'access_token', value: access);
    await _storage.write(key: 'refresh_token', value: refresh);
  }

  Future<String?> get accessToken => _storage.read(key: 'access_token');
  Future<String?> get refreshToken => _storage.read(key: 'refresh_token');

  Future<void> clear() => _storage.deleteAll();
}

// Dio interceptor — auto-refresh token
class AuthInterceptor extends Interceptor {
  final TokenManager _tokenManager;
  final Dio _dio;

  AuthInterceptor(this._tokenManager, this._dio);

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    final token = await _tokenManager.accessToken;
    if (token != null) options.headers['Authorization'] = 'Bearer $token';
    handler.next(options);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    if (err.response?.statusCode == 401) {
      final refresh = await _tokenManager.refreshToken;
      if (refresh != null) {
        try {
          final response = await _dio.post('/auth/refresh', data: {'refresh_token': refresh});
          await _tokenManager.saveTokens(
            access: response.data['access_token'],
            refresh: response.data['refresh_token'],
          );
          // Retry request
          final retryResponse = await _dio.fetch(err.requestOptions);
          handler.resolve(retryResponse);
          return;
        } catch (_) {
          await _tokenManager.clear();
        }
      }
    }
    handler.next(err);
  }
}

4. 生物辨識認證

class BiometricService {
  final _auth = LocalAuthentication();

  Future<bool> get isAvailable async {
    final canCheck = await _auth.canCheckBiometrics;
    final isSupported = await _auth.isDeviceSupported();
    return canCheck && isSupported;
  }

  Future<bool> authenticate() async {
    return _auth.authenticate(
      localizedReason: 'Xác thực để truy cập ứng dụng',
      options: const AuthenticationOptions(biometricOnly: true),
    );
  }
}

下一篇: 動畫和動作。