react-native的token认证流程

在 React Native 中实现 Token 认证是移动应用开发中的常见需求,它用于验证用户的身份并授权其访问受保护的 API 资源。

Token 认证的核心流程:

  1. 用户登录 (Login):

    • 用户在前端输入用户名和密码。
    • 前端将这些凭据发送到后端 API。
    • 后端验证凭据。如果验证成功,后端会生成一个 Token(通常是 JWT - JSON Web Token)并返回给前端。
    • 前端接收到 Token 后,将其安全地存储起来(例如使用 AsyncStorage)。
    • 前端更新用户的登录状态(例如 Context 或 Redux 中的状态)。
  2. 访问受保护资源 (Access Protected Resources):

    • 前端需要访问后端受保护的 API 时,将之前保存的 Token 附带在请求的 Authorization 头中发送给后端。
    • 后端接收到请求后,验证 Token 的有效性(例如检查签名、有效期、是否被吊销等)。
    • 如果 Token 有效,后端处理请求并返回数据。
    • 如果 Token 无效(过期、篡改等),后端会返回错误(通常是 401 Unauthorized 或 403 Forbidden),前端需要处理这些错误(例如引导用户重新登录)。
  3. Token 刷新 (Token Refresh - 可选但推荐):

    • 为了安全性,Token 通常有较短的有效期。
    • 当访问 Token 过期时,前端可以使用一个 Refresh Token(通常有效期较长,也存储在客户端)向后端请求一个新的访问 Token。
    • 后端验证 Refresh Token,如果有效,则签发新的访问 Token。
    • 这减少了用户频繁重新登录的次数,同时保持了安全性。
  4. 用户登出 (Logout):

    • 用户选择登出时,前端从 AsyncStorage 中移除保存的 Token。
    • 前端清除用户相关的状态。
    • (可选)通知后端使该 Token 失效(如果后端支持)。

React Native 中的实现细节:

我们将使用以下技术栈:

  • @react-native-async-storage/async-storage: 用于在设备上持久化存储 Token。
  • Context API (或 Redux/Zustand 等状态管理库): 用于在整个应用中管理用户的登录状态和 Token。
  • fetch API (或 axios): 用于进行网络请求。
  • crypto-js (可选,用于密码哈希): 在发送密码到后端前进行哈希处理,增加一层客户端安全性(尽管主要安全在于 HTTPS 和后端处理)。

详细代码实现与示例

我们将构建一个简化的应用,包含:

  1. UserContext.tsx: 管理用户登录状态和 Token。
  2. AuthScreen.tsx: 登录界面。
  3. HomeScreen.tsx: 登录后的主界面,可以访问受保护资源并登出。
  4. App.tsx: 应用入口,根据登录状态决定显示哪个界面。
1. 安装必要的库

Bash

复制代码
npm install @react-native-async-storage/async-storage crypto-js @react-navigation/native @react-navigation/stack
npx expo install react-native-screens react-native-safe-area-context # For react-navigation
# 或
yarn add @react-native-async-storage/async-storage crypto-js @react-navigation/native @react-navigation/stack
yarn expo install react-native-screens react-native-safe-area-context
2. 后端模拟 (Python Flask 示例)

为了能够运行前端代码,你需要一个简单的后端。这里提供一个 Python Flask 的极简示例。

backend/app.py

复制代码
from flask import Flask, request, jsonify
from flask_cors import CORS
import jwt
import datetime
import hashlib # 用于密码哈希

app = Flask(__name__)
CORS(app) # 允许跨域请求

SECRET_KEY = "your_super_secret_key_for_jwt" # 生产环境请使用更复杂的密钥
REFRESH_SECRET_KEY = "your_super_secret_refresh_key_for_jwt"

# 简单模拟的用户数据库
users_db = {
    "testuser": {
        "password_hash": hashlib.sha256("testpassword".encode('utf-8')).hexdigest(), # 密码哈希
        "user_id": "user123",
        "user_name": "Test User",
        "email": "test@example.com",
        "phone": "123-456-7890"
    }
}

