我经常不得不应对在iOS应用程序设计中过期的会话/访问令牌,并且从来没有真正找到我100%舒适的设计,所以我在这里问这里是否有人能想出来比我目前使用的更好的设计。
您有一个使用用户名和密码登录的应用。服务器返回一个访问令牌,该令牌应该用于将来对该用户进行身份验证的请求。在将来的某个时间点(未知时间),服务器将使该令牌到期,并且使用该令牌发送的任何请求都将返回身份验证失败。
由于会话过期而导致失败,应用程序应使用原始凭据重新登录并获取新的访问令牌。然后它可以重试原始请求。
因此,假设您有一个API来获取需要身份验证的新闻文章列表。流程可能是这样的:
现在想象一下,这是从应用程序中的多个位置完成的。
我通常做的是将凭据存储在NSUserDefaults
中(如果我不关心这些凭据的安全性 - 显然更好地使用密钥链)然后在全局管理器对象上有一个方法(单例)当我注意到会话已过期时,使用这些凭据刷新登录。当登录状态发生变化时,此全局管理器会触发通知,以便应用程序的其他部分可以知道何时由于会话过期而应该在失败后重试请求。
好吧,我从来没有喜欢过经理对象的状态机处理。执行请求的每个位置都需要保存一些状态才能知道登录刷新正在进行,并在刷新登录后重试请求。还有一个问题,如果刷新因为密码错误而导致该怎么办(用户可能更改了它) - 你可能不想完全注销并破坏应用程序的所有用户状态,因为你可能只是能够像以前一样要求新密码并继续进行。但是全局管理器并没有真正与UI相关联,因此很难处理要求新登录的UI。
我知道这个问题特别模糊和概念化(我仍然认为可以在StackOverflow上工作吗?)但我真的只想知道其他人如何解决这类问题。只是解释您如何处理会话过期,重试来自整个应用程序的失败请求,并在刷新不起作用时向用户询问新凭据。
我想整个事情的关键是这个问题:
在何处放置由于会话过期而失败的请求的重试逻辑。我看到这些地方是可选的:
refreshLogin
时可能会阻塞)。答案 0 :(得分:5)
不要向死者提出这个问题,但由于没有得到答复,我会给出我的意见。
我为这种情况选择做的是将信用卡存储在钥匙串中(或者在任何地方,真的),然后我将HTTPClient子类化,以检查是否在每次调用之前刷新。通过这种方式,我可以识别需要刷新,执行它,并在一个步骤中重试所有调用,并且如果需要可以在链中发送错误块以处理用户无法相应刷新的任何情况。
这似乎与您(或可能是)试图完成的内容一致。无需通知或任何爵士乐,您可以编写一次并在整个应用程序中重复使用它,通过子类HTTPClient发送您的呼叫。
编辑:请记住,您应该记得允许任何身份验证调用通过!
答案 1 :(得分:2)
您所问的是如何处理会话过期。其他人已经回答了你的问题。但我认为你提出了错误的问题。让我解释一下。
我们想要的是iOS上用户会话的设计模式。我们需要从iOS设备的角度来看待它:
结论:为iOS设计的任何API都不应该使会话令牌过期。它只是在设备上保密。
根据我的经验,关于会话到期的设计模式的问题的答案基本上是: 使用iOS for API时,请勿使用会话过期。
One of the biggest iOS REST API以这种方式这样做,我不得不同意。
答案 2 :(得分:0)
您提到的问题中有两件事似乎是回答它的关键所在:
A)应用程序处于一种状态,如果用户未登录,数据可能会丢失 B)应用程序需要重新登录才能保存数据。
如果这些是约束,你应该相应地解决它们:
答案 3 :(得分:0)
在我看来,放置逻辑的正确位置是在视图控制器级别。
如果我正确地理解了您的问题,那么您有一个Network API来处理服务器调用并将结果(可能来自JSON)返回给视图控制器。
最好的方法应该是创建一个Login View Controller,其中包含用户名/电子邮件和密码字段,与其他应用程序逻辑分开。从服务器接收到OK后,该视图控制器可以被解除,应用程序将按预期流动。
如果您的令牌无效,服务器应该向您的网络API返回错误401。
您可以简单地将该错误封装到NSError
对象中,然后传递给View Controller进行处理。
在应用程序的任何位置,服务器调用都可以返回错误401,因此您向用户道歉并撤回登录视图控制器,以强制创建新令牌。
我希望我能帮忙。
答案 4 :(得分:0)
我的方法与您的'(2)在API请求级别'类似 - 除了将请求封装在对象中之外,封装了API服务器的整个概念。
因此我通常最终使用async方法签名包装每种类型的请求:
(抱歉,它的C#,不是obj-c(我使用的是Xamarin.iOS)但概念是一样的 - Action<T>
等同于obj-c Blocks)
void GetLatestNews(GetLatestRequest request, Action<NewsListingResponse> success, Action<Exception> error)
void Search(SearchRequest request, Action<SearchResponse> success, Action<Exception> error)
我通常只是在静态类上创建这些静态方法(很少会有超过1个相同类型的服务器,因此单个静态类可以正常工作)但我有时会将它们放在单例实例类中,以便我可以传入一个模拟的单元测试实例。
无论如何,现在你的VC客户端代码只是根据需要使用api:
override void ViewDidAppear(bool animated)
{
SomeNewsSiteApi.GetLatestNews( new GetLatestRequest{Count=20},
response =>
{
// Update table view here
},
error =>
{
// Show some error alert
});
}
这样做的好处是这些方法的实现处理使用当前令牌发送请求,如果失败,则获取新令牌,然后使用新令牌重新发送相同的请求,然后最终回调Action<T> success
回调。{{1}}客户端VC代码不知道任何重新获取令牌,它知道并关心的是它正在请求某些数据,并等待响应或错误。
答案 5 :(得分:0)
几个月来面对同样的问题。我的方法'(2)在API请求级别'
如果您有最佳解决方案,请告知我们。 这将真正帮助许多面临这些问题的人。
当会话过期是非常大的解决方案时刷新身份验证令牌。