İOS 7'de bir uygulama içi makbuzları ve paket makbuzlarını LOCALLY ile doğrulamak için eksiksiz bir çözüm


160

Teoride bir uygulama içi ve / veya paket makbuzunu doğrulayacak bir sürü doküman ve kod okudum.

SSL, sertifikalar, şifreleme vb. Hakkındaki bilgimin neredeyse sıfır olduğu göz önüne alındığında, okuduğum tüm açıklamaların, bu ümit vaat eden gibi , anlaşılması zor buldum.

Onlar açıklamaların eksik olduğunu söylüyorlar, çünkü her insan bunu nasıl yapacağını bulmalı ya da bilgisayar korsanları kalıpları tanıyabilen ve tanımlayabilen ve uygulamayı yayabilen bir kraker uygulaması oluşturmak için kolay bir işe sahip olacaklar. Tamam, bunu bir noktaya kadar kabul ediyorum. Ben tamamen nasıl yapılacağını açıklayabilir ve "bu yöntemi değiştirmek", "bu diğer yöntemi değiştirmek", "bu değişkeni gizlemek", "bu ve bu", vb adını değiştirmek ", vb.

Bazı iyi ruhlar , beş yaşımdayken (tamam, 3 yapın), iOS 7'de LOCALLY makbuzlarını ve uygulama içi satın alma makbuzlarını nasıl açık bir şekilde doğrulayacağımızı açıklayacak kadar nazik olabilir mi?

Teşekkürler!!!


Uygulamalarınız üzerinde çalışan bir sürümünüz varsa ve endişeleriniz, bilgisayar korsanlarının bunu nasıl yaptığınızı görecekse, burada yayınlamadan önce hassas yöntemlerinizi değiştirmeniz yeterlidir. Dizeleri gizleyin, satırların sırasını değiştirin, döngü yapma şeklinizi değiştirin (numaralandırmayı engellemek için kullanmaktan tersi) ve bunun gibi şeyler. Açıkçası, burada yazılabilecek kodu kullanan herkesin aynı şeyi yapması gerekir, kolayca saldırıya uğrama riski yoktur.


1
Adil uyarı: yerel olarak yapmak, bu işlevi uygulamanızdan çıkarmayı çok daha kolay hale getirir.
NinjaLikesCheez

2
Tamam, biliyorum, ama burada önemli olan şeyleri zorlaştırmak ve otomatik çatlamayı / yamayı önlemek. Soru, bir bilgisayar korsanı gerçekten uygulamanızı kırmak istiyorsa, yerel veya uzak, hangi yöntemi kullanırsanız kullanın. Fikir, otomatik yamalamayı tekrar önlemek için serbest bıraktığınız her yeni sürümü biraz değiştirmektir.
Ördek

4
@NinjaLikesCheez - doğrulama bir sunucuda yapılsa bile çek NOP yapılabilir.
Ördek

14
üzgünüm, ama bu bir mazeret değil. Yazarın yapması gereken tek şey, KODU OLDUĞU GİBİ KULLANMAYIN. Herhangi bir örnek olmadan, roket bilimcisi olmadan bunu anlamak imkansızdır.
Ördek

3
DRM'yi uygulamak istemiyorsanız, yerel doğrulama ile uğraşmayın. Sadece makbuzunuzu uygulamanızdan doğrudan Apple'a GÖNDERİN ve kolayca ayrıştırılmış bir JSON biçiminde size tekrar gönderirler. Korsanların bunu kırması önemsizdir, ancak freemium'a geçiyorsanız ve korsanlıkla ilgilenmiyorsanız, sadece birkaç satır kolay kod.
Dan Fabulich

Yanıtlar:


146

İşte bunu uygulama içi satın alma kitaplığım RMStore'da nasıl çözdüğümün bir yolu . Tüm makbuzun doğrulanmasını içeren bir işlemin nasıl doğrulanacağını açıklayacağım.

Bir bakışta

Makbuzu alın ve işlemi doğrulayın. Başarısız olursa, makbuzu yenileyin ve tekrar deneyin. Bu, makbuzu yenilemek eşzamansız olduğundan doğrulama işlemini eşzamansız hale getirir.