@app.route('/api/user_login', methods=['POST'])
def user_login():
    data = request.get_json()
    username = data.get('username')
    password_hash = data.get('password') # 接收前端传来的哈希后的密码

    if not username or not password_hash:
        return jsonify({"message": "用户名和密码不能为空"}), 400

    user_data = users_db.get(username)

    if user_data and user_data["password_hash"] == password_hash:
        # 生成访问 Token (有效期短,例如 15 分钟)
        access_token_payload = {
            "user_id": user_data["user_id"],
            "user_name": user_data["user_name"],
            "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
        }
        access_token = jwt.encode(access_token_payload, SECRET_KEY, algorithm="HS256")

        # 生成刷新 Token (有效期长,例如 7 天)
        refresh_token_payload = {
            "user_id": user_data["user_id"],
            "exp": datetime.datetime.utcnow() + datetime.timedelta(days=7)
        }
        refresh_token = jwt.encode(refresh_token_payload, REFRESH_SECRET_KEY, algorithm="HS256")

        return jsonify({
            "token": access_token,
            "refresh_token": refresh_token, # 将 refresh_token 也返回
            "user_id": user_data["user_id"],
            "user_name": user_data["user_name"],
            "email": user_data["email"],
            "phone": user_data["phone"],
            "message": "登录成功"
        }), 200
    else:
        return jsonify({"message": "无效的用户名或密码"}), 401

@app.route('/api/protected_data', methods=['GET'])
def protected_data():
    auth_header = request.headers.get('Authorization')
    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify({"message": "未授权:缺少或无效的Authorization头"}), 401

    token = auth_header.split(" ")[1]
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        user_id = payload.get('user_id')
        user_name = payload.get('user_name', '未知用户')
        return jsonify({"message": f"欢迎, {user_name}! 这是受保护的数据。", "user_id": user_id}), 200
    except jwt.ExpiredSignatureError:
        return jsonify({"message": "未授权:Token 已过期"}), 401
    except jwt.InvalidTokenError:
        return jsonify({"message": "未授权:无效的 Token"}), 401

@app.route('/api/refresh_token', methods=['POST'])
def refresh_token():
    data = request.get_json()
    refresh_token = data.get('refresh_token')

    if not refresh_token:
        return jsonify({"message": "未授权:缺少刷新Token"}), 400

    try:
        payload = jwt.decode(refresh_token, REFRESH_SECRET_KEY, algorithms=["HS256"])
        user_id = payload.get('user_id')

        # 重新生成新的访问 Token
        access_token_payload = {
            "user_id": user_id,
            "user_name": users_db.get(user_id, {}).get("user_name", "未知用户"), # 重新获取用户名
            "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
        }
        new_access_token = jwt.encode(access_token_payload, SECRET_KEY, algorithm="HS256")

        # 也可以同时生成新的刷新 Token,以实现刷新 Token 的滚动更新
        new_refresh_token_payload = {
            "user_id": user_id,
            "exp": datetime.datetime.utcnow() + datetime.timedelta(days=7)
        }
        new_refresh_token = jwt.encode(new_refresh_token_payload, REFRESH_SECRET_KEY, algorithm="HS256")


        return jsonify({
            "token": new_access_token,
            "refresh_token": new_refresh_token # 返回新的刷新 Token
        }), 200
    except jwt.ExpiredSignatureError:
        return jsonify({"message": "未授权:刷新 Token 已过期,请重新登录"}), 401
    except jwt.InvalidTokenError:
        return jsonify({"message": "未授权:无效的刷新 Token"}), 401


if __name__ == '__main__':
    # 注意:10.0.2.2 是 Android 模拟器访问宿主机(开发机器)的默认 IP
    # 如果是 iOS 模拟器或真实设备,请使用你电脑的局域网 IP
    app.run(host='10.0.2.2', port=5000, debug=True)

运行后端:

  1. 保存为 backend/app.py
  2. 安装依赖:pip install Flask Flask-Cors PyJWT
  3. 运行:python backend/app.py
3. React Native 前端代码
src/contexts/UserContext.tsx

这是核心,负责管理用户认证状态、Token 的存储与获取。

复制代码
// src/contexts/UserContext.tsx
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { SHA256 } from 'crypto-js'; // 用于前端密码哈希

// 定义用户信息接口
export interface UserInfo {
  user_id: string;
  user_name: string;
  email: string;
  phone: string;
  status: 'logged_in' | 'logged_out';
}

