在鸿蒙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();
});
五、安全增强与最佳实践
-
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); // 每分钟检查一次 -
加密存储敏感信息
typescript// 使用鸿蒙加解密API处理敏感数据 import { cryptoFramework } from '@ohos.security.cryptoFramework'; // 加密Token后再传递给Web async function encryptToken(token: string): Promise<string> { // 实际实现使用鸿蒙加密库 return btoa(token); // 简单base64示例 } -
通信安全验证
javascript// 在Web侧验证消息来源 if (window.harmonyBridge) { // 添加消息签名验证 window.harmonyBridge.validateMessage = (message, signature) => { // 验证消息是否来自可信的鸿蒙应用 }; }
通过上述方案,Hash路由模式下的鸿蒙Web组件与Vue应用之间可以建立安全、可靠的登录态传递机制,支持深度跳转、页面刷新和持久化登录状态,满足企业级应用的安全要求。