Gönderen RMStoreAppReceiptVerifier :

RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
const BOOL verified = [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:nil]; // failureBlock is nil intentionally. See below.
if (verified) return;

// Apple recommends to refresh the receipt if validation fails on iOS
[[RMStore defaultStore] refreshReceiptOnSuccess:^{
    RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
    [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:failureBlock];
} failure:^(NSError *error) {
    [self failWithBlock:failureBlock error:error];
}];

Makbuz verilerinin alınması

Makbuz içinde [[NSBundle mainBundle] appStoreReceiptURL]ve aslında bir PCKS7 kapsayıcısıdır. Bu kapsayıcıyı açmak için OpenSSL kullandım. Diğerleri görünüşe göre bunu tamamen sistem çerçeveleri ile yapmışlardır .

Projenize OpenSSL eklemek önemsiz değil. RMStore wiki yardımcı olmalıdır.

PKCS7 kapsayıcısını açmak için OpenSSL kullanmayı seçerseniz, kodunuz şöyle görünebilir. Gönderen RMAppReceipt :

+ (NSData*)dataFromPKCS7Path:(NSString*)path
{
    const char *cpath = [[path stringByStandardizingPath] fileSystemRepresentation];
    FILE *fp = fopen(cpath, "rb");
    if (!fp) return nil;

    PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL);
    fclose(fp);

    if (!p7) return nil;

    NSData *data;
    NSURL *certificateURL = [[NSBundle mainBundle] URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"];
    NSData *certificateData = [NSData dataWithContentsOfURL:certificateURL];
    if ([self verifyPKCS7:p7 withCertificateData:certificateData])
    {
        struct pkcs7_st *contents = p7->d.sign->contents;
        if (PKCS7_type_is_data(contents))
        {
            ASN1_OCTET_STRING *octets = contents->d.data;
            data = [NSData dataWithBytes:octets->data length:octets->length];
        }
    }
    PKCS7_free(p7);
    return data;
}

Doğrulamanın ayrıntılarına daha sonra gireceğiz.

Makbuz alanlarını alma

Makbuz ASN1 biçiminde ifade edilir. Genel bilgiler, doğrulama amaçlı bazı alanlar (daha sonra geleceğiz) ve her bir uygulama içi satın alma işlemiyle ilgili belirli bilgiler içerir.

Yine, ASSS1 okuma söz konusu olduğunda OpenSSL kurtarmaya geliyor. Gönderen RMAppReceipt , birkaç yardımcı yöntemler kullanılarak:

NSMutableArray *purchases = [NSMutableArray array];
[RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) {
    const uint8_t *s = data.bytes;
    const NSUInteger length = data.length;
    switch (type)
    {
        case RMAppReceiptASN1TypeBundleIdentifier:
            _bundleIdentifierData = data;
            _bundleIdentifier = RMASN1ReadUTF8String(&s, length);
            break;
        case RMAppReceiptASN1TypeAppVersion:
            _appVersion = RMASN1ReadUTF8String(&s, length);
            break;
        case RMAppReceiptASN1TypeOpaqueValue:
            _opaqueValue = data;
            break;
        case RMAppReceiptASN1TypeHash:
            _hash = data;
            break;
        case RMAppReceiptASN1TypeInAppPurchaseReceipt:
        {
            RMAppReceiptIAP *purchase = [[RMAppReceiptIAP alloc] initWithASN1Data:data];
            [purchases addObject:purchase];
            break;
        }
        case RMAppReceiptASN1TypeOriginalAppVersion:
            _originalAppVersion = RMASN1ReadUTF8String(&s, length);
            break;
        case RMAppReceiptASN1TypeExpirationDate:
        {
            NSString *string = RMASN1ReadIA5SString(&s, length);
            _expirationDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
    }
}];
_inAppPurchases = purchases;

Uygulama içi satın alma işlemlerini alma

Her uygulama içi satın alma işlemi de ASN1'de yapılır. Ayrıştırmak, genel makbuz bilgilerini ayrıştırmaktan çok benzer.