// 定义 Context 的类型
interface UserContextType {
  user: UserInfo | null; // 当前登录的用户信息
  authToken: string | null; // 访问 Token
  refreshToken: string | null; // 刷新 Token
  login: (username: string, password: string) => Promise<void>; // 登录方法
  logout: () => void; // 登出方法
  isLoading: boolean; // 是否正在加载用户数据 (应用启动时)
  refreshAccessToken: () => Promise<boolean>; // 刷新访问 Token 的方法
}

// 创建 Context,并设置默认值
const UserContext = createContext<UserContextType>({
  user: null,
  authToken: null,
  refreshToken: null,
  login: async () => { /* no-op */ },
  logout: () => { /* no-op */ },
  isLoading: true, // 初始加载状态为 true
  refreshAccessToken: async () => false, // 默认返回 false
});

// UserProvider 组件
const UserProvider = ({ children }: { children: ReactNode }) => {
  const [user, setUser] = useState<UserInfo | null>(null);
  const [authToken, setAuthToken] = useState<string | null>(null);
  const [refreshToken, setRefreshToken] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  // --- 1. 应用启动时加载存储的用户和 Token 信息 ---
  useEffect(() => {
    const loadStoredAuth = async () => {
      try {
        const storedAuthToken = await AsyncStorage.getItem('authToken');
        const storedRefreshToken = await AsyncStorage.getItem('refreshToken');
        const storedUserId = await AsyncStorage.getItem('userId');
        const storedUserName = await AsyncStorage.getItem('userName'); // 假设也存储了用户名

        if (storedAuthToken && storedRefreshToken && storedUserId && storedUserName) {
          setAuthToken(storedAuthToken);
          setRefreshToken(storedRefreshToken);
          // 这里可以根据实际情况从 API 重新获取完整的用户信息,
          // 或者直接使用存储的信息(如果它足够完整)
          setUser({
            user_id: storedUserId,
            user_name: storedUserName,
            email: 'N/A', // 示例,实际应从API获取或存储
            phone: 'N/A', // 示例
            status: 'logged_in',
          });
          console.log('User and tokens loaded from AsyncStorage.');
        } else {
          console.log('No existing user data or tokens found.');
          setUser(null);
          setAuthToken(null);
          setRefreshToken(null);
        }
      } catch (error) {
        console.error('Error loading user data from AsyncStorage:', error);
        setUser(null);
        setAuthToken(null);
        setRefreshToken(null);
      } finally {
        setIsLoading(false); // 加载完成
      }
    };

    loadStoredAuth();
  }, []); // 空依赖数组,只在组件挂载时运行一次

  // --- 2. 登录方法 ---
  const login = useCallback(async (username: string, password: string) => {
    try {
      const hashedPassword = SHA256(password).toString(); // 客户端密码哈希
      console.log('Hashed Password (Frontend):', hashedPassword);

      const response = await fetch('http://10.0.2.2:5000/api/user_login', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ username, password: hashedPassword }),
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Login failed');
      }

      const data = await response.json();

      if (!data.token || !data.refresh_token || !data.user_id) {
        throw new Error('Invalid API response structure: missing token or user_id');
      }

      // 保存 Token 和用户 ID
      await AsyncStorage.setItem('authToken', data.token);
      await AsyncStorage.setItem('refreshToken', data.refresh_token);
      await AsyncStorage.setItem('userId', data.user_id);
      await AsyncStorage.setItem('userName', data.user_name); // 也存储用户名

      // 更新 Context 状态
      setAuthToken(data.token);
      setRefreshToken(data.refresh_token);
      setUser({
        user_id: data.user_id,
        user_name: data.user_name || username,
        email: data.email || 'N/A',
        phone: data.phone || 'N/A',
        status: 'logged_in',
      });
      console.log('Login successful, user and tokens set.');
    } catch (error) {
      console.error('Login error:', error);
      // 登录失败时清除所有状态
      await logout(); // 调用登出方法清除所有相关信息
      throw error; // 重新抛出错误以便调用方处理
    }
  }, []); // 空依赖数组,确保 login 函数的稳定性

  // --- 3. 登出方法 ---
  const logout = useCallback(async () => {
    console.log('Logging out...');
    await AsyncStorage.removeItem('authToken');
    await AsyncStorage.removeItem('refreshToken');
    await AsyncStorage.removeItem('userId');
    await AsyncStorage.removeItem('userName');

    setUser(null);
    setAuthToken(null);
    setRefreshToken(null);
    console.log('User logged out, tokens cleared.');
  }, []); // 空依赖数组,确保 logout 函数的稳定性

  // --- 4. 刷新访问 Token 的方法 ---
  const refreshAccessToken = useCallback(async (): Promise<boolean> => {
    if (!refreshToken) {
      console.warn('No refresh token available. Cannot refresh access token.');
      await logout(); // 没有刷新 Token,视为需要重新登录
      return false;
    }
    try {
      console.log('Attempting to refresh access token...');
      const response = await fetch('http://10.0.2.2:5000/api/refresh_token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refresh_token: refreshToken }),
      });

      if (!response.ok) {
        const errorData = await response.json();
        console.error('Failed to refresh token:', errorData.message);
        await logout(); // 刷新失败,强制登出
        return false;
      }

      const data = await response.json();
      if (!data.token || !data.refresh_token) { // 后端可能也返回新的刷新token
          console.error('Invalid refresh token response.');
          await logout();
          return false;
      }

      await AsyncStorage.setItem('authToken', data.token);
      await AsyncStorage.setItem('refreshToken', data.refresh_token); // 保存新的刷新 Token
      setAuthToken(data.token);
      setRefreshToken(data.refresh_token); // 更新状态
      console.log('Access token refreshed successfully.');
      return true;
    } catch (error) {
      console.error('Error during token refresh:', error);
      await logout(); // 网络或其他错误,强制登出
      return false;
    }
  }, [refreshToken, logout]); // 依赖 refreshToken 和 logout,确保是最新的

  // 提供的 Context 值
  const contextValue = {
    user,
    authToken,
    refreshToken,
    login,
    logout,
    isLoading,
    refreshAccessToken,
  };

  return (
    <UserContext.Provider value={contextValue}>
      {children}
    </UserContext.Provider>
  );
};

export default UserProvider;

// 自定义 Hook,方便组件使用 Context
export const useUser = () => useContext(UserContext);
src/screens/AuthScreen.tsx

登录界面,用户输入凭据并调用 login 方法。

复制代码
// src/screens/AuthScreen.tsx
import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet, ActivityIndicator, Alert } from 'react-native';
import { useUser } from '../contexts/UserContext'; // 引入 useUser Hook

function AuthScreen() {
  const [username, setUsername] = useState('testuser'); // 默认用户名
  const [password, setPassword] = useState('testpassword'); // 默认密码
  const [isAuthenticating, setIsAuthenticating] = useState(false); // 登录状态
  const { login } = useUser(); // 获取登录方法

  const handleLogin = async () => {
    setIsAuthenticating(true); // 开始登录
    try {
      await login(username, password); // 调用 Context 中的登录方法
      // 登录成功,Context 会自动更新 user 状态,App.tsx 会负责导航
    } catch (error: any) {
      Alert.alert('登录失败', error.message || '请检查用户名和密码。');
      console.error('Login error in AuthScreen:', error);
    } finally {
      setIsAuthenticating(false); // 结束登录
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>用户登录</Text>
      <TextInput
        style={styles.input}
        placeholder="用户名"
        value={username}
        onChangeText={setUsername}
        autoCapitalize="none"
      />
      <TextInput
        style={styles.input}
        placeholder="密码"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      />
      <Button
        title={isAuthenticating ? "登录中..." : "登录"}
        onPress={handleLogin}
        disabled={isAuthenticating} // 登录中禁用按钮
      />
      {isAuthenticating && <ActivityIndicator style={styles.spinner} size="small" color="#0000ff" />}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    marginBottom: 30,
    color: '#333',
  },
  input: {
    width: '100%',
    padding: 15,
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    marginBottom: 15,
    backgroundColor: '#fff',
  },
  spinner: {
    marginTop: 10,
  },
});

export default AuthScreen;
src/screens/HomeScreen.tsx

登录后的主界面,可以访问受保护资源并登出。

TypeScript

复制代码
// src/screens/HomeScreen.tsx
import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet, ActivityIndicator, Alert } from 'react-native';
import { useUser } from '../contexts/UserContext'; // 引入 useUser Hook

function HomeScreen() {
  const { user, authToken, logout, refreshAccessToken } = useUser(); // 获取用户信息、token、登出、刷新方法
  const [protectedData, setProtectedData] = useState<string | null>(null);
  const [isFetchingData, setIsFetchingData] = useState(false);

  // --- 访问受保护数据的方法 ---
  const fetchProtectedData = async () => {
    if (!authToken) {
      Alert.alert('错误', '没有访问 Token,请重新登录。');
      return;
    }

    setIsFetchingData(true);
    try {
      const response = await fetch('http://10.0.2.2:5000/api/protected_data', {
        headers: {
          'Authorization': `Bearer ${authToken}`, // 在 Authorization 头中携带 Token
        },
      });

      if (response.status === 401) { // Unauthorized (Token 可能过期或无效)
        const errorData = await response.json();
        if (errorData.message === "未授权:Token 已过期") {
            console.log('Access Token expired, attempting to refresh...');
            const refreshed = await refreshAccessToken(); // 尝试刷新 Token
            if (refreshed) {
                // 如果刷新成功,可以考虑重新尝试本次请求 (更高级的实现会在拦截器中自动重试)
                Alert.alert('提示', '访问 Token 已刷新,请重新尝试获取数据。');
            } else {
                Alert.alert('会话过期', '您的会话已过期,请重新登录。');
            }
        } else {
            Alert.alert('认证失败', errorData.message || '无效的访问 Token。');
        }
        await logout(); // 遇到 401 且无法刷新,通常需要登出
        return;
      }

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || '获取受保护数据失败。');
      }

      const data = await response.json();
      setProtectedData(data.message);
    } catch (error: any) {
      Alert.alert('错误', `获取数据失败: ${error.message}`);
      console.error('Error fetching protected data:', error);
    } finally {
      setIsFetchingData(false);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>欢迎,{user?.user_name || '用户'}!</Text>
      <Text style={styles.info}>您的用户ID: {user?.user_id}</Text>
      {authToken && (
        <Text style={styles.info}>访问 Token: {authToken.substring(0, 20)}...</Text>
      )}
      {refreshToken && (
        <Text style={styles.info}>刷新 Token: {refreshToken.substring(0, 20)}...</Text>
      )}


      <Button
        title={isFetchingData ? "获取中..." : "获取受保护数据"}
        onPress={fetchProtectedData}
        disabled={isFetchingData || !authToken} // 没有 Token 或正在获取时禁用
      />
      {isFetchingData && <ActivityIndicator style={styles.spinner} size="small" color="#0000ff" />}

      {protectedData && (
        <View style={styles.dataContainer}>
          <Text style={styles.dataTitle}>受保护数据:</Text>
          <Text style={styles.dataText}>{protectedData}</Text>
        </View>
      )}

      <View style={styles.logoutButtonContainer}>
        <Button title="登出" onPress={logout} color="red" />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 10,
    color: '#333',
  },
  info: {
    fontSize: 16,
    marginBottom: 5,
    color: '#555',
  },
  spinner: {
    marginTop: 10,
  },
  dataContainer: {
    marginTop: 30,
    padding: 15,
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    backgroundColor: '#e9e9e9',
    width: '100%',
    alignItems: 'center',
  },
  dataTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
    color: '#333',
  },
  dataText: {
    fontSize: 16,
    textAlign: 'center',
    color: '#666',
  },
  logoutButtonContainer: {
    marginTop: 40,
    width: '80%',
  },
});

export default HomeScreen;
src/navigation/MainNavigator.tsx (主应用内部导航)

TypeScript

复制代码
// src/navigation/MainNavigator.tsx
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/HomeScreen';
// 你可以添加更多登录后的页面

const Stack = createStackNavigator();

function MainNavigator() {
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      <Stack.Screen name="Home" component={HomeScreen} />
      {/* <Stack.Screen name="Settings" component={SettingsScreen} /> */}
    </Stack.Navigator>
  );
}

export default MainNavigator;
src/navigation/AuthNavigator.tsx (认证页面导航)
复制代码
// src/navigation/AuthNavigator.tsx
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import AuthScreen from '../screens/AuthScreen';

const Stack = createStackNavigator();

function AuthNavigator() {
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      <Stack.Screen name="Login" component={AuthScreen} />
      {/* <Stack.Screen name="Register" component={RegisterScreen} /> */}
    </Stack.Navigator>
  );
}

export default AuthNavigator;
App.tsx (应用入口)

这是应用的主文件,它设置了 UserProvider 并根据用户的登录状态来切换导航器。

TypeScript

复制代码
// App.tsx
import 'react-native-gesture-handler'; // react-navigation 必备
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { ActivityIndicator, View, StyleSheet, Text } from 'react-native';

import UserProvider, { useUser } from './src/contexts/UserContext'; // 引入你的 UserProvider 和 useUser Hook
import MainNavigator from './src/navigation/MainNavigator';
import AuthNavigator from './src/navigation/AuthNavigator';

const Stack = createStackNavigator();

// 根导航器,根据认证状态切换主/认证流程
function RootNavigator() {
  const { user, isLoading } = useUser(); // 从 Context 获取 user 和 isLoading 状态

  if (isLoading) {
    // 如果正在加载用户数据,显示加载指示器或启动屏
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicator size="large" color="#0000ff" />
        <Text style={styles.loadingText}>正在加载应用数据...</Text>
      </View>
    );
  }

  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      {user ? (
        // 如果 user 不为 null (已登录),显示主应用界面
        <Stack.Screen name="Main" component={MainNavigator} />
      ) : (
        // 如果 user 为 null (未登录),显示认证界面
        <Stack.Screen name="Auth" component={AuthNavigator} />
      )}
    </Stack.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <UserProvider>
        <RootNavigator />
      </UserProvider>
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  loadingText: {
    marginTop: 10,
    fontSize: 16,
    color: '#555',
  },
});
4. 项目结构
复制代码
my-react-native-app/
├── App.tsx
├── package.json
├── src/
│   ├── contexts/
│   │   └── UserContext.tsx
│   ├── navigation/
│   │   ├── AuthNavigator.tsx
│   │   └── MainNavigator.tsx
│   └── screens/
│       ├── AuthScreen.tsx
│       └── HomeScreen.tsx
└── backend/ (你的后端代码,例如 app.py)
    └── app.py

总结

  • UserProvider : 是 Token 认证的核心。它负责在应用启动时从 AsyncStorage 中加载 Token,管理登录/登出逻辑,并通过 Context API 将 user 信息、authTokenrefreshTokenisLoading 状态暴露给整个应用。
  • isLoading 状态 : 用于在应用启动时,等待 UserProviderAsyncStorage 加载完数据。在这期间,你应该显示一个加载指示器,避免闪烁或提前渲染错误内容。
  • login 方法 : 用户登录成功后,从后端获取 Token 并将其保存在 AsyncStorage 和 Context 状态中。
  • logout 方法 : 清除 AsyncStorage 中的 Token 和 Context 状态。
  • refreshAccessToken 方法: 处理访问 Token 过期的情况。它会尝试使用刷新 Token 获取新的访问 Token。
  • 网络请求 : 在需要访问受保护资源时,从 useUser() 中获取 authToken,并将其添加到请求的 Authorization: Bearer <token> 头中。
  • 错误处理: 特别是对于 401/403 错误,需要捕获并引导用户重新认证(例如,跳转到登录页)。

这个示例提供了一个基本但完整的 React Native Token 认证流程。在生产环境中,你可能还需要考虑更复杂的错误处理、Token 有效性检查、安全性增强(如 HTTPS)、更强大的网络请求库(如 Axios 及其拦截器)以及更完善的用户信息管理。

相关推荐
千码君20161 天前
React Native:为什么带上version就会报错呢?
javascript·react native·react.js
xiangzhihong82 天前
XTransfer 开源 XRN 框架:基于 React Native 的跨三端开发实践与鸿蒙 NEXT 攻坚
react native
phdsky2 天前
【设计模式】状态模式
设计模式·状态模式
wayne2142 天前
ReactNative性能优化实践方案
react native·react.js·性能优化
默默地离开2 天前
小白学习react native 第一天
前端·react native
风槐啊3 天前
邪修实战系列(6)
java·ide·windows·spring boot·状态模式
默默地离开3 天前
React Native 入门实战:样式、状态管理与网络请求全解析 (二)
前端·react native
木西6 天前
React Native DApp 开发全栈实战·从 0 到 1 系列(完结篇)
react native·web3·智能合约
冰冷的bin7 天前
【React Native】文本跑马灯组件MarqueeText
react native·typescript
木西8 天前
React Native DApp 开发全栈实战·从 0 到 1 系列(兑换-前端部分)
react native·web3·solidity