深入了解HTTPS及在iOS系統中适配HTTPS類型網絡請求(下)
一、引言
上一篇部落格詳細讨論了HTTPS協定的原理,搭建HTTPS測試環境以及證書的相關基礎。本篇部落格将繼續探讨更多在iOS開發中适配HTTPS類型請求的内容。
二、關于NSURLAuthenticationChallenge相關類
我們在實作URLSession的認證協定方法時,會接收到一個NSURLAuthenticationChallenge類型的參數。簡單了解,這個參數就是服務端發起的一個驗證挑戰,用戶端需要根據挑戰的類型提供相應的挑戰憑證。當然,挑戰憑證不一定都是進行HTTPS證書的信任,也可能是需要用戶端提供使用者密碼或者提供雙向驗證時的用戶端證書。當這個挑戰憑證被驗證通過時,請求便可以繼續順利進行。NSURLAuthenticationChallenge類對象中有一個sender代理執行個體,開發者通過這個執行個體來可控采用怎樣的驗證方式。解析如下:
//使用憑證進行驗證
- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
//試圖不提供憑證繼續請求
- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
//取消憑證驗證
- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
//使用預設提供的憑證行為
- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
//拒絕目前提供的受保護控件并且嘗試不提供憑證繼續請求
- (void)rejectProtectionSpaceAndContinueWithChallenge:(NSURLAuthenticationChallenge *)challenge;
可以看到,上面的協定方法中如果要進行憑證的驗證,需要用戶端提供一個憑證對象NSURLCredential。這個類可以簡單了解為用戶端建立的憑證資訊,解析如下:
//通過使用者名和密碼進行憑證的建立
- (instancetype)initWithUser:(NSString *)user password:(NSString *)password persistence:(NSURLCredentialPersistence)persistence;
//同上
+ (NSURLCredential *)credentialWithUser:(NSString *)user password:(NSString *)password persistence:(NSURLCredentialPersistence)persistence;
//使用者名屬性 隻讀
@property (nullable, readonly, copy) NSString *user;
//密碼屬性 隻讀
@property (nullable, readonly, copy) NSString *password;
//是否有密碼 隻讀
@property (readonly) BOOL hasPassword;
//通過用戶端提供證書進行雙向驗證
- (instancetype)initWithIdentity:(SecIdentityRef)identity certificates:(nullable NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence NS_AVAILABLE(10_6, 3_0);
//同上
+ (NSURLCredential *)credentialWithIdentity:(SecIdentityRef)identity certificates:(nullable NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence NS_AVAILABLE(10_6, 3_0);
//建立證書信任憑證 使用者自簽名的HTTPS請求
- (instancetype)initWithTrust:(SecTrustRef)trust NS_AVAILABLE(10_6, 3_0);
//同上
+ (NSURLCredential *)credentialForTrust:(SecTrustRef)trust NS_AVAILABLE(10_6, 3_0);
上面方法中的NSURLCredentialPersistence枚舉用來設定憑證的存儲方式,解析如下:
typedef NS_ENUM(NSUInteger, NSURLCredentialPersistence) {
NSURLCredentialPersistenceNone, //不儲存
NSURLCredentialPersistenceForSession, //在本URLSession中有效
NSURLCredentialPersistencePermanent, //儲存在鑰匙串中 ,永久有效
NSURLCredentialPersistenceSynchronizable NS_ENUM_AVAILABLE(10_8, 6_0) //永久有效 并且被所有APPID裝置共享
};
三、使用AFNetworking進行自簽名證書HTTPS請求的認證
-(void)afHttps{
NSURLRequest * req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://localhost:8080/users"]];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// securityPolicy.allowInvalidCertificates = YES;//是否允許使用自簽名證書
securityPolicy.validatesDomainName = NO;//是否需要驗證域名,預設YES
AFHTTPSessionManager *_manager = [AFHTTPSessionManager manager];
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
_manager.securityPolicy = securityPolicy;
//設定逾時
[_manager.requestSerializer willChangeValueForKey:@"timeoutinterval"];
_manager.requestSerializer.timeoutInterval = 20.f;
[_manager.requestSerializer didChangeValueForKey:@"timeoutinterval"];
_manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringCacheData;
_manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/xml",@"text/html",@"text/plain",@"application/json",nil];
[_manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
/**
* 導入多張CA憑證
*/
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cert" ofType:@"der"];//自簽名證書
NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
NSArray *cerArray = @[caCert];
_manager.securityPolicy.pinnedCertificates = cerArray;
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
NSArray *caArray = @[(__bridge id)(caRef)];
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
SecTrustSetAnchorCertificatesOnly(serverTrust,NO);
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__autoreleasing NSURLCredential *credential = nil;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([_manager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
return disposition;
}];
[[_manager dataTaskWithRequest:req completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
NSLog(@"%@,%@",[[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding],error);
}]resume];
}