如何在Swift中进行HTTP请求并使用基本身份验证

120

我有一个基于RESTful的服务,并且使用基本认证,我想从iOS+swift中调用它。 我必须在哪里提供凭据才能进行此请求?

我的代码(对不起,我刚开始学习iOS/obj-c/swift):

class APIProxy: NSObject {
    var data: NSMutableData = NSMutableData()
    
    func connectToWebApi() {
        var urlPath = "http://xx.xx.xx.xx/BP3_0_32/ru/hs/testservis/somemethod"
        NSLog("connection string \(urlPath)")
        var url: NSURL = NSURL(string: urlPath)
        var request = NSMutableURLRequest(URL: url)
        let username = "hs"
        let password = "1"
        let loginString = NSString(format: "%@:%@", username, password)
        let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)
        let base64LoginString = loginData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.fromMask(0))
        request.setValue(base64LoginString, forHTTPHeaderField: "Authorization")
        
        var connection: NSURLConnection = NSURLConnection(request: request, delegate: self)
        
        connection.start()
    }
    
    
    //NSURLConnection delegate method
    func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
        println("Failed with error:\(error.localizedDescription)")
    }
    
    //NSURLConnection delegate method
    func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
        //New request so we need to clear the data object
        self.data = NSMutableData()
    }
    
    //NSURLConnection delegate method
    func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
        //Append incoming data
        self.data.appendData(data)
    }
    
    //NSURLConnection delegate method
    func connectionDidFinishLoading(connection: NSURLConnection!) {
        NSLog("connectionDidFinishLoading");
    }
    
}

顺便提一下,NSURLConnection(request: request, delegate: self) 会为您 start 连接。不要自己显式调用 start 方法,否则会导致连接第二次启动。 - Rob
3
NSURLConnection已经被弃用,你应该尽快转换到NSURLSession。 - Sam Soffes
9个回答

205

您可以在 Swift 3 中像这样使用 URLRequest 实例提供凭证:

let username = "user"
let password = "pass"
let loginString = String(format: "%@:%@", username, password)
let loginData = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString()

// create the request
let url = URL(string: "http://www.example.com/")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")

// fire off the request
// make sure your class conforms to NSURLConnectionDelegate
let urlConnection = NSURLConnection(request: request, delegate: self)

或者在 Swift 2 中的 NSMutableURLRequest 中:

// set up the base64-encoded credentials
let username = "user"
let password = "pass"
let loginString = NSString(format: "%@:%@", username, password)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions([])

// create the request
let url = NSURL(string: "http://www.example.com/")
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")

// fire off the request
// make sure your class conforms to NSURLConnectionDelegate
let urlConnection = NSURLConnection(request: request, delegate: self)

1
好的,注意到了!已更新答案。 - Nate Cook
4
在Xcode 6.1中,'NSDataBase64EncodingOptions.Type'没有名为'fromMask'的成员,这是错误信息。请问'mask(0)'是什么意思?请帮忙解决。 - Bala Vishnu
2
我在xCode中看到了与@BalaVishnu相同的消息,但我只是使用了.allZeros。 - Sean Larkin
1
Swift在Xcode 1.1中的选项集语法已更改。您可以使用NSDataBase64EncodingOptions(0)nil表示无选项。答案已更新。 - Nate Cook
@NateCook 你太棒了! - Anurag Sharma
显示剩余4条评论

32

Swift 4:

let username = "username"
let password = "password"
let loginString = "\(username):\(password)"

guard let loginData = loginString.data(using: String.Encoding.utf8) else {
    return
}
let base64LoginString = loginData.base64EncodedString()

request.httpMethod = "GET"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")

1
如果你使用Data(loginString.utf8),就不需要使用守卫语句了。 - robinst

23

//创建基于Base64编码的身份验证字符串

    let PasswordString = "\(txtUserName.text):\(txtPassword.text)"
    let PasswordData = PasswordString.dataUsingEncoding(NSUTF8StringEncoding)
    let base64EncodedCredential = PasswordData!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
    //let base64EncodedCredential = PasswordData!.base64EncodedStringWithOptions(nil)

//创建认证URL

    let urlPath: String = "http://...../auth"
    var url: NSURL = NSURL(string: urlPath)

//创建和初始化基本身份验证请求

    var request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
    request.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
    request.HTTPMethod = "GET"

//你可以使用以下其中一种方法

