iPhone: NSHTTPCookie не сохраняется при перезапуске приложения
В моем приложении для iPhone я хочу иметь возможность повторно использовать тот же сеанс на стороне сервера, когда мое приложение перезапускается. Сеанс на сервере идентифицируется файлом cookie, который отправляется при каждом запросе. Когда я перезапускаю приложение, этот cookie исчезает, и я больше не могу использовать тот же сеанс.
Что я заметил, когда использовал NSHTTPCookieStorage
посмотреть куки, которые я получил с сервера, [cookie isSessionOnly]
возвращается YES
, У меня сложилось впечатление, что именно поэтому куки не сохраняются при перезапуске моего приложения. Что мне нужно сделать, чтобы мой файл cookie НЕ был только сеансом? Какие HTTP-заголовки мне нужно отправлять с сервера?
6 ответов
Вы можете сохранить куки-файл, сохранив его словарь свойств, а затем восстановив его как новый куки-файл, прежде чем снова подключиться.
Сохранить:
NSArray* allCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:URL]];
for (NSHTTPCookie *cookie in allCookies) {
if ([cookie.name isEqualToString:MY_COOKIE]) {
NSMutableDictionary* cookieDictionary = [NSMutableDictionary dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey:PREF_KEY]];
[cookieDictionary setValue:cookie.properties forKey:URL];
[[NSUserDefaults standardUserDefaults] setObject:cookieDictionary forKey:PREF_KEY];
}
}
Нагрузка:
NSDictionary* cookieDictionary = [[NSUserDefaults standardUserDefaults] dictionaryForKey:PREF_KEY];
NSDictionary* cookieProperties = [cookieDictionary valueForKey:URL];
if (cookieProperties != nil) {
NSHTTPCookie* cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
NSArray* cookieArray = [NSArray arrayWithObject:cookie];
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookieArray forURL:[NSURL URLWithString:URL] mainDocumentURL:nil];
}
Я проголосовал за ответ @TomIrving и уточняю здесь, потому что многие пользователи не увидят очень важный комментарий, в котором он говорит:
"Вам нужно установить дату истечения срока действия, в противном случае предполагается, что файл cookie является только сеансом".
Как правило, файл cookie будет удален, когда вы закроете свое приложение, ЕСЛИ файл cookie не имеет срока годности в будущем.
Вам не нужно хранить и восстанавливать куки в и из NSUserDefaults
если у вас есть контроль над сервером, и вы можете попросить его установить заголовок "Expires" в будущем. Если у вас нет контроля над сервером или вы не хотите переопределять поведение сервера, вы можете "обмануть" свое приложение, изменив expiresDate
изнутри:
- Получите файл cookie, который вы хотите изменить
[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]
- Скопируйте его свойства в новый
NSMutableDictionary
, меняя"Expires"
значение на дату в будущем. - Создать новый файл cookie из нового
NSMutableDictionary
с помощью:[NSHTTPCookie.cookieWithProperties:]
- Сохраните только что созданный файл cookie, используя
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie newCookie]
Когда вы снова откроете свое приложение, вы заметите, что cookie не был удален.
Сеансовые файлы cookie истекают по своей природе. Вы можете хранить их вручную в связке ключей, если вы действительно этого хотите. Я предпочитаю Keychain для сохранения в UserDefaults или архивирования, потому что куки лучше защитить, как пароль пользователя.
К сожалению, сохранение файлов cookie только для сессии не очень полезно, приведенный ниже код является лишь иллюстрацией того, как хранить файлы cookie, но не может заставить сервер принимать такие файлы cookie любым способом (если вы не можете управлять сервером).
Swift 2.2
// Saving into Keychain
if let cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies {
let cookiesData: NSData = NSKeyedArchiver.archivedDataWithRootObject(cookies)
let userAccount = "some unique string to identify the item in Keychain, in my case I use username"
let domain = "some other string you can use in combination with userAccount to identify the item"
let keychainQuery: [NSString: NSObject] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: userAccount + "cookies",
kSecAttrService: domain,
kSecValueData: cookiesData]
SecItemDelete(keychainQuery as CFDictionaryRef) //Trying to delete the item from Keychaing just in case it already exists there
let status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)
if (status == errSecSuccess) {
print("Cookies succesfully saved into Keychain")
}
}
// Getting from Keychain
let userAccount = "some unique string to identify the item in Keychain, in my case I use username"
let domain = "some other string you can use in combination with userAccount to identify the item"
let keychainQueryForCookies: [NSString: NSObject] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: domain, // we use JIRA URL as service string for Keychain
kSecAttrAccount: userAccount + "cookies",
kSecReturnData: kCFBooleanTrue,
kSecMatchLimit: kSecMatchLimitOne]
var rawResultForCookies: AnyObject?
let status: OSStatus = SecItemCopyMatching(keychainQueryForCookies, &rawResultForCookies)
if (status == errSecSuccess) {
let retrievedData = rawResultForCookies as? NSData
if let unwrappedData = retrievedData {
if let cookies = NSKeyedUnarchiver.unarchiveObjectWithData(unwrappedData) as? [NSHTTPCookie] {
for aCookie in cookies {
NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookie(aCookie)
}
}
}
}
Я полагаю, что сервер должен решить, является ли cookie только для сессии, вы ничего не можете с этим поделать.
Быстрый путь
Хранить:
static func storeCookies() {
let cookiesStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
let userDefaults = NSUserDefaults.standardUserDefaults()
let serverBaseUrl = "http://yourserverurl.com"
var cookieDict = [String : AnyObject]()
for cookie in cookiesStorage.cookiesForURL(NSURL(string: serverBaseUrl)!)! {
cookieDict[cookie.name] = cookie.properties
}
userDefaults.setObject(cookieDict, forKey: cookiesKey)
}
Восстановить:
static func restoreCookies() {
let cookiesStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
let userDefaults = NSUserDefaults.standardUserDefaults()
if let cookieDictionary = userDefaults.dictionaryForKey(cookiesKey) {
for (cookieName, cookieProperties) in cookieDictionary {
if let cookie = NSHTTPCookie(properties: cookieProperties as! [String : AnyObject] ) {
cookiesStorage.setCookie(cookie)
}
}
}
}
Swift 5 версия
func storeCookies() {
guard let serverBaseUrl = URL(string: Constants.baseURL) else {
return
}
let cookiesStorage: HTTPCookieStorage = .shared
var cookieDict: [String: Any] = [:]
cookiesStorage.cookies(for: serverBaseUrl)?.forEach({ cookieDict[$0.name] = $0.properties })
let userDefaults = UserDefaults.standard
userDefaults.set(cookieDict, forKey: Constants.cookiesKey)
}
func restoreCookies() {
let cookiesStorage: HTTPCookieStorage = .shared
let userDefaults = UserDefaults.standard
guard let cookieDictionary = userDefaults.dictionary(forKey: Constants.cookiesKey) else {
return
}
let cookies = cookieDictionary
.compactMap({ $0.value as? [HTTPCookiePropertyKey: Any] })
.compactMap({ HTTPCookie(properties: $0) })
cookiesStorage.setCookies(cookies, for: URL(string: Constants.baseURL), mainDocumentURL: nil)
}
SWIFT 3
СПАСТИ:
if let httpResponse = response as? HTTPURLResponse, let fields = httpResponse.allHeaderFields as? [String : String] {
let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields, for: response.url!)
HTTPCookieStorage.shared.setCookies(cookies, for: response.url!, mainDocumentURL: nil)
for cookie in cookies {
if cookie.name == cookieName{
if cookieName == Constants.WS.COOKIES.COOKIE_SMS {
UserDefaults.standard.set(NSKeyedArchiver.archivedData(withRootObject: cookie), forKey: Constants.SHARED_DEFAULT.COOKIE_SMS)
UserDefaults.standard.synchronize()
}
return cookie.value
}
}
}
ПОЛУЧИТЬ:
let cookie: HTTPCookie = NSKeyedUnarchiver.unarchiveObject(with: UserDefaults.standard.object(forKey: Constants.SHARED_DEFAULT.COOKIE_SMS) as! Data) as! HTTPCookie
HTTPCookieStorage.shared.setCookie(cookie)