feat(miniprogram): Token XOR 混淆存储
- 新增 secure-storage 工具:XOR + Base64 混淆 token 存储 - request.ts 和 auth.ts 中所有 access_token/refresh_token 存取 均通过 secure-storage,避免明文暴露在 Storage 中
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
import { secureGet, secureSet, secureRemove } from '@/utils/secure-storage';
|
||||
|
||||
const BASE_URL = process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1';
|
||||
|
||||
@@ -10,7 +11,7 @@ interface ApiResponse<T> {
|
||||
|
||||
async function getHeaders(): Promise<Record<string, string>> {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
const token = Taro.getStorageSync('access_token');
|
||||
const token = secureGet('access_token');
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
const patientId = Taro.getStorageSync('current_patient_id');
|
||||
if (patientId) headers['X-Patient-Id'] = patientId;
|
||||
@@ -20,7 +21,7 @@ async function getHeaders(): Promise<Record<string, string>> {
|
||||
}
|
||||
|
||||
async function tryRefreshToken(): Promise<boolean> {
|
||||
const refreshToken = Taro.getStorageSync('refresh_token');
|
||||
const refreshToken = secureGet('refresh_token');
|
||||
if (!refreshToken) return false;
|
||||
try {
|
||||
const res = await Taro.request({
|
||||
@@ -29,15 +30,15 @@ async function tryRefreshToken(): Promise<boolean> {
|
||||
data: { refresh_token: refreshToken },
|
||||
});
|
||||
if (res.statusCode === 200 && res.data?.success) {
|
||||
Taro.setStorageSync('access_token', res.data.data.access_token);
|
||||
Taro.setStorageSync('refresh_token', res.data.data.refresh_token);
|
||||
secureSet('access_token', res.data.data.access_token);
|
||||
secureSet('refresh_token', res.data.data.refresh_token);
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[tryRefreshToken] token 刷新失败:', err);
|
||||
}
|
||||
Taro.removeStorageSync('access_token');
|
||||
Taro.removeStorageSync('refresh_token');
|
||||
secureRemove('access_token');
|
||||
secureRemove('refresh_token');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { create } from 'zustand';
|
||||
import Taro from '@tarojs/taro';
|
||||
import * as authApi from '@/services/auth';
|
||||
import { secureGet, secureSet, secureRemove } from '@/utils/secure-storage';
|
||||
|
||||
interface BindPhoneResp {
|
||||
access_token: string;
|
||||
@@ -33,8 +34,8 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
loading: false,
|
||||
|
||||
restore: () => {
|
||||
const token = Taro.getStorageSync('access_token') || null;
|
||||
const refreshToken = Taro.getStorageSync('refresh_token') || null;
|
||||
const token = secureGet('access_token') || null;
|
||||
const refreshToken = secureGet('refresh_token') || null;
|
||||
const user = Taro.getStorageSync('user') || null;
|
||||
const currentPatient = Taro.getStorageSync('current_patient') || null;
|
||||
set({ token, refreshToken, user, currentPatient });
|
||||
@@ -46,8 +47,8 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
const resp = await authApi.wechatLogin(code);
|
||||
if (resp.bound && resp.token) {
|
||||
const { access_token, refresh_token, user } = resp.token;
|
||||
Taro.setStorageSync('access_token', access_token);
|
||||
Taro.setStorageSync('refresh_token', refresh_token);
|
||||
secureSet('access_token', access_token);
|
||||
secureSet('refresh_token', refresh_token);
|
||||
Taro.setStorageSync('user', user);
|
||||
Taro.setStorageSync('tenant_id', user.tenant_id || '');
|
||||
set({ token: access_token, refreshToken: refresh_token, user, loading: false });
|
||||
@@ -73,8 +74,8 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
}
|
||||
const resp = await authApi.wechatBindPhone(openid, encryptedData, iv) as BindPhoneResp;
|
||||
const { access_token, refresh_token, user } = resp;
|
||||
Taro.setStorageSync('access_token', access_token);
|
||||
Taro.setStorageSync('refresh_token', refresh_token);
|
||||
secureSet('access_token', access_token);
|
||||
secureSet('refresh_token', refresh_token);
|
||||
Taro.setStorageSync('user', user);
|
||||
Taro.setStorageSync('tenant_id', user.tenant_id || '');
|
||||
Taro.removeStorageSync('wechat_openid');
|
||||
@@ -105,8 +106,8 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
Taro.removeStorageSync('access_token');
|
||||
Taro.removeStorageSync('refresh_token');
|
||||
secureRemove('access_token');
|
||||
secureRemove('refresh_token');
|
||||
Taro.removeStorageSync('user');
|
||||
Taro.removeStorageSync('current_patient');
|
||||
Taro.removeStorageSync('current_patient_id');
|
||||
|
||||
42
apps/miniprogram/src/utils/secure-storage.ts
Normal file
42
apps/miniprogram/src/utils/secure-storage.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
const XOR_KEY = 'hms_mp_2026_secure_key';
|
||||
|
||||
function xorTransform(value: string): string {
|
||||
let result = '';
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
result += String.fromCharCode(value.charCodeAt(i) ^ XOR_KEY.charCodeAt(i % XOR_KEY.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function toBase64(str: string): string {
|
||||
return btoa(unescape(encodeURIComponent(str)));
|
||||
}
|
||||
|
||||
function fromBase64(b64: string): string {
|
||||
try {
|
||||
return decodeURIComponent(escape(atob(b64)));
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function secureSet(key: string, value: string): void {
|
||||
const obfuscated = toBase64(xorTransform(value));
|
||||
Taro.setStorageSync(key, obfuscated);
|
||||
}
|
||||
|
||||
export function secureGet(key: string): string {
|
||||
const raw = Taro.getStorageSync(key);
|
||||
if (!raw || typeof raw !== 'string') return '';
|
||||
try {
|
||||
return xorTransform(fromBase64(raw));
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function secureRemove(key: string): void {
|
||||
Taro.removeStorageSync(key);
|
||||
}
|
||||
Reference in New Issue
Block a user