//1. 使用 NSURLConnectionDataDelegate 进行 URL 请求

    let queue:NSOperationQueue = NSOperationQueue()
    let urlConnection = NSURLConnection(request: request, delegate: self)
    urlConnection.start()

//使用异步请求发送URL请求

    NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
        println(NSString(data: data, encoding: NSUTF8StringEncoding))
    }

//使用异步请求的2个URL请求,输出json

    NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
        var err: NSError
        var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
        println("\(jsonResult)")
    })

//使用同步请求的3个URL请求

    var response: AutoreleasingUnsafePointer<NSURLResponse?>=nil
    var dataVal: NSData =  NSURLConnection.sendSynchronousRequest(request, returningResponse: response, error:nil)
    var err: NSError
    var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(dataVal, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
    println("\(jsonResult)")

//4 使用NSURLSession进行URL请求

    let config = NSURLSessionConfiguration.defaultSessionConfiguration()
    let authString = "Basic \(base64EncodedCredential)"
    config.HTTPAdditionalHeaders = ["Authorization" : authString]
    let session = NSURLSession(configuration: config)

    session.dataTaskWithURL(url) {
        (let data, let response, let error) in
        if let httpResponse = response as? NSHTTPURLResponse {
            let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
            println(dataString)
        }
    }.resume()

// 如果您在服务器请求为GET请求时更改了request.HTTPMethod = "POST",则可能会导致致命错误


2
顺便提一下,这重复了 OP 代码中的错误:NSURLConnection(request: request, delegate: self) 开始请求。你不应该再次 start 它。 - Rob

7

在Swift 2中:

extension NSMutableURLRequest {
    func setAuthorizationHeader(username username: String, password: String) -> Bool {
        guard let data = "\(username):\(password)".dataUsingEncoding(NSUTF8StringEncoding) else { return false }

        let base64 = data.base64EncodedStringWithOptions([])
        setValue("Basic \(base64)", forHTTPHeaderField: "Authorization")
        return true
    }
}

我不确定在将其转换为base64之前是否需要转义任何内容。 - Sam Soffes

4

使用SWIFT 3和APACHE简单身份验证,可以使代码更加简洁明了:

func urlSession(_ session: URLSession, task: URLSessionTask,
                didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    let credential = URLCredential(user: "test",
                                   password: "test",
                                   persistence: .none)

    completionHandler(.useCredential, credential)


}

2

我曾经遇到过类似的问题,想通过POST方式将一些自动化邮件发送到MailGun。最终我通过使用大型HTTP响应来解决了这个问题。我将完整路径放入了Keys.plist文件中,以便上传我的代码到github,并且将某些参数分解为变量,以便在未来的编程中进行程序化设置。

// Email the FBO with desired information
// Parse our Keys.plist so we can use our path
var keys: NSDictionary?

if let path = NSBundle.mainBundle().pathForResource("Keys", ofType: "plist") {
    keys = NSDictionary(contentsOfFile: path)
}

if let dict = keys {
    // variablize our https path with API key, recipient and message text
    let mailgunAPIPath = dict["mailgunAPIPath"] as? String
    let emailRecipient = "bar@foo.com"
    let emailMessage = "Testing%20email%20sender%20variables"

    // Create a session and fill it with our request
    let session = NSURLSession.sharedSession()
    let request = NSMutableURLRequest(URL: NSURL(string: mailgunAPIPath! + "from=FBOGo%20Reservation%20%3Cscheduler@<my domain>.com%3E&to=reservations@<my domain>.com&to=\(emailRecipient)&subject=A%20New%20Reservation%21&text=\(emailMessage)")!)

    // POST and report back with any errors and response codes
    request.HTTPMethod = "POST"
    let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
        if let error = error {
            print(error)
        }

        if let response = response {
            print("url = \(response.URL!)")
            print("response = \(response)")
            let httpResponse = response as! NSHTTPURLResponse
            print("response code = \(httpResponse.statusCode)")
        }
    })
    task.resume()
}

Mailgun路径在Keys.plist中以名为mailgunAPIPath的字符串形式存在,其值为:

https://API:key-<my key>@api.mailgun.net/v3/<my domain>.com/messages?

希望这可以为那些试图避免使用第三方代码进行POST请求的人提供解决方案!

1

