hushicai / hushicai.github.io

Blog
https://hushicai.github.io
27 stars 1 forks source link

React Native持久化sessions #9

Open hushicai opened 5 years ago

hushicai commented 5 years ago

最近我们遇上了一些比较棘手的问题:

  1. 用户强制杀掉app后,再次打开app,应用大概率会退出到登录界面。
  2. 用户在app中频繁切换账号后,大概停留1分钟后,杀掉app,再次打开app后,登录态是上一个用户的。

这篇文章尝试解释以上问题以及我们如何解决它。

问题:不靠谱的cookie存储

经过一番调试定位后,我们发现,当app重新打开时,app有时候会向服务器发送无效cookie,所以导致了上述问题。

我们一直无法找出为什么无效cookie会被发送的原因,直到我们碰上了这个核心问题:由底层原生框架提供给React Native的cookie存储并非100%可靠

React Native的fetch是在原生网络技术栈上实现的。

Cookies是由这些原生技术栈管理的,而不是React Native本身[Github][StackOverflow]。

1_6rqbmdflnnnwwx52ajr2dg

(React Native takes advantage of native functionality on iOS and Android)

我们发现有两个可能引起上述bug的原因:

可能原因1:原生API存储了错误的cookie

我们查看了一下Apple和Google给它们的原生网络技术栈定义的cookie接收文档:

IOS(NSURLConnection):

The URL loading system automatically sends any stored cookies appropriate for an NSURLRequest object unless the request specifies not to send cookies. Likewise, cookies returned in an NSURLResponse object are accepted in accordance with the current cookie acceptance policy.

Android(CookieManager):

Cookies are manipulated according to RFC2109.

既然React Native是在这些APIs的基础上实现的,那么在应用层级上手动设置的cookie就有可能会被原生API修改,并且被替换成原生的cookie(IOS cookies,Android CookieStore)。

因为cookie是透明处理的,所以如果要进一步调试这个设想,需要编写原生代码,我们决定现在先避免它。

可能原因2:Cookies没有被恰当地持久化

默认地,为了性能原因,cookies首先存储在RAM中,然后定时地同步到永久存储中(Android CookieSyncManager,IOS NSHTTPCookieStorage)。

在iOS [StackOverflow]和Android [StackOverflow]上,cookie都可能永远不会最终成为持久存储(例如应用程序在打开后很快关闭)。

StackOverflow上建议,对于Android,强制cookie重新同步;对于iOS,扩展原生API以手动管理cookie。

我们仍然不确定可能导致cookie持久化失败的特定条件,但此时看起来构建一个变通方法可能比在原生层面上修复更快。

解决方案:手动管理cookie

既然我们无法依靠默认功能来实现可靠的cookie存储,因此我们决定自己动手处理并自行管理会话cookie。

我们选择使用AsyncStorage来存储我们的cookie,并在每个请求的http头部中设置该cookie。 这样,即使应用程序已关闭或更新,我们也可以确保持久存储正确的cookie。 我们还使用了名为react-native-cookies的第三方库,在每个请求之前,刷新原生cookie管理器存储的cookie,以便与请求一起发送正确的cookie(我们自己从AsyncStorage中检索到的cookie)。

以下是我们的代码片段,用于演示此过程的工作原理:

let CookieManager = require('react-native-cookies');
let storage = require('./storage');

function makeRequest(path, options) {
  return storage.getCookie().then(cookie => {
    // Clearing all cookies stored by React Native
    CookieManager.clearAll();
    let fetchParams = {
      ...options,
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        cookie: cookie
      },
      credentials: 'omit'
    };
    return fetch(path, fetchParams)
      .then(response => {
        storage.setCookie(response.headers.get('set-cookie'));
        return response;
      })
      .then(data => data.json());
  });
}

这样修改之后,我们解决了上面的问题,我们的用户现在可以很愉快地在我们的app中保持登录态。 如果你也遇到了同样的问题,我们希望这篇文章可以帮到你。

安全性

如果你觉得AsyncStorage不够安全,你可以参考react-native-sensitive-info的keystore分支,它基于IOS Keychain和Android keyStore提供了加密的本地存储。

参考文章

https://build.affinity.co/persisting-sessions-with-react-native-4c46af3bfd83

kanglang commented 3 years ago

let storage = require('./storage'); 请问大神这里的storage 是 AsyncStorage吗,storage.getCookie()中这个getCooKie()方法从哪里来的?是自己定义的还是AsyncStorage的? 求解答

hushicai commented 3 years ago

let storage = require('./storage'); 请问大神这里的storage 是 AsyncStorage吗,storage.getCookie()中这个getCooKie()方法从哪里来的?是自己定义的还是AsyncStorage的? 求解答

嗯,就是AsyncStorage,一个简单的key/value存储就可以了。核心就是手动在所有请求的headers带上之前的cookie。