IPhone: NSHTTPCookie не сохраняется через перезагрузки приложений

В моем приложении iPhone я хочу иметь возможность повторно использовать тот же сеанс на стороне сервера, когда мое приложение перезагрузится. Сеанс на сервере идентифицируется cookie, который отправляется по каждому запросу. Когда я перезапущу приложение, этот файл cookie исчез, и я больше не могу использовать тот же сеанс.

Что я заметил, когда я использовал NSHTTPCookieStorage для поиска cookie, полученного с сервера, является то, что [cookie isSessionOnly] возвращает YES. У меня создается впечатление, что именно поэтому куки файлы не сохраняются в перезагрузках моего приложения. Что мне нужно сделать, чтобы сделать мой cookie НЕ сеансом? Какие заголовки HTTP мне нужно отправить с сервера?

Ответ 1

Вы можете сохранить файл cookie, сохранив его словарь свойств, а затем восстановив его в качестве нового файла cookie перед тем, как перейти к повторному подключению.

Сохранить

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];
    }
 }

Load:

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];
}

Ответ 2

У меня есть upvoted @TomIrving ответ и я здесь, потому что многие пользователи не увидят очень важный комментарий, в котором он говорит:

"Вам нужно установить дату истечения срока действия, иначе cookie будет считаться сессией".

В принципе, файл cookie будет удален при закрытии приложения, если в будущем cookie имеет дату истечения срока действия.

Вам не нужно хранить и восстанавливать файлы cookie в NSUserDefaults и NSUserDefaults, если у вас есть контроль над сервером, и можете попросить его указать заголовок "Expires" на что-то в будущем. Если у вас нет контроля над сервером или вы не хотите переопределять поведение вашего сервера, вы можете "обмануть" свое приложение, изменив expiresDate внутри него:

  • Получить файл cookie, который вы хотите изменить, [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]
  • Скопируйте его свойства в новый NSMutableDictionary, изменив значение "Expires" на дату в будущем.
  • Создайте новый файл cookie из нового NSMutableDictionary, используя: [NSHTTPCookie.cookieWithProperties:]
  • Сохраните созданный файл cookie с помощью [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie newCookie]

При повторном открытии приложения вы заметите, что файл cookie не был удален.

Ответ 3

Куки файлы только для сеанса истекают по своей природе. Вы можете сохранить их вручную в Keychain, если вы действительно этого хотите. Я предпочитаю Keychain сохранять в UserDefaults или архивировать, потому что файлы cookie лучше защищаются, как и пароль пользователя.

К сожалению, сохранение 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)
            }
        }
    }
}

Ответ 4

Я верю, что сервер до того, чтобы решить, является ли cookie только сеансом, вы ничего не можете с этим поделать.

Ответ 5

Быстрый способ

Store:

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)
            }
        }
    }
}

Ответ 6

SWIFT 3

SAVE:

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
                }
            }
        }

GET

let cookie: HTTPCookie = NSKeyedUnarchiver.unarchiveObject(with: UserDefaults.standard.object(forKey: Constants.SHARED_DEFAULT.COOKIE_SMS) as! Data) as! HTTPCookie
        HTTPCookieStorage.shared.setCookie(cookie)

Ответ 7

Версия 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)
}