SwiftUI iOS15 async/await 的工作示例

   struct ExampleJSONService {
        
        let passwordString = "user:password"
        let configuration = URLSessionConfiguration.default
        
        enum ExampleJSONServiceError: Error {
            case failed
            case failedToDecode
            case invalidStatusCode
        }
        
        func fetchStuff(for myID:String) async throws -> [Stuff] {
            
            let passwordData = passwordString.data(using:String.Encoding.utf8)!
            let base64EncodedCredential = passwordData.base64EncodedString()
            let authString = "Basic \(base64EncodedCredential)"
            let session = URLSession(configuration: configuration)
            
            configuration.httpAdditionalHeaders = ["Authorization" : authString]
            
            let dataUrl = "https://toto.org/stuff/\(myID)/data.json"
            
            let url = URL(string: dataUrl)!
            
            var urlRequest = URLRequest(url: url)
            
            urlRequest.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
            urlRequest.httpMethod = "GET"
            
            let (data, response) = try await session.data(for: urlRequest)
            
            guard let response = response as? HTTPURLResponse,
    
                  response.statusCode == 200  else  {
                      throw PrixJSONServiceError.invalidStatusCode
                  }
            
            let decodedData = try JSONDecoder().decode([Prix].self, from: data)
    
            return decodedData
        }
        
    }

1
我的解决方案如下:
import UIKit


class LoginViewController: UIViewController, NSURLConnectionDataDelegate {

  @IBOutlet var usernameTextField: UITextField
  @IBOutlet var passwordTextField: UITextField

  @IBAction func login(sender: AnyObject) {
    var url = NSURL(string: "YOUR_URL")
    var request = NSURLRequest(URL: url)
    var connection = NSURLConnection(request: request, delegate: self, startImmediately: true)

  }

  func connection(connection:NSURLConnection!, willSendRequestForAuthenticationChallenge challenge:NSURLAuthenticationChallenge!) {

    if challenge.previousFailureCount > 1 {

    } else {
        let creds = NSURLCredential(user: usernameTextField.text, password: passwordTextField.text, persistence: NSURLCredentialPersistence.None)
        challenge.sender.useCredential(creds, forAuthenticationChallenge: challenge)

    }

}

  func connection(connection:NSURLConnection!, didReceiveResponse response: NSURLResponse) {
    let status = (response as NSHTTPURLResponse).statusCode
    println("status code is \(status)")
    // 200? Yeah authentication was successful
  }


  override func viewDidLoad() {
    super.viewDidLoad()

  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()

  }  
}

你可以将这个类用作ViewController的实现。将你的字段连接到IBOutlet注释变量,将你的按钮连接到IBAction注释函数。
解释: 在login函数中,你使用NSURL、NSURLRequest和NSURLConnection创建请求。 重要的是代理,它引用了这个类(self)。 为了接收代理的调用,你需要:
  • 向类添加协议NSURLConnectionDataDelegate
  • 实现协议函数“connection:willSendRequestForAuthenticationChallenge” 这用于向请求添加凭据
  • 实现协议函数“connection:didReceiveResponse” 这将检查HTTP响应状态码

有没有一种方法可以检查同步请求的HTTP响应状态代码? - Matt
NSURLConnection已被弃用。苹果公司强烈建议您使用NSURLSession。 - Sam Soffes

1

我在登录按钮点击时调用json。

@IBAction func loginClicked(sender : AnyObject){

var request = NSMutableURLRequest(URL: NSURL(string: kLoginURL)) // Here, kLogin contains the Login API.


var session = NSURLSession.sharedSession()

request.HTTPMethod = "POST"

var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(self.criteriaDic(), options: nil, error: &err) // This Line fills the web service with required parameters.
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
 //   println("Response: \(response)")
var strData = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Body: \(strData)")       
var err1: NSError?
var json2 = NSJSONSerialization.JSONObjectWithData(strData.dataUsingEncoding(NSUTF8StringEncoding), options: .MutableLeaves, error:&err1 ) as NSDictionary

println("json2 :\(json2)")

if(err) {
    println(err!.localizedDescription)
}
else {
    var success = json2["success"] as? Int
    println("Succes: \(success)")
}
})

task.resume()

}

在这里,我为参数创建了一个单独的字典。
var params = ["format":"json", "MobileType":"IOS","MIN":"f8d16d98ad12acdbbe1de647414495ec","UserName":emailTxtField.text,"PWD":passwordTxtField.text,"SigninVia":"SH"]as NSDictionary
     return params
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接