HarmonyOS 5 ------访问权限控制

一、权限控制体系设计与实现

1. 凭证存储与 Auth 工具封装

技术原理

采用持久化存储方案 实现用户凭证(Token / 用户信息)的安全存储,基于 HarmonyOS 的AsyncStorage实现键值对管理,通过模块化封装提升复用性。

代码实现

typescript 复制代码
// auth.ts
import { AsyncStorage } from '@ohos.data.secureStore';
import { AuthError } from '../errors/AuthError'; // 自定义错误处理模块

// 定义凭证存储规范
export enum AuthKey {
  USER_CREDENTIAL = 'user_credential_v1', // 凭证存储Key(版本化管理)
  AUTH_TIMESTAMP = 'auth_timestamp' // 凭证时效标记
}

/**
 * @class AuthManager
 * @description 权限管理核心类,负责凭证的持久化与生命周期管理
 */
export class AuthManager {
  /**
   * 初始化用户凭证(含时效验证)
   * @returns {Promise<UserCredential | null>} 初始化后的用户凭证对象
   */
  async initialize(): Promise<UserCredential | null> {
    try {
      const rawCredential = await AsyncStorage.getString(AuthKey.USER_CREDENTIAL);
      if (!rawCredential) return null;
      
      const credential = JSON.parse(rawCredential) as UserCredential;
      const timestamp = await this.getAuthTimestamp();
      
      // 简单时效验证(可扩展为Token过期时间校验)
      if (Date.now() - timestamp > 3600 * 1000) { // 1小时有效期
        await this.clear();
        return null;
      }
      
      return credential;
    } catch (error) {
      console.error('[AuthManager] 初始化失败:', error);
      throw new AuthError('INIT_FAILED', '凭证初始化失败');
    }
  }

  /**
   * 存储用户凭证
   * @param {UserCredential} credential 用户凭证对象(含Token/用户信息)
   * @returns {Promise<void>}
   */
  async setCredential(credential: UserCredential): Promise<void> {
    try {
      await Promise.all([
        AsyncStorage.setString(AuthKey.USER_CREDENTIAL, JSON.stringify(credential)),
        AsyncStorage.setNumber(AuthKey.AUTH_TIMESTAMP, Date.now())
      ]);
    } catch (error) {
      console.error('[AuthManager] 存储失败:', error);
      throw new AuthError('STORAGE_FAILED', '凭证存储失败');
    }
  }

  /**
   * 获取用户凭证
   * @returns {Promise<UserCredential | null>}
   */
  async getCredential(): Promise<UserCredential | null> {
    try {
      const rawCredential = await AsyncStorage.getString(AuthKey.USER_CREDENTIAL);
      return rawCredential ? (JSON.parse(rawCredential) as UserCredential) : null;
    } catch (error) {
      console.error('[AuthManager] 获取失败:', error);
      throw new AuthError('FETCH_FAILED', '凭证获取失败');
    }
  }

  /**
   * 清除凭证
   * @returns {Promise<void>}
   */
  async clear(): Promise<void> {
    await Promise.all([
      AsyncStorage.delete(AuthKey.USER_CREDENTIAL),
      AsyncStorage.delete(AuthKey.AUTH_TIMESTAMP)
    ]);
  }

  private async getAuthTimestamp(): Promise<number> {
    return AsyncStorage.getNumber(AuthKey.AUTH_TIMESTAMP) || 0;
  }
}

// 导出单例实例
export const auth = new AuthManager();

// 类型定义(user.d.ts)
interface UserCredential {
  token: string; // 认证Token
  userId: string; // 用户唯一标识
  username: string; // 用户名称
  expiresIn: number; // 过期时间(秒)
}

2. 首页初始化与登录流程

技术方案

  • 通过aboutToAppear生命周期钩子实现页面级凭证初始化

  • 登录流程采用模块化交互设计,分离 UI 逻辑与权限逻辑

代码实现

typescript 复制代码
// HomePage.ets
import { auth } from '../services/auth';
import { UserCredential } from '../types/user';
import { router } from '@ohos.router';

@Entry
@Component
struct HomePage {
  @State credential: UserCredential | null = null;
  @State isLoading: boolean = true;

  // 页面初始化钩子
  aboutToAppear() {
    this.initAuthState();
  }

  // 权限状态初始化
  private async initAuthState() {
    try {
      const credential = await auth.initialize();
      this.credential = credential;
    } catch (error) {
      console.error('[HomePage] 初始化失败:', error);
    } finally {
      this.isLoading = false;
    }
  }

  // 登录路由跳转
  private navigateToLogin() {
    router.pushUrl({ url: 'pages/LoginPage' });
  }

  build() {
    Column() {
      if (isLoading) {
        Text('初始化中...').fontSize(16);
      } else if (credential) {
        // 已登录界面
        UserInfoCard({ user: credential });
        Button('退出登录').onClick(() => auth.clear().then(() => router.replaceUrl('pages/LoginPage')));
      } else {
        // 未登录界面
        PublicLanding(); // 公共着陆页组件
        Button('立即登录').type(ButtonType.Capsule).onClick(this.navigateToLogin.bind(this));
      }
    }.width('100%').height('100%');
  }
}

// 登录页核心逻辑(LoginPage.ets)
@Entry
@Component
struct LoginPage {
  @State formData: LoginForm = { username: '', password: '' };
  @State errorMsg: string | null = null;

  async handleLogin() {
    try {
      // 模拟登录请求(实际需调用后端API)
      const response = await fetch('https://api.example.com/auth/login', {
        method: 'POST',
        body: JSON.stringify(formData),
        headers: { 'Content-Type': 'application/json' }
      });
      
      if (!response.ok) throw new Error('登录失败');
      const credential = await response.json() as UserCredential;
      
      // 存储凭证并通知全局
      await auth.setCredential(credential);
      emitter.emit('auth:loginSuccess'); // 触发登录成功事件
      
      // 处理回跳逻辑
      const returnUrl = router.getParams()?.return_url || 'pages/HomePage';
      router.replaceUrl(returnUrl);
      
    } catch (error) {
      this.errorMsg = '用户名或密码错误';
      console.error('[LoginPage] 登录失败:', error);
    }
  }
}

二、请求凭证拦截与失效处理

1. 全局请求拦截器

技术原理

基于HTTP 中间件模式 实现请求凭证自动注入,通过状态码(401 Unauthorized)识别权限失效场景,采用单例锁机制避免多次重复跳转。

代码实现

javascript 复制代码
// http-interceptor.ts
import { auth } from '../services/auth';
import { router } from '@ohos.router';

const UNAUTHORIZED_STATUS = 401;
let isProcessing = false; // 防止并发请求重复处理

/**
 * HTTP请求拦截器
 * @param {RequestOptions} options 请求配置
 * @returns {RequestOptions} 增强后的请求配置
 */
export function authInterceptor(options: RequestOptions): RequestOptions {
  const newOptions = { ...options };
  // 注入凭证(仅处理非登录请求)
  if (!newOptions.url.includes('/auth/login')) {
    newOptions.headers = newOptions.headers || {};
    newOptions.headers.Authorization = `Bearer ${auth.getCredential()?.token || ''}`;
  }
  return newOptions;
}

/**
 * 响应处理器
 * @param {Response} response 原始响应
 * @returns {Promise<Response>} 处理后的响应
 */
export async function responseHandler(response: Response): Promise<Response> {
  if (response.status === UNAUTHORIZED_STATUS) {
    await handleAuthFailure();
    throw new AuthError('UNAUTHORIZED', '权限验证失败');
  }
  return response;
}

// 权限失效处理
async function handleAuthFailure() {
  if (isProcessing) return;
  isProcessing = true;
  
  try {
    await auth.clear(); // 清除失效凭证
    const currentUrl = router.getCurrentRoute()?.url || 'pages/HomePage';
    router.pushUrl({ url: 'pages/LoginPage', params: { return_url: currentUrl } });
  } finally {
    isProcessing = false;
  }
}

2. 接口调用示例

typescript 复制代码
// api.service.ts
import { authInterceptor, responseHandler } from './http-interceptor';

interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}

export class ApiService {
  static async get<T>(url: string): Promise<T> {
    const response = await fetch(url, authInterceptor({ method: 'GET' }));
    return responseHandler(response).then(res => res.json() as ApiResponse<T>).then(res => res.data);
  }
}

// 使用示例(首页数据获取)
class HomeModel {
  async loadQuestions() {
    return ApiService.get<Question[]>('/api/questions');
  }
}

三、首页数据更新与事件通信

1. 基于发布 - 订阅模式的事件通知

技术方案

采用 ** 轻量级事件总线(Emitter)** 实现跨组件通信,避免组件间直接依赖,提升可维护性。

代码实现

typescript 复制代码
// event-bus.ts
type EventCallback = (...args: any[]) => void;

class EventEmitter {
  private events: Record<string, EventCallback[]> = {};

  on(eventName: string, callback: EventCallback): void {
    if (!this.events[eventName]) this.events[eventName] = [];
    this.events[eventName].push(callback);
  }

  off(eventName: string, callback: EventCallback): void {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
    }
  }

  emit(eventName: string, ...args: any[]): void {
    this.events[eventName]?.forEach(callback => callback(...args));
  }
}

export const emitter = new EventEmitter();

// 首页组件绑定事件
@Entry
@Component
struct HomeCategory {
  @State questions: Question[] = [];

  constructor() {
    emitter.on('auth:loginSuccess', this.refreshQuestions.bind(this));
  }

  private async refreshQuestions() {
    const model = new HomeModel();
    this.questions = await model.loadQuestions();
  }

  // 组件渲染逻辑...
}

四、访问控制与路由鉴权

1. 鉴权方法封装

php 复制代码
// auth.ts(扩展鉴权方法)
export class AuthManager {
  // ... 原有方法 ...

  /**
   * 路由鉴权方法
   * @param {string} targetUrl 目标路由地址
   * @param {Function} callback 鉴权通过后的执行函数
   */
  async authorize(targetUrl: string, callback: () => void): Promise<void> {
    const credential = await this.getCredential();
    if (credential) {
      callback(); // 鉴权通过执行回调
    } else {
      // 存储回跳地址并跳转登录页
      router.pushUrl({ url: 'pages/LoginPage', params: { return_url: targetUrl } });
    }
  }
}

2. 路由使用示例

typescript 复制代码
// 需要鉴权的页面
@Entry
@Component
struct ProtectedPage {
  build() {
    Button('进入受保护页面')
      .onClick(() => {
        auth.authorize('pages/ProtectedPage', () => {
          router.pushUrl('pages/ProtectedPage');
        });
      });
  }
}

// 登录页回跳处理
@Entry
@Component
struct LoginPage {
  aboutToAppear() {
    const returnUrl = router.getParams()?.return_url as string;
    if (returnUrl) {
      // 登录成功后替换为回跳地址
      this.returnUrl = returnUrl;
    }
  }

  async handleLogin() {
    // ... 登录逻辑 ...
    router.replaceUrl(this.returnUrl || 'pages/HomePage');
  }
}

五、隐私协议与用户协议页面

1. 通用 Web 页面组件

typescript 复制代码
// WebPage.ets(增强版)
import { webview } from '@ohos.web.webview';
import { router } from '@ohos.router';
import { PageTitle } from '@ohos.app.ability';

@Entry
@Component
struct WebPage {
  @State pageTitle: string = '协议内容';
  @State targetUrl: string = '';
  private controller = new webview.WebviewController();

  aboutToAppear() {
    const params = router.getParams() as { title: string; url: string };
    this.pageTitle = params.title;
    this.targetUrl = params.url;
    
    // 设置页面标题
    const titleBar = PageTitle.getPageTitle();
    titleBar.setTitle(this.pageTitle);
  }

  build() {
    Column() {
      Web({ src: this.targetUrl, controller: this.controller })
        .layoutWeight(1)
        .width('100%')
        .height('100%')
        .onPageFinished((url) => {
          console.log('[WebPage] 页面加载完成:', url);
        });
    }
    .onDestroy(() => this.controller.destroy()) // 资源释放
    .requirePermissions(['ohos.permission.INTERNET']); // 权限声明
  }
}

2. 协议入口调用

php 复制代码
// LoginPage.ets(协议入口)
Text('用户协议')
  .fontSize(14)
  .textColor('#1890FF')
  .onClick(() => {
    router.pushUrl({
      url: 'pages/WebPage',
      params: { title: '用户协议', url: 'http://110.41.143.89/user' }
    });
  });

Text('隐私政策')
  .fontSize(14)
  .margin({ left: 8 })
  .textColor('#1890FF')
  .onClick(() => {
    router.pushUrl({
      url: 'pages/WebPage',
      params: { title: '隐私政策', url: 'http://110.41.143.89/privacy' }
    });
  });

六、工程化增强建议

  1. 凭证加密存储

    使用@ohos.security.hcrypt对存储的凭证进行 AES 加密,提升数据安全性。

  2. Token 刷新机制

    实现refresh_token机制,当401状态码返回时自动请求新 Token,减少用户登录次数。

  3. 权限层级管理

    扩展UserCredential包含角色信息(如role: 'admin' | 'user'),在路由鉴权中增加角色校验逻辑。

  4. 请求队列管理

    在凭证失效处理中引入请求队列,确保刷新 Token 期间的请求有序执行。

该方案遵循分层架构设计原则,将权限逻辑、存储逻辑、UI 逻辑分离,具备可测试性与可扩展性,适用于中大型 HarmonyOS 应用的权限管理场景。

相关推荐
lqj_本人10 小时前
鸿蒙OS&UniApp结合机器学习打造智能图像分类应用:HarmonyOS实践指南#三方框架 #Uniapp
机器学习·uni-app·harmonyos
哼唧唧_10 小时前
使用 React Native 开发鸿蒙运动健康类应用的高频易错点总结
react native·react.js·harmonyos·harmony os5·运动健康
二流小码农13 小时前
鸿蒙开发:loading动画的几种实现方式
android·ios·harmonyos
大胖子10113 小时前
HarmonyOS5ArkTS常见数据类型认识
harmonyos
大胖子10114 小时前
HarmonyOS5鸿蒙开发常用装饰器
harmonyos
大胖子10114 小时前
HarmonyOS5鸿蒙开发常用组件介绍
harmonyos
小镇梦想家15 小时前
鸿蒙NEXT-Flutter(1)
harmonyos
zhanshuo16 小时前
安卓→鸿蒙迁移实战:3步重构消息提示,解锁跨设备协同黑科技!
harmonyos
不爱吃糖的程序媛16 小时前
鸿蒙版Taro 搭建开发环境
华为·harmonyos·taro
zhanshuo17 小时前
鸿蒙实战:智能灯泡状态监控,低功耗预警方案揭秘!
harmonyos