自定义 URI 方案 "Unsupported URL"

Custom URI Scheme "Unsupported URL"

下面是我的 info.plist 并且我已经注册了自定义应用程序查询方案 URI..

当我在模拟器或设备上使用 redirect_uri=myapplication://oauthcallback 执行 OAuth 回调时,我得到:

Task <CC539C38-4191-48BB-B126-E41BCE28151B>.<6> load failed with 
error Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" 
UserInfo={NSLocalizedDescription=unsupported URL, 
NSErrorFailingURLStringKey=myapplication://oauthcallback?code=rGudk3a7c7&state=state-F7AF0906-984F-47C3-841B-9A55246C3784, 
NSErrorFailingURLKey=myapplication://oauthcallback?code=rGudk3a7c7&state=state-F7AF0906-984F-47C3-841B-9A55246C3784, 
_NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <CC539C38-4191-48BB-B126-E41BCE28151B>.<6>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <CC539C38-4191-48BB-B126-E41BCE28151B>.<6>, 
NSUnderlyingError=0x60000253e8e0 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "(null)"}} [-1002]

有什么想法吗? Info.plist 下面:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0.0</string>
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLName</key>
            <string></string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>myapplication</string>
            </array>
        </dict>
    </array>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSApplicationQueriesSchemes</key>
    <array>
        <string>tel</string>
        <string>myapplication</string>
    </array>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
    </array>
</dict>
</plist>

我明白了。事实证明,在 iOS 上执行 OAuth2 时,RedirectURI 不起作用!它总是 returns 不受支持 URL.. 所以你要做的是创建一个自定义 URL 协议并在其中处理你的 redirectURI..

//
//  URLHandler.swift
//  XIO
//
//  Created by Brandon Anthony on 2018-10-01.
//  Copyright © 2018 SO. All rights reserved.
//

import Foundation

class URLHandler: URLProtocol {

  private static let requestIdentifier = "com.xio.uri.handled"

  override class func canInit(with request: URLRequest) -> Bool {
    guard request.url?.scheme == "myscheme" else {
      return false
    }

    guard let handled = URLProtocol.property(forKey: URLHandler.requestIdentifier, in: request) as? Bool else {
      return true
    }

    return !handled
  }

  override func startLoading() {
    guard let request = (self.request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else {
      return
    }

    URLProtocol.setProperty(true, forKey: URLHandler.requestIdentifier, in: request)

    DispatchQueue.global(qos: .background).async {
      guard let url = request.url, let headers = request.allHTTPHeaderFields else {
        self.client?.urlProtocol(self, didFailWithError: RuntimeError("URLHandler - Invalid URL."))
        return
      }

      guard let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: headers) else {
        self.client?.urlProtocol(self, didFailWithError: RuntimeError("URLHandler - Invalid Response."))
        return
      }


      let json: [String: Any] = ["key": "value"]

      do {
        let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
        self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
        self.client?.urlProtocol(self, didLoad: data as Data)
        self.client?.urlProtocolDidFinishLoading(self)
      } catch {
        self.client?.urlProtocol(self, didFailWithError: error)
      }
    }
  }

  override func stopLoading() {

  }

  override class func canonicalRequest(for request: URLRequest) -> URLRequest {
    return request
  }

  override class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool {
    return super.requestIsCacheEquivalent(a, to: b)
  }
}

或者,您可以等待调用失败,然后从 NSError 对象中解析响应。另一个解决方案是启动请求,然后自己处理响应:

//
//  URLHandler.swift
//  XIO
//
//  Created by Brandon Anthony on 2018-10-01.
//  Copyright © 2018 SO. All rights reserved.
//

import Foundation

class URLHandler: URLProtocol, URLSessionDataDelegate {

  private var session: URLSession?
  private var dataTask: URLSessionDataTask?
  private static let requestIdentifier = "com.xio.uri.handled"

  override class func canInit(with request: URLRequest) -> Bool {
    guard request.url?.scheme == "myscheme" else {
      return false
    }

    guard let handled = URLProtocol.property(forKey: URLHandler.requestIdentifier, in: request) as? Bool else {
      return true
    }

    return !handled
  }

  override func startLoading() {
    guard let request = (self.request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else {
      return
    }

    URLProtocol.setProperty(true, forKey: URLHandler.requestIdentifier, in: request)


    var headers = request.allHTTPHeaderFields
    headers?["Accept"] = "application/json"
    headers?["Content-Type"] = "application/json"
    request.allHTTPHeaderFields = headers

    let configuration = URLSessionConfiguration.default
    self.session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
    self.dataTask = self.session?.dataTask(with: request as URLRequest)
    self.dataTask?.resume()
  }

  override func stopLoading() {
    self.dataTask?.cancel()
    self.dataTask = nil

    self.session?.invalidateAndCancel()
    self.session = nil
  }

  override class func canonicalRequest(for request: URLRequest) -> URLRequest {
    return request
  }

  override class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool {
    return super.requestIsCacheEquivalent(a, to: b)
  }

  // MARK: NSURLSessionTaskDelegate

  func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    if let error = error as NSError? {
      if error.code == NSURLErrorUnsupportedURL {

        guard let request = task.currentRequest else {
          self.client?.urlProtocol(self, didFailWithError: error)
          return
        }

        guard let url = request.url, let headers = request.allHTTPHeaderFields else {
          self.client?.urlProtocol(self, didFailWithError: error)
          return
        }

        guard let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: headers) else {
          self.client?.urlProtocol(self, didFailWithError: error)
          return
        }

        OktaTokenManager(Okta.shared).parseCode(url: url) { state, code in
          guard let state = state, let code = code else {
            self.client?.urlProtocol(self, didFailWithError: error)
            return
          }

          let json: [String: Any] = ["key": "value"]
          ]

          do {
            let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
            self.client?.urlProtocol(self, didLoad: data as Data)
            self.client?.urlProtocolDidFinishLoading(self)
          } catch let err {
            print(err)
            self.client?.urlProtocol(self, didFailWithError: error)
          }
        }
        return
      }
    }

    if let response = task.response {
      client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
    }

    if let error = error { //&& error.code != NSURLErrorCancelled {
      self.client?.urlProtocol(self, didFailWithError: error)
    } else {
      self.client?.urlProtocolDidFinishLoading(self) //cacheResponse
    }
  }
}

最后,您可以不执行上述任何操作,当您的请求失败时,解析错误(类似于上面)以获得真正的响应。不过我更喜欢这种方法。尤其是第一种方法发起请求。