如何修复CryptoJS本地存储解密时的TypeError: Cannot read 'words'属性错误?
问题根源分析
刷新页面时触发TypeError: Cannot read properties of undefined (reading 'words'),本质是循环依赖导致解密时密钥未初始化:
- 页面刷新后,redux通过
localStorageSync的rehydrate功能尝试从本地存储恢复加密的auth状态,此时需要调用SECURE_DATA.decrypt方法。 - 但你的
key变量是在syncCondition中赋值的,而syncCondition要么在rehydrate完成后才执行,要么执行时的state是redux初始空状态(state.auth.token为undefined),导致解密时key为undefined,传入CryptoJS的AES解密方法后触发底层错误。
修复方案
方案一:分离密钥与加密数据,提前初始化密钥
将认证token(即解密密钥)存储在非加密的位置(比如cookie),在页面加载时直接读取初始化key,避免依赖redux state:
- 新增获取密钥的工具函数:
// 从cookie读取auth token(假设你的token存在名为authToken的cookie中) const getAuthToken = (): string => { const cookieEntry = document.cookie.split('; ').find(row => row.startsWith('authToken=')); return cookieEntry ? cookieEntry.split('=')[1] : ''; };
- 修改
SECURE_DATA加入异常处理,避免崩溃:
const SECURE_DATA = { encrypt: (state: string) => { if (!key) throw new Error('Encryption key not initialized'); return CryptoJs.AES.encrypt(state, key).toString(); }, decrypt: (state: string) => { if (!key) return ''; try { return CryptoJs.AES.decrypt(state, key).toString(CryptoJs.enc.Utf8); } catch (err) { console.error('Decryption failed:', err); localStorage.removeItem('auth'); // 清除损坏的存储数据 return ''; } }, };
- 调整
localStorageSyncReducer,提前初始化key:
export function localStorageSyncReducer( reducer: ActionReducer<StoreState> ): ActionReducer<StoreState> { // 页面加载时立即初始化密钥 key = getAuthToken(); return localStorageSync({ keys: STORE_KEYS_TO_PERSIST, rehydrate: true, syncCondition(state) { // 同步更新密钥为最新的token key = state.auth.token || getAuthToken(); return true; }, })(reducer); }
方案二:手动处理rehydrate,避免自动同步的循环依赖
禁用localStorageSync的自动rehydrate功能,手动读取并解密本地存储数据作为redux初始状态:
- 修改
localStorageSyncReducer,关闭自动rehydrate:
export function localStorageSyncReducer( reducer: ActionReducer<StoreState> ): ActionReducer<StoreState> { return localStorageSync({ keys: STORE_KEYS_TO_PERSIST, rehydrate: false, // 禁用自动恢复 syncCondition(state) { key = state.auth.token; return true; }, })(reducer); }
- 在创建redux store时,手动加载并解密存储数据:
// 加载并解密持久化状态 const loadPersistedState = (): Partial<StoreState> | undefined => { const persistedAuth = localStorage.getItem('auth'); if (!persistedAuth) return undefined; // 从cookie读取密钥(或其他可靠来源) const token = getAuthToken(); if (!token) { localStorage.removeItem('auth'); return undefined; } try { const decryptedAuthStr = CryptoJs.AES.decrypt(persistedAuth, token).toString(CryptoJs.enc.Utf8); return { auth: JSON.parse(decryptedAuthStr) }; } catch (err) { console.error('Failed to load persisted state:', err); localStorage.removeItem('auth'); return undefined; } }; // 创建store时传入手动加载的初始状态 const store = createStore( reducers, loadPersistedState(), // 其他中间件/enhancers );
额外注意事项
- 不要依赖redux state作为解密密钥的唯一来源,因为rehydrate过程中state尚未初始化。
- 加入异常处理可以避免单个存储项损坏导致整个页面崩溃。
- 如果使用cookie存储token,确保cookie设置了合适的
HttpOnly、Secure等安全属性。
内容的提问来源于stack exchange,提问作者vijay s
相关产品推荐
相关产品推荐