Gönderen RMAppReceipt , aynı yardımcı yöntemler kullanılarak:

[RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) {
    const uint8_t *p = data.bytes;
    const NSUInteger length = data.length;
    switch (type)
    {
        case RMAppReceiptASN1TypeQuantity:
            _quantity = RMASN1ReadInteger(&p, length);
            break;
        case RMAppReceiptASN1TypeProductIdentifier:
            _productIdentifier = RMASN1ReadUTF8String(&p, length);
            break;
        case RMAppReceiptASN1TypeTransactionIdentifier:
            _transactionIdentifier = RMASN1ReadUTF8String(&p, length);
            break;
        case RMAppReceiptASN1TypePurchaseDate:
        {
            NSString *string = RMASN1ReadIA5SString(&p, length);
            _purchaseDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
        case RMAppReceiptASN1TypeOriginalTransactionIdentifier:
            _originalTransactionIdentifier = RMASN1ReadUTF8String(&p, length);
            break;
        case RMAppReceiptASN1TypeOriginalPurchaseDate:
        {
            NSString *string = RMASN1ReadIA5SString(&p, length);
            _originalPurchaseDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
        case RMAppReceiptASN1TypeSubscriptionExpirationDate:
        {
            NSString *string = RMASN1ReadIA5SString(&p, length);
            _subscriptionExpirationDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
        case RMAppReceiptASN1TypeWebOrderLineItemID:
            _webOrderLineItemID = RMASN1ReadInteger(&p, length);
            break;
        case RMAppReceiptASN1TypeCancellationDate:
        {
            NSString *string = RMASN1ReadIA5SString(&p, length);
            _cancellationDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
    }
}]; 

Sarf malzemeleri ve yenilenemeyen abonelikler gibi bazı uygulama içi satın alımların makbuzda yalnızca bir kez görüneceğine dikkat edilmelidir. Bunları satın aldıktan hemen sonra doğrulamanız gerekir (yine RMStore size bu konuda yardımcı olur).

Bir bakışta doğrulama

Şimdi tüm alanları makbuzdan ve tüm uygulama içi satın alımlarından aldık. İlk önce makbuzun kendisini doğrularız ve sonra makbuzun işlemin ürününü içerip içermediğini kontrol ederiz.

Başlangıçta geri çağırdığımız yöntem aşağıdadır. Gönderen RMStoreAppReceiptVerificator :

- (BOOL)verifyTransaction:(SKPaymentTransaction*)transaction
                inReceipt:(RMAppReceipt*)receipt
                           success:(void (^)())successBlock
                           failure:(void (^)(NSError *error))failureBlock
{
    const BOOL receiptVerified = [self verifyAppReceipt:receipt];
    if (!receiptVerified)
    {
        [self failWithBlock:failureBlock message:NSLocalizedString(@"The app receipt failed verification", @"")];
        return NO;
    }
    SKPayment *payment = transaction.payment;
    const BOOL transactionVerified = [receipt containsInAppPurchaseOfProductIdentifier:payment.productIdentifier];
    if (!transactionVerified)
    {
        [self failWithBlock:failureBlock message:NSLocalizedString(@"The app receipt doest not contain the given product", @"")];
        return NO;
    }
    if (successBlock)
    {
        successBlock();
    }
    return YES;
}

Makbuzu doğrulama

Makbuzun kendisinin doğrulanması aşağıdakilerle ilgilidir:

  1. Makbuzun geçerli PKCS7 ve ASN1 olup olmadığını kontrol edin. Bunu dolaylı olarak yaptık.
  2. Makbuzun Apple tarafından imzalandığını doğrulama. Bu makbuz ayrıştırılmadan önce yapıldı ve aşağıda ayrıntılandırılacaktır.
  3. Makbuzdaki paket kimliğinin paket kimliğinize uygun olup olmadığını kontrol edin. Uygulama paketinizi değiştirmek ve başka bir makbuz kullanmak çok zor görünmediğinden, paket tanımlayıcınızı kodlamalısınız.
  4. Makbuzdaki uygulama sürümünün uygulama sürümü tanımlayıcınıza karşılık geldiğini kontrol edin. Yukarıda belirtilen nedenlerle uygulama sürümünü kodlamalısınız.
  5. Makbuzun mevcut cihaza karşılık geldiğinden emin olmak için makbuz karmasını kontrol edin.

RMStoreAppReceiptVerificator'dan yüksek düzeyde kodda 5 adım :

- (BOOL)verifyAppReceipt:(RMAppReceipt*)receipt
{
    // Steps 1 & 2 were done while parsing the receipt
    if (!receipt) return NO;   

    // Step 3
    if (![receipt.bundleIdentifier isEqualToString:self.bundleIdentifier]) return NO;

    // Step 4        
    if (![receipt.appVersion isEqualToString:self.bundleVersion]) return NO;

    // Step 5        
    if (![receipt verifyReceiptHash]) return NO;

    return YES;
}

2. ve 5. adımlara geçelim.

Makbuz imzasını doğrulama

Verileri çıkardığımızda makbuz imzası doğrulamasına baktık. Makbuz, Apple Root Certificate Authority'den indirilebilen Apple Inc. Root Certificate ile imzalanmıştır . Aşağıdaki kod, PKCS7 kapsayıcısını ve kök sertifikayı veri olarak alır ve eşleşip eşleşmediğini denetler:

+ (BOOL)verifyPKCS7:(PKCS7*)container withCertificateData:(NSData*)certificateData
{ // Based on: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW17
    static int verified = 1;
    int result = 0;
    OpenSSL_add_all_digests(); // Required for PKCS7_verify to work
    X509_STORE *store = X509_STORE_new();
    if (store)
    {
        const uint8_t *certificateBytes = (uint8_t *)(certificateData.bytes);
        X509 *certificate = d2i_X509(NULL, &certificateBytes, (long)certificateData.length);
        if (certificate)
        {
            X509_STORE_add_cert(store, certificate);

            BIO *payload = BIO_new(BIO_s_mem());
            result = PKCS7_verify(container, NULL, store, NULL, payload, 0);
            BIO_free(payload);

            X509_free(certificate);
        }
    }
    X509_STORE_free(store);
    EVP_cleanup(); // Balances OpenSSL_add_all_digests (), per http://www.openssl.org/docs/crypto/OpenSSL_add_all_algorithms.html

    return result == verified;
}

Bu, başlangıçta, makbuz ayrıştırılmadan önce yapıldı.

Makbuz karmasını doğrulama

Makbuzdaki karma, cihaz kimliğinin bir SHA1'i, makbuzda bulunan bazı opak değer ve paket kimliğidir.

İOS'ta makbuz karmasını bu şekilde doğrularsınız. Gönderen RMAppReceipt :

- (BOOL)verifyReceiptHash
{
    // TODO: Getting the uuid in Mac is different. See: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5
    NSUUID *uuid = [[UIDevice currentDevice] identifierForVendor];
    unsigned char uuidBytes[16];
    [uuid getUUIDBytes:uuidBytes];

    // Order taken from: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5
    NSMutableData *data = [NSMutableData data];
    [data appendBytes:uuidBytes length:sizeof(uuidBytes)];
    [data appendData:self.opaqueValue];
    [data appendData:self.bundleIdentifierData];

    NSMutableData *expectedHash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH];
    SHA1(data.bytes, data.length, expectedHash.mutableBytes);

    return [expectedHash isEqualToData:self.hash];
}

Ve bu onun özü. Burada veya orada bir şeyler eksik olabilir, bu yüzden daha sonra bu gönderiye geri dönebilirim. Her durumda, daha fazla ayrıntı için kodun tamamına göz atmanızı öneririm.


2
Güvenlik uyarısı: açık kaynak kodunun kullanılması uygulamanızı daha savunmasız hale getirir. Güvenlik önemliyse, RMStore ve yukarıdaki kodu yalnızca rehber olarak kullanmak isteyebilirsiniz.
hpique

6
Gelecekte OpenSSL'den kurtulmanız ve sadece sistem çerçevelerini kullanarak kütüphanenizi kompakt hale getirmeniz harika olurdu.
Ördek

2
@RubberDuck Bkz. Github.com/robotmedia/RMStore/issues/16 . Girin veya katkıda bulunun. :)
hpique

1
@RubberDuck Şimdiye kadar sıfır OpenSSL bilgim vardı. Kim bilir, hoşuna gidebilir. : P
hpique

2
İstek ve / veya yanıtın ele geçirilebildiği ve değiştirilebildiği Orta Saldırıya Karşı Bir Adam'a karşı hassastır. Örneğin, istek bir 3. taraf sunucusuna yönlendirilebilir ve uygulamanın bir ürünün satın alınmadığını düşünmesi için kandırılması ve işlevselliğin ücretsiz olarak etkinleştirilmesi için yanlış bir yanıt döndürülebilir.
Jasarien

13

Burada kimseden bahsetmediğine şaşırdım . Her seferinde farklı bir şekilde, gizli makbuz doğrulama kodunu otomatik olarak üreten bir araçtır; hem GUI hem de komut satırı işlemini destekler. Şiddetle tavsiye edilir.

(Receigen'e bağlı değil, sadece mutlu bir kullanıcı.)

Ben yazarken otomatik olarak Receigen (her sürüm değişikliğinde yapılması gerektiği için) yeniden çalıştırmak için böyle bir Rakefile kullanın rake receigen:

desc "Regenerate App Store Receipt validation code using Receigen app (which must be already installed)"
task :receigen do
  # TODO: modify these to match your app
  bundle_id = 'com.example.YourBundleIdentifierHere'
  output_file = File.join(__dir__, 'SomeDir/ReceiptValidation.h')

  version = PList.get(File.join(__dir__, 'YourProjectFolder/Info.plist'), 'CFBundleVersion')
  command = %Q</Applications/Receigen.app/Contents/MacOS/Receigen --identifier #{bundle_id} --version #{version} --os ios --prefix ReceiptValidation --success callblock --failure callblock>
  puts "#{command} > #{output_file}"
  data = `#{command}`
  File.open(output_file, 'w') { |f| f.write(data) }
end

module PList
  def self.get file_name, key
    if File.read(file_name) =~ %r!<key>#{Regexp.escape(key)}</key>\s*<string>(.*?)</string>!
      $1.strip
    else
      nil
    end
  end
end

1
Receigen ile ilgilenenler için bu, App Store'da 29.99 $ karşılığında ücretli bir çözümdür. Eylül 2014'ten bu yana güncellenmemiş olmasına rağmen.
DevGansta

Doğru, güncelleme eksikliği çok endişe vericidir. Ancak yine de çalışıyor; FWIW, uygulamalarımda kullanıyorum.
Andrey Tarantsov

Cihazlarınızda sızıntı olup olmadığını kontrol edin, Receigen ile onları çok olsun.
Aziz

Receigen en ileri teknolojidir, ancak evet düştüğü için bir utançtır.
Fattie

1
Henüz düşürülmemiş gibi görünüyor. Üç hafta önce güncellendi!
Oleg Korzhukov

2

Not: İstemci tarafında bu tür bir doğrulama yapılması önerilmez

Bu, uygulama içi satın alma makbuzunun doğrulanması için bir Swift 4 sürümüdür ...

Makbuz doğrulamasındaki olası hataları temsil etmek için bir numaralandırma oluşturalım

enum ReceiptValidationError: Error {
    case receiptNotFound
    case jsonResponseIsNotValid(description: String)
    case notBought
    case expired
}

Sonra makbuzu doğrulayan işlevi yaratalım, doğrulayamazsa bir hata atar.

func validateReceipt() throws {
    guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) else {
        throw ReceiptValidationError.receiptNotFound
    }

    let receiptData = try! Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
    let receiptString = receiptData.base64EncodedString()
    let jsonObjectBody = ["receipt-data" : receiptString, "password" : <#String#>]

    #if DEBUG
    let url = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")!
    #else
    let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
    #endif

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody = try! JSONSerialization.data(withJSONObject: jsonObjectBody, options: .prettyPrinted)

    let semaphore = DispatchSemaphore(value: 0)

    var validationError : ReceiptValidationError?

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, let httpResponse = response as? HTTPURLResponse, error == nil, httpResponse.statusCode == 200 else {
            validationError = ReceiptValidationError.jsonResponseIsNotValid(description: error?.localizedDescription ?? "")
            semaphore.signal()
            return
        }
        guard let jsonResponse = (try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [AnyHashable: Any] else {
            validationError = ReceiptValidationError.jsonResponseIsNotValid(description: "Unable to parse json")
            semaphore.signal()
            return
        }
        guard let expirationDate = self.expirationDate(jsonResponse: jsonResponse, forProductId: <#String#>) else {
            validationError = ReceiptValidationError.notBought
            semaphore.signal()
            return
        }

        let currentDate = Date()
        if currentDate > expirationDate {
            validationError = ReceiptValidationError.expired
        }

        semaphore.signal()
    }
    task.resume()

    semaphore.wait()

    if let validationError = validationError {
        throw validationError
    }
}

Belirli bir ürünün son kullanma tarihini almak için bu yardımcı işlevi kullanalım. İşlev bir JSON yanıtı ve bir ürün kimliği alır. JSON yanıtı, farklı ürünler için birden çok makbuz bilgisi içerebilir, bu nedenle belirtilen parametre için son bilgiyi alır.

func expirationDate(jsonResponse: [AnyHashable: Any], forProductId productId :String) -> Date? {
    guard let receiptInfo = (jsonResponse["latest_receipt_info"] as? [[AnyHashable: Any]]) else {
        return nil
    }

    let filteredReceipts = receiptInfo.filter{ return ($0["product_id"] as? String) == productId }

    guard let lastReceipt = filteredReceipts.last else {
        return nil
    }

    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"

    if let expiresString = lastReceipt["expires_date"] as? String {
        return formatter.date(from: expiresString)
    }

    return nil
}

Artık bu işlevi çağırabilir ve olası hata durumlarını işleyebilirsiniz

do {
    try validateReceipt()
    // The receipt is valid 😌
    print("Receipt is valid")
} catch ReceiptValidationError.receiptNotFound {
    // There is no receipt on the device 😱
} catch ReceiptValidationError.jsonResponseIsNotValid(let description) {
    // unable to parse the json 🤯
    print(description)
} catch ReceiptValidationError.notBought {
    // the subscription hasn't being purchased 😒
} catch ReceiptValidationError.expired {
    // the subscription is expired 😵
} catch {
    print("Unexpected error: \(error).")
}

App Store Connect'ten bir Şifre alabilirsiniz . https://developer.apple.combu bağlantıyı aç tıklayın

  • Account tab
  • Do Sign in
  • Open iTune Connect
  • Open My App
  • Open Feature Tab
  • Open In App Purchase
  • Click at the right side on 'View Shared Secret'
  • At the bottom you will get a secrete key

Bu anahtarı kopyalayın ve şifre alanına yapıştırın.

Umarım bu, hızlı versiyonda bunu isteyen herkes için yardımcı olacaktır.


19
Apple doğrulama URL'sini asla cihazınızdan kullanmamalısınız. Yalnızca sunucunuzdan kullanılmalıdır. Bu WWDC oturumlarında belirtilmiştir.
pechar

Kullanıcı uygulamaları silerse veya uzun süre açmazsa ne olur? Son kullanma tarihi hesaplamanız iyi çalışıyor mu?
karthikeyan

O zaman doğrulamayı sunucu tarafında tutmanız gerekir.
Pushpendra

1
@Pechar'ın dediği gibi, bunu asla yapmamalısınız. Lütfen cevabınızın üstüne ekleyin. 36:32 => adresindeki WWDC oturumu bakın developer.apple.com/videos/play/wwdc2016/702
cicerocamargo

Makbuz verilerini doğrudan cihazdan göndermenin neden güvenli olmadığını anlamıyorum. Herkes açıklayabilir mi?
Koh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.