鸿蒙Web组件中Hash路由传登录态方案

在鸿蒙ArkUI Web组件中,当Vue应用使用Hash路由模式时,传递登录态到Vue应用需要建立原生与Web之间的安全通信机制,并确保认证状态在页面刷新和深度跳转时保持持久化。以下是具体的技术方案和实现步骤:

一、核心原理与架构设计

Hash路由模式下,Vue应用运行在Web组件的沙箱环境中,与原生鸿蒙应用存在天然的隔离。传递登录态的本质是跨域/跨上下文的状态同步,需通过以下两种机制协同工作:

传递机制 适用场景 优点 缺点
URL参数注入 首次加载时传递一次性Token 实现简单,无需等待页面加载完成 安全性较低,Token暴露在URL中;刷新后失效
JavaScript桥接+本地存储 运行时状态同步与持久化 安全性高,支持复杂数据;刷新后状态可恢复 需等待页面加载完成;实现复杂度较高

二、方案一:URL参数注入(适合简单场景)

在首次加载Vue应用时,将Token作为URL哈希参数的一部分传递给前端,Vue应用在初始化时解析并存储Token。

1. 鸿蒙原生侧实现(ArkTS)

typescript 复制代码
import web_webview from '@ohos.web.webview';
import { BusinessError } from '@ohos.base';

@Entry
@Component
struct AuthWebView {
  controller: web_webview.WebviewController = new web_webview.WebviewController();
  
  // 从原生存储中获取用户Token
  @State userToken: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // JWT示例
  
  build() {
    Column() {
      // 将Token作为URL参数传递
      Web({ 
        src: `$rawfile('dist/index.html')#/dashboard?token=${this.userToken}`, 
        controller: this.controller 
      })
      .width('100%')
      .height('100%')
      .javaScriptAccess(true)
      .onPageFinish((url: string) => {
        console.info(`页面加载完成,URL: ${url}`);
      })
    }
  }
}

2. Vue应用侧实现(解析URL参数)

javascript 复制代码
// src/utils/auth.js - 解析URL中的Token参数
export function parseTokenFromURL() {
  const hash = window.location.hash;
  const queryString = hash.split('?')[1];
  
  if (!queryString) return null;
  
  const params = new URLSearchParams(queryString);
  const token = params.get('token');
  
  if (token) {
    // 存储到localStorage供后续使用 
    localStorage.setItem('auth_token', token);
    
    // 从URL中移除token参数(增强安全性)
    const cleanHash = hash.split('?')[0];
    window.history.replaceState(null, '', window.location.pathname + cleanHash);
    
    return token;
  }
  return null;
}

// src/main.js - 应用启动时调用
import { parseTokenFromURL } from './utils/auth';
import router from './router';
import { createApp } from 'vue';
import App from './App.vue';

// 在创建Vue实例前解析Token
const token = parseTokenFromURL();
if (token) {
  // 将Token设置到全局状态管理(如Vuex/Pinia)
  console.log('从URL获取到Token:', token.substring(0, 20) + '...');
}

const app = createApp(App);
app.use(router);
app.mount('#app');

⚠️ 安全提醒:此方案将敏感Token暴露在URL中,可能被浏览器历史记录或第三方扩展截获,仅适用于低安全要求的内部系统。

三、方案二:JavaScript桥接+本地存储(推荐生产环境)

通过建立原生与Web的JavaScript通信桥接,安全地传递认证信息,并结合本地存储实现状态持久化。

1. 鸿蒙原生侧:建立认证桥接接口

typescript 复制代码
// AuthBridge.ets - 认证桥接服务
import web_webview from '@ohos.web.webview';
import { BusinessError } from '@ohos.base';
import { preferences } from '@ohos.data.preferences';

export class AuthWebBridge {
  private controller: web_webview.WebviewController;
  private pref: preferences.Preferences;
  
  constructor(controller: web_webview.WebviewController) {
    this.controller = controller;
    // 初始化本地偏好存储 [类似Web的localStorage]
    preferences.getPreferences(this.controller.getContext(), 'auth_store')
      .then((pref) => {
        this.pref = pref;
      });
  }
  
  // 注入用户认证信息到Web环境
  async injectAuthData(): Promise<void> {
    // 从鸿蒙安全存储中获取用户Token
    const userToken = await this.getSecureToken();
    const userInfo = {
      token: userToken,
      userId: 'user_123456',
      userName: '张三',
      roles: ['admin', 'editor'],
      expiresAt: Date.now() + 3600000 // 1小时后过期
    };
    
    // 构建注入的JavaScript代码
    const jsCode = `
      (function() {
        // 创建全局认证对象
        window.harmonyAuth = {
          userInfo: ${JSON.stringify(userInfo)},
          initializedAt: ${Date.now()},
          
          // 存储到Web本地存储
          persistToStorage: function() {
            try {
              localStorage.setItem('harmony_auth_data', JSON.stringify(this.userInfo));
              localStorage.setItem('harmony_auth_sync', '${Date.now()}');
              console.log('认证数据已持久化到localStorage');
            } catch (e) {
              console.error('存储认证数据失败:', e);
            }
          },
          
          // 验证Token有效性
          isValid: function() {
            return this.userInfo && 
                   this.userInfo.expiresAt > Date.now() &&
                   this.userInfo.token.length > 10;
          }
        };
        
        // 立即持久化
        window.harmonyAuth.persistToStorage();
        
        // 派发自定义事件,通知Vue应用
        window.dispatchEvent(new CustomEvent('harmony-auth-ready', {
          detail: window.harmonyAuth.userInfo
        }));
        
        console.log('鸿蒙认证数据注入完成');
      })();
    `;
    
    // 在页面加载完成后执行注入
    this.controller.runJavaScript(jsCode, (err: BusinessError, result: string) => {
      if (err) {
        console.error(`注入认证数据失败: ${JSON.stringify(err)}`);
      } else {
        console.info(`认证数据注入成功: ${result}`);
      }
    });
  }
  
  // 从鸿蒙安全存储获取Token(模拟)
  private async getSecureToken(): Promise<string> {
    // 实际项目中应从鸿蒙安全存储系统获取
    return 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
  }
  
  // 清理认证数据
  async clearAuthData(): Promise<void> {
    const jsCode = `
      localStorage.removeItem('harmony_auth_data');
      localStorage.removeItem('harmony_auth_sync');
      delete window.harmonyAuth;
      console.log('认证数据已清理');
    `;
    
    this.controller.runJavaScript(jsCode);
  }
}

2. Vue应用侧:接收并管理认证状态

javascript 复制代码
// src/services/auth.service.js - Vue认证服务
class AuthService {
  constructor() {
    this.userInfo = null;
    this.isInitialized = false;
    
    // 监听鸿蒙注入事件
    window.addEventListener('harmony-auth-ready', this.handleHarmonyAuth.bind(this));
    
    // 尝试从本地存储恢复
    this.restoreFromStorage();
  }
  
  // 处理鸿蒙注入的认证数据
  handleHarmonyAuth(event) {
    console.log('收到鸿蒙认证数据:', event.detail);
    this.userInfo = event.detail;
    this.persistToStorage();
    this.isInitialized = true;
    
    // 通知Vue组件认证就绪
    this.notifyAuthReady();
  }
  
  // 从localStorage恢复认证状态 
  restoreFromStorage() {
    try {
      const stored = localStorage.getItem('harmony_auth_data');
      if (stored) {
        this.userInfo = JSON.parse(stored);
        
        // 检查Token是否过期
        if (this.userInfo.expiresAt > Date.now()) {
          this.isInitialized = true;
          console.log('从localStorage恢复认证状态成功');
        } else {
          console.warn('存储的Token已过期');
          this.clearAuth();
        }
      }
    } catch (error) {
      console.error('恢复认证状态失败:', error);
      this.clearAuth();
    }
  }
  
  // 持久化到本地存储
  persistToStorage() {
    if (this.userInfo) {
      localStorage.setItem('harmony_auth_data', JSON.stringify(this.userInfo));
      localStorage.setItem('harmony_auth_last_update', Date.now().toString());
    }
  }
  
  // 获取认证Token(用于API请求)
  getAuthToken() {
    return this.userInfo?.token || null;
  }
  
  // 获取用户信息
  getUserInfo() {
    return this.userInfo ? { ...this.userInfo } : null;
  }
  
  // 检查是否已认证
  isAuthenticated() {
    return !!this.userInfo && this.userInfo.expiresAt > Date.now();
  }
  
  // 清理认证数据
  clearAuth() {
    this.userInfo = null;
    this.isInitialized = false;
    localStorage.removeItem('harmony_auth_data');
    localStorage.removeItem('harmony_auth_last_update');
    
    // 通知鸿蒙原生侧同步清理
    if (window.harmonyBridge && window.harmonyBridge.logout) {
      window.harmonyBridge.logout();
    }
  }
  
  // 通知Vue组件
  notifyAuthReady() {
    window.dispatchEvent(new CustomEvent('auth-state-changed', {
      detail: { isAuthenticated: this.isAuthenticated() }
    }));
  }
}

// 创建单例实例
export const authService = new AuthService();

// Vue插件形式集成
export const authPlugin = {
  install(app) {
    app.config.globalProperties.$auth = authService;
    app.provide('auth', authService);
  }
};

3. Vue组件中使用认证状态

vue 复制代码
<!-- src/components/Dashboard.vue -->
<template>
  <div class="dashboard">
    <div v-if="loading">加载用户信息...</div>
    <div v-else-if="userInfo">
      <h1>欢迎回来,{{ userInfo.userName }}!</h1>
      <p>用户ID: {{ userInfo.userId }}</p>
      <p>角色: {{ userInfo.roles.join(', ') }}</p>
      <button @click="logout">退出登录</button>
    </div>
    <div v-else>
      <p>未检测到登录状态,请重新登录</p>
      <button @click="requestLogin">请求登录</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { authService } from '../services/auth.service';

const loading = ref(true);
const userInfo = ref(null);

// 监听认证状态变化
const handleAuthChange = (event) => {
  console.log('认证状态变化:', event.detail);
  updateUserInfo();
};

const updateUserInfo = () => {
  userInfo.value = authService.getUserInfo();
  loading.value = false;
};

const logout = () => {
  authService.clearAuth();
  userInfo.value = null;
  // 可以跳转到登录页
  // router.push('/login');
};

const requestLogin = () => {
  // 通过桥接请求原生登录
  if (window.harmonyBridge && window.harmonyBridge.requestLogin) {
    window.harmonyBridge.requestLogin();
  }
};

onMounted(() => {
  // 初始检查
  updateUserInfo();
  
  // 监听认证状态变化事件
  window.addEventListener('auth-state-changed', handleAuthChange);
  
  // 如果未初始化,尝试从鸿蒙获取
  if (!authService.isInitialized && window.harmonyBridge) {
    window.harmonyBridge.requestAuthData?.();
  }
});

onUnmounted(() => {
  window.removeEventListener('auth-state-changed', handleAuthChange);
});
</script>

四、深度跳转时的状态保持策略

在Hash路由深度跳转场景下,确保登录态不丢失需要特殊处理:

1. 鸿蒙侧深度跳转时注入状态

typescript 复制代码
// DeepLinkService.ets - 深度跳转服务
export class DeepLinkService {
  static navigateToDeepLink(
    controller: web_webview.WebviewController, 
    path: string, 
    authData?: object
  ) {
    // 构建带认证数据的跳转脚本
    const jsCode = `
      (function() {
        // 如果有新的认证数据,更新存储
        ${authData ? `localStorage.setItem('harmony_auth_data', '${JSON.stringify(authData)}');` : ''}
        
        // 执行路由跳转
        if (window.harmonyBridge && window.harmonyBridge.navigateTo) {
          window.harmonyBridge.navigateTo('${path}');
        } else {
          // 直接修改hash
          window.location.hash = '#/${path}';
        }
      })();
    `;
    
    controller.runJavaScript(jsCode, (err, result) => {
      if (err) {
        console.error(`深度跳转失败: ${JSON.stringify(err)}`);
        // 降级方案:直接加载带hash的URL
        controller.loadUrl(`$rawfile('dist/index.html')#/${path}`);
      }
    });
  }
}

2. Vue路由守卫中验证状态

javascript 复制代码
// src/router/index.js - 路由配置
import { createRouter, createWebHashHistory } from 'vue-router';
import { authService } from '../services/auth.service';

const routes = [
  { path: '/', component: () => import('../views/Home.vue') },
  { 
    path: '/dashboard', 
    component: () => import('../views/Dashboard.vue'),
    meta: { requiresAuth: true } // 需要认证
  },
  { 
    path: '/admin', 
    component: () => import('../views/Admin.vue'),
    meta: { requiresAuth: true, requiredRoles: ['admin'] }
  },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes
});

// 全局路由守卫
router.beforeEach((to, from, next) => {
  // 检查路由是否需要认证
  if (to.meta.requiresAuth) {
    if (!authService.isAuthenticated()) {
      console.warn('未认证访问受保护路由:', to.path);
      
      // 尝试从存储恢复
      authService.restoreFromStorage();
      
      if (!authService.isAuthenticated()) {
        // 通知鸿蒙需要登录
        if (window.harmonyBridge && window.harmonyBridge.showLogin) {
          window.harmonyBridge.showLogin();
        }
        
        // 跳转到登录页或首页
        return next('/');
      }
    }
    
    // 检查角色权限
    if (to.meta.requiredRoles) {
      const userRoles = authService.getUserInfo()?.roles || [];
      const hasRole = to.meta.requiredRoles.some(role => 
        userRoles.includes(role)
      );
      
      if (!hasRole) {
        console.error('权限不足:', to.path);
        return next('/unauthorized');
      }
    }
  }
  
  next();
});

五、安全增强与最佳实践

  1. Token刷新机制

    javascript 复制代码
    // 定期检查Token有效期
    setInterval(() => {
      if (authService.isAuthenticated()) {
        const expiresIn = authService.userInfo.expiresAt - Date.now();
        
        // 有效期少于5分钟时自动刷新
        if (expiresIn < 5 * 60 * 1000) {
          window.harmonyBridge?.refreshToken?.()
            .then(newToken => {
              authService.userInfo.token = newToken;
              authService.userInfo.expiresAt = Date.now() + 3600000;
              authService.persistToStorage();
            });
        }
      }
    }, 60000); // 每分钟检查一次
  2. 加密存储敏感信息

    typescript 复制代码
    // 使用鸿蒙加解密API处理敏感数据
    import { cryptoFramework } from '@ohos.security.cryptoFramework';
    
    // 加密Token后再传递给Web
    async function encryptToken(token: string): Promise<string> {
      // 实际实现使用鸿蒙加密库
      return btoa(token); // 简单base64示例
    }
  3. 通信安全验证

    javascript 复制代码
    // 在Web侧验证消息来源
    if (window.harmonyBridge) {
      // 添加消息签名验证
      window.harmonyBridge.validateMessage = (message, signature) => {
        // 验证消息是否来自可信的鸿蒙应用
      };
    }

通过上述方案,Hash路由模式下的鸿蒙Web组件与Vue应用之间可以建立安全、可靠的登录态传递机制,支持深度跳转、页面刷新和持久化登录状态,满足企业级应用的安全要求。​​​

相关推荐
nashane1 小时前
HarmonyOS 6学习:Canvas性能优化与长截图流畅实现实战
学习·性能优化·harmonyos
轻口味2 小时前
HarmonyOS 6.1 全栈实战录 - 13 流量增长新引擎:全场景归因与 App Linking 链接深度开发实战
pytorch·深度学习·harmonyos
LCG元2 小时前
STM32实战:基于STM32F103的智慧教室环境监控系统(CO₂+光照+人数统计)
前端·stm32·嵌入式硬件
yqcoder2 小时前
Vue 的心脏:深度解析 Vue 2 vs Vue 3 响应式机制
前端·javascript·vue.js
东方小月2 小时前
Claude Code Skill 完全指南:一个 markdown 文件,就是一个专家分身
前端·后端
DianSan_ERP2 小时前
抖店订单接口中消费者信息加密解密机制与安全履约全解析
前端·网络·数据库·后端·安全·团队开发·运维开发
PBitW3 小时前
一个skill,让项目管理和写绩效变得简单!
前端·trae
Dxy12393102163 小时前
CSS中的filter属性详解
前端·css
Vincent_czr3 小时前
iOS中常常遇到后端返回JSON出现null值问题
前端