如何在Flutter中使用Firebase和Bloc状态管理构建安全的用户身份验证流程

用户认证在移动应用开发中至关重要它帮助确保只有授权用户可以访问敏感信息和执行应用程序中的操作在本教程中,我们将探讨如何使用Firebase进行认证和Bloc状态管理模式,在Flutter中构建安全的用户认证

用户认证对于移动应用开发至关重要。它有助于确保只有授权用户才能访问敏感信息并在应用程序中执行操作。

在本教程中,我们将探讨如何在Flutter中使用Firebase构建安全的用户认证,并使用Bloc状态管理模式处理应用程序状态。到最后,您将对如何集成Firebase认证和使用Bloc实现安全登录和注册流程有坚实的理解。

先决条件:

为了充分利用本教程,您应该具备以下条件:

  • 熟悉Flutter和Dart
  • Firebase帐户:如果没有,请创建一个Firebase帐户。您可以通过Firebase控制台设置Firebase项目。

Firebase认证的工作原理

Firebase认证是一个强大的服务,简化了应用中用户认证过程。它支持多种认证方法,包括电子邮件/密码、社交媒体等。

Firebase认证的一个关键优势是其内置安全功能,包括用户凭据的安全存储和敏感数据的加密。

流程图描述

让我们使用流程图来可视化操作流程,以便更好地理解您将要学习的概念。请查看下面的图表以获得更好的理解:

流程图
图1:应用的流程图

上面的图像是一个流程图,用于可视化应用程序的流程,我们来讨论每个部分代表的含义。圆角矩形代表流程的起始和结束点;紫色矩形代表屏幕;浅蓝色矩形代表发生的过程;最后,菱形代表决策。

  • 应用程序从AuthenticationFlowScreen开始。
  • StreamBuilder监听认证状态的变化。
  • 如果用户已经认证,则进入HomeScreen;否则,进入SignupScreen
  • AuthenticationBloc管理用户认证的事件和状态。
  • 当用户注册(触发SignUpUser事件)时:
  • 它初始化认证加载状态(AuthenticationLoadingState)。
  • 调用AuthServicesignUpUser进行用户注册。
  • 如果成功,它会发出带有用户数据的AuthenticationSuccessState;否则,发出AuthenticationFailureState
  • 当用户开始登出过程(触发SignOut事件)时:
  • 它开始认证加载状态(AuthenticationLoadingState)。
  • 调用AuthServicesignOutUser进行用户登出。
  • 如果在登出过程中发生错误,它会记录错误消息。

项目设置

为了开始使用Firebase认证,您必须在Flutter项目中设置Firebase。

按照以下步骤将Firebase和bloc添加到您的项目中:

将依赖项添加到您的项目中

在您的首选代码编辑器中打开您的项目。

将以下依赖项添加到您的pubspec.yaml文件中:

dependencies:firebase_core: ^2.20.0firebase_auth: ^4.12.0flutter_bloc: ^8.1.3

然后保存pubspec.yaml文件以获取依赖项。

配置Firebase

通过Firebase控制台创建一个新的Firebase项目。点击项目中的认证,按照提供的说明进行操作。

更多信息,请参阅Firebase网站

初始化Firebase

首先,在lib文件夹中打开main.dart文件。

向文件中添加以下代码以初始化Firebase:

void main() async {WidgetsFlutterBinding.ensureInitialized();await Firebase.initializeApp(  options: DefaultFirebaseOptions.currentPlatform);

上述代码显示了运行应用程序的代码。这段代码没有什么特别之处,只是我们在void main中添加了一些代码来初始化Firebase。

用户模型

在创建用于与Firebase服务通信的Firebase类之前,让我们定义一个UserModel来表示用户数据。

首先,在项目的lib目录中创建一个user.dart文件。

然后在文件中添加以下代码:

class UserModel {final String? id;final String? email;final String? displayName;UserModel({ this.id, this.email, this.displayName, });}

现在,您已经设置好了Firebase并创建了一个用户模型,接下来需要创建一个服务类来直接与Firebase通信。

身份验证服务

创建一个名为services的文件夹,在该文件夹中创建一个名为authentication.dart的文件,然后将以下代码添加到文件中。

import 'package:firebase_auth/firebase_auth.dart';import '../models/user.dart';class AuthService {  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;  /// 创建用户  Future<UserModel?> signUpUser(    String email,    String password,  ) async {    try {      final UserCredential userCredential =          await _firebaseAuth.createUserWithEmailAndPassword(        email: email.trim(),        password: password.trim(),      );      final User? firebaseUser = userCredential.user;      if (firebaseUser != null) {        return UserModel(          id: firebaseUser.uid,          email: firebaseUser.email ?? '',          displayName: firebaseUser.displayName ?? '',        );      }    } on FirebaseAuthException catch (e) {      print(e.toString());    }    return null;  }    /// signOutUser    Future<void> signOutUser() async {      final User? firebaseUser = FirebaseAuth.instance.currentUser;    if (firebaseUser != null) {      await FirebaseAuth.instance.signOut();    }  }  // ... (其他方法)}}

上面的代码片段是使用Firebase在应用程序中创建用户的方法。该方法中的signUpUser方法接受两个字符串参数:emailpassword。然后,您调用Firebase方法使用我们添加的参数来创建一个用户。

现在您知道如何创建注册方法,您也可以创建登录方法。该类最终描述了Firebase与应用程序之间的通信。

接下来的部分是将服务连接到您的状态管理,我们将看到如何实现。

Bloc状态管理的工作原理

Bloc是Flutter中的流行状态管理模式,有助于以可预测和可测试的方式管理复杂的应用程序状态。Bloc代表”Business Logic Component”(业务逻辑组件),它将业务逻辑和UI分离。Bloc将是您的应用程序与Firebase之间的桥梁。

VScode有一个扩展功能,可以为Bloc创建样板代码。您可以使用该扩展来加快开发过程。

设置Firebase身份验证Bloc

Bloc由事件和状态组成。首先,我们将为Bloc创建状态和事件。然后,我们将创建一个AuthenticationBloc,该Bloc将使用我们创建的事件、状态和服务处理逻辑。

AuthenticationState类

AuthenticationState类负责管理身份验证过程的不同状态。如代码中所示,有初始状态、加载中状态、成功状态和失败状态,以确保我们了解身份验证过程中发生的情况。

首先,在项目的bloc目录中创建一个authentication_state.dart文件。

part of 'authentication_bloc.dart';

// 定义抽象类 AuthenticationState
abstract class AuthenticationState {
  const AuthenticationState();
  List<Object> get props => [];
}

// 定义 AuthenticationInitialState 类,表示身份验证初始状态
class AuthenticationInitialState extends AuthenticationState {}

// 定义 AuthenticationLoadingState 类,表示身份验证加载中的状态
class AuthenticationLoadingState extends AuthenticationState {
  final bool isLoading;
  AuthenticationLoadingState({required this.isLoading});
}

// 定义 AuthenticationSuccessState 类,表示身份验证成功的状态
class AuthenticationSuccessState extends AuthenticationState {
  final UserModel user;
  const AuthenticationSuccessState(this.user);

  @override
  List<Object> get props => [user];
}

// 定义 AuthenticationFailureState 类,表示身份验证失败的状态
class AuthenticationFailureState extends AuthenticationState {
  final String errorMessage;
  const AuthenticationFailureState(this.errorMessage);
  
  @override
  List<Object> get props => [errorMessage];
}

// 定义抽象类 AuthenticationEvent
abstract class AuthenticationEvent {
  const AuthenticationEvent();
  List<Object> get props => [];
}

// 定义 SignUpUser 类,表示注册事件
class SignUpUser extends AuthenticationEvent {
  final String email;
  final String password;
  const SignUpUser(this.email, this.password);

  @override
  List<Object> get props => [email, password];
}

// 定义 SignOut 类,表示登出事件
class SignOut extends AuthenticationEvent {}  

上述代码解析:

AuthenticationState 抽象类:

  • AuthenticationState 是不同身份验证过程中可能出现的各种状态的基类。
  • 它包含一个 props 方法,返回一个对象列表。该方法用于比较该类的实例时进行相等性检查。

AuthenticationInitialState 类:

  • AuthenticationInitialState 表示身份验证过程的初始状态。

AuthenticationLoadingState 类:

  • AuthenticationLoadingState 表示身份验证过程正在进行中,UI 可能显示一个加载指示器。
  • 它接受一个布尔类型的参数 isLoading,表示身份验证过程当前是否正在加载。

AuthenticationSuccessState 类:

  • AuthenticationSuccessState 表示身份验证过程已完成的状态。
  • 它包含一个类型为 UserModel 的用户属性,表示经过身份验证的用户。

AuthenticationFailureState 类:

  • AuthenticationFailureState 表示身份验证过程失败的状态。
  • 它包含一个 error message 属性,包含有关失败的信息。

下面是 AuthenticationEvent 类:

  • AuthenticationEvent 负责触发 AuthenticationBloc 执行的事件。在这个例子中,它是“sign-in”事件。你可以在这里编写其他事件,比如“sign-up”和“sign-out”事件。
  • 在项目的 bloc 目录中创建一个名为 authentication_Event.dart 的文件。
part of 'authentication_bloc.dart';

// 定义抽象类 AuthenticationEvent
abstract class AuthenticationEvent {
  const AuthenticationEvent();
  List<Object> get props => [];
}

// 定义 SignUpUser 类,表示注册事件
class SignUpUser extends AuthenticationEvent {
  final String email;
  final String password;
  const SignUpUser(this.email, this.password);

  @override
  List<Object> get props => [email, password];
}

// 定义 SignOut 类,表示登出事件
class SignOut extends AuthenticationEvent {}  

AuthenticationEvent 类的结构与 AuthenticationState 类相似。让我们来看一下代码,了解它在做什么:

AuthenticationEvent 抽象类:

  • 这是触发身份验证状态更改的不同事件的基类。

SignUpUser 类:

  • 这个类表示用户尝试注册的事件。
  • 它接受两个参数 emailpassword,表示用户用于注册的凭据。
  • 该类的实例将向 Bloc 发出信号,表示用户正在尝试注册,而 Bloc 可以通过启动注册过程并相应地转换身份验证状态来做出响应。

SignOut 类:

  • 该类的实例将向 Bloc 发出信号,表示用户正在尝试登出。 bloc 可以通过启动登出过程并相应地更新身份验证状态来做出响应。

下面是 AuthenticationBloc 类:

  • AuthenticationBloc 将处理整个身份验证状态,从用户点击按钮到屏幕上的显示。它还直接与我们创建的 Firebase 服务进行交互。
  • 首先,在项目的 bloc 目录中创建一个名为 authentication_bloc.dart 的文件。

“`html

添加以下代码来定义 AuthenticationBloc 类:

import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import '../models/user.dart';
import '../services/authentication.dart';
part 'authentication_event.dart';
part 'authentication_state.dart';

class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
  final AuthService authService = AuthService();

  AuthenticationBloc() : super(AuthenticationInitialState()) {
    on<AuthenticationEvent>((event, emit) {});
    on<SignUpUser>((event, emit) async {
      emit(AuthenticationLoadingState(isLoading: true));
      try {
        final UserModel? user = await authService.signUpUser(event.email, event.password);
        if (user != null) {
          emit(AuthenticationSuccessState(user));
        } else {
          emit(const AuthenticationFailureState('create user failed'));
        }
      } catch (e) {
        print(e.toString());
      }
      emit(AuthenticationLoadingState(isLoading: false));
    });
    on<SignOut>((event, emit) async {
      emit(AuthenticationLoadingState(isLoading: true));
      try {
        authService.signOutUser();
      } catch (e) {
        print('error');
        print(e.toString());
      }
      emit(AuthenticationLoadingState(isLoading: false));
    });
  }
}

在这段代码中,我们创建了 AuthService 类的实例,这个类负责处理用户认证操作,比如注册和注销。

on<SignUpUser>((event, emit) async { ... } 定义了一个处理 SignUpUser 事件的方法。当这个事件被触发时,bloc 会进行以下步骤:

  • 它发出一个 AuthenticationLoadingState 来表示认证过程正在进行中。
  • 它调用 authServicesignUpUser 方法,尝试使用提供的电子邮件和密码创建用户账户。
  • 如果用户账户创建成功(也就是说,用户不是 null),它会发出一个包含用户数据的 AuthenticationSuccessState
  • 如果用户账户创建失败,它会发出一个包含错误消息的 AuthenticationFailureState,并记录错误日志。
  • 无论成功与否,它会发出另一个 AuthenticationLoadingState 来表示认证过程结束。

on<SignOut>((event, emit) async { ... } 定义了一个处理 SignOut 事件的方法。当这个事件被触发时,bloc 会进行以下步骤:

  • 它发出一个 AuthenticationLoadingState 来表示注销过程正在进行中。
  • 它调用 authServicesignOutUser 方法来注销用户。
  • 如果注销过程中发生任何错误,它会记录错误日志。
  • 它发出另一个 AuthenticationLoadingState 来表示注销过程结束。

AuthenticationBloc 管理认证过程的状态,包括加载、成功和失败状态,这些状态是根据用户动作触发的事件而生成的。 authService 负责实际执行认证操作。完成 Bloc 的设置后,我们可以使用 Bloc 实现认证流程。

如何使用 Bloc 实现认证流程

要实现认证流程,您将创建一个专用的无状态小部件,用来检查用户是否已经登录,以便我们知道要向用户显示什么界面。该页面将根据用户的认证状态显示不同的页面。

AuthenticationFlowScreen:

在项目的 screens 目录中创建一个名为 authentication_page.dart 的新文件。

import 'package:bloc_authentication_flow/screens/home.dart';
import 'package:bloc_authentication_flow/screens/sign_up.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class AuthenticationFlowScreen extends StatelessWidget {
  const AuthenticationFlowScreen({Key? key}) : super(key: key);
  
  static String id = 'main screen';
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: StreamBuilder(
        stream: FirebaseAuth.instance.authStateChanges(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return const HomeScreen();
          } else {
            return const SignupScreen();
          }
        },
      ),
    );
  }
}

“`

在上述代码中,您有一个带有StatelessWidgetStreamBuilder作为子组件的组件。 StreamBuilder充当裁判,使用Firebase检查状态变化以及用户是否已登录。如果用户已登录,则将其重定向到主页屏幕,否则将其重定向到注册屏幕。

将主页路由更改为AuthenticationFlowScreen,以便在路由到任何页面之前让应用程序进行检查。

   home: const AuthenticationFlowScreen() 

注册页面

首先,在screens目录中创建一个名为sign_up.dart的新文件。

import 'package:bloc_authentication_flow/screens/home.dart';import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import '../bloc/authentication_bloc.dart';class SignupScreen extends StatefulWidget {  static String id = 'login_screen';  const SignupScreen({    Key? key,  }) : super(key: key);  @override  State<SignupScreen> createState() => _SignupScreenState();}class _SignupScreenState extends State<SignupScreen> {  // 文本控制器  final emailController = TextEditingController();  final passwordController = TextEditingController();  @override  void dispose() {    emailController.dispose();    passwordController.dispose();    super.dispose();  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: const Text(          'Login to Your Account',          style: TextStyle(            color: Colors.deepPurple,          ),        ),        centerTitle: true,      ),      body: Padding(        padding: const EdgeInsets.all(16.0),        child: Column(          crossAxisAlignment: CrossAxisAlignment.start,          children: [            const SizedBox(height: 20),            const Text('Email address'),            const SizedBox(height: 10),            TextFormField(              controller: emailController,              decoration: const InputDecoration(                border: OutlineInputBorder(),                hintText: 'Enter your email',              ),            ),            const SizedBox(height: 10),            const Text('Password'),            TextFormField(              controller: passwordController,              decoration: const InputDecoration(                border: OutlineInputBorder(),                hintText: 'Enter your password',              ),              obscureText: false,            ),            const SizedBox(height: 10),            GestureDetector(              onTap: () {},              child: const Text(                'Forgot password?',                style: TextStyle(                  color: Colors.deepPurple,                ),              ),            ),            const SizedBox(height: 20),            BlocConsumer<AuthenticationBloc, AuthenticationState>(              listener: (context, state) {                if (state is AuthenticationSuccessState) {                  Navigator.pushNamedAndRemoveUntil(                    context,                    HomeScreen.id,                    (route) => false,                  );                } else if (state is AuthenticationFailureState) {                  showDialog(                      context: context,                      builder: (context) {                        return const AlertDialog(                          content: Text('error'),                        );                      });                }              },              builder: (context, state) {                return SizedBox(                  height: 50,                  width: double.infinity,                  child: ElevatedButton(                    onPressed: () {                      BlocProvider.of<AuthenticationBloc>(context).add(                        SignUpUser(                          emailController.text.trim(),                          passwordController.text.trim(),                        ),                      );                    },                    child:  Text(                      state is AuthenticationLoadingState                            ? '.......',                            : 'Signup',                      style: TextStyle(                        fontSize: 20,                      ),                    ),                  ),                );              },            ),            const SizedBox(height: 20),            Row(              mainAxisAlignment: MainAxisAlignment.center,              children: [                const Text("Already have an account? "),                GestureDetector(                  onTap: () {},                  child: const Text(                    'Login',                    style: TextStyle(                      color: Colors.deepPurple,                    ),                  ),                )              ],            ),          ],        ),      ),    );  }}

这段代码只是一个简单的登录界面,包含两个textfields和一个raised button。 BlocConsumer小部件包装了Sign up按钮,并监听AuthenticationBloc状态的变化。当用户按下按钮时,它会向AuthenticationBloc发送一个事件,以启动用户注册流程。

根据身份验证状态,此按钮可能会显示不同的反馈信息或导航到另一个屏幕。它会检查AuthenticationSuccessStateAuthenticationLoadingStateAuthenticationFailureState状态以相应地进行响应。

ezgif.com-video-to-gif--1-
图片2:一个登录界面展示了一个登录过程,有3中状态中的2种。

主屏幕

screens目录中创建另一个名为home_screen.dart的文件,并将下面的代码添加到文件中。

import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import '../bloc/authentication_bloc.dart';class HomeScreen extends StatelessWidget {  static String id = 'home_screen';  const HomeScreen({super.key});  @override  Widget build(BuildContext context) {    return Scaffold(      body: Center(        child: Column(          mainAxisAlignment: MainAxisAlignment.center,          children: [            const Text(              'Hello User',              style: TextStyle(                fontSize: 20,              ),            ),            const SizedBox(              height: 20,            ),            BlocConsumer<AuthenticationBloc, AuthenticationState>(              listener: (context, state) {                if (state is AuthenticationLoadingState) {                   const CircularProgressIndicator();                } else if (state is AuthenticationFailureState){                    showDialog(context: context, builder: (context){                          return const AlertDialog(                            content: Text('error'),                          );                        });                }              },              builder: (context, state) {                return ElevatedButton(                    onPressed: () {                      BlocProvider.of<AuthenticationBloc>(context)                      .add(SignOut());                    }, child: const Text(                      'logOut'                      ));              },            ),          ],        ),      ),    );  }}

上面的代码表示HomeScreen,它也是一个简单的页面,由脚手架、列和文本小部件组成,但有趣的部分是在说“注销”的提升按钮中的BlocConsumer。让我们仔细看看。

BlocConsumer监听来自AuthenticationBloc的状态变化。它有两个参数- listener 和 builder。

  • listener:监听状态的变化,并根据从AuthenticationBloc接收到的当前状态做出反应。
  • 如果状态是AuthenticationLoadingState,它会显示一个CircularProgressIndicator
  • 如果状态是AuthenticationFailureState,它会显示一个带有消息“错误”的AlertDialog
  • builder:基于从AuthenticationBloc接收到的当前状态构建UI。
  • 它渲染一个标有“注销”的ElevatedButton
  • 按下按钮时,它通过BlocProvider触发AuthenticationBloc中的SignOut事件。

通过实现 Bloc 认证流程,您可以运行 Flutter 应用程序并测试注册功能。确保根据应用程序规格要求处理其他与身份验证相关的场景,例如用户登录和密码恢复。此外,您还需要优雅地处理错误,以为用户提供良好的体验。

如果您想克隆该存储库,可以在这里查看并点赞GitHub。

结论

在本文中,我们探讨了在 Flutter 中使用 Firebase 进行用户认证流程的构建,以及使用 Bloc 状态管理模式处理应用程序状态。

我们学习了如何在 Flutter 项目中设置 Firebase,创建用于身份验证的 Bloc,并使用 Bloc 实现身份验证流程。

通过充分利用 Firebase 的强大功能和 Bloc 的可预测性,您可以确保在 Flutter 应用中实现安全且无缝的用户认证体验。


Leave a Reply

Your email address will not be published. Required fields are marked *