来自 swift iOS 的 Netsuite Oauth 1.0 身份验证

Posted

技术标签:

【中文标题】来自 swift iOS 的 Netsuite Oauth 1.0 身份验证【英文标题】:Netsuite Oauth 1.0 authentication from swift iOS 【发布时间】:2021-05-12 18:21:36 【问题描述】:

我正在尝试将 netsuite 模块集成到 ios 应用程序中。我无法为标头生成正确的 oauth 签名。为此,我尝试使用“OAuthSwift”库,但我不知道如何添加像 oauth_nonce、oauth_signature 这样的标头。我搜索了许多网站并检查了 *** 引用,但没有找到符合我要求的。

根据评论,我遵循了发布的 java 代码 [https://***.com/questions/51286409/netsuite-token-based-authentication]。

这是我处理过的代码。我得到的响应是 INVALID_LOGIN_ATTEMPT。我的猜测是,也许 OAUTH_Signature 是错误的。请帮帮我。

 import UIKit
 import CommonCrypto

 class ViewController: UIViewController 


var OAuth = "";
   override func viewDidLoad() 
       super.viewDidLoad()

   var oauth_val = mediate();
    
    
    let url = URL(string: "https://rest.na1.netsuite.com/app/site/hosting/restlet.nl?script=xxx&deploy=x")
    guard let requestUrl = url else  fatalError() 
    // Create URL Request
    var request = URLRequest(url: requestUrl)
    // Specify HTTP Method to use
    request.httpMethod = "GET"
    
    //http header
    
    request.setValue("application/json", forHTTPHeaderField: "Accept")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue(oauth_val, forHTTPHeaderField: "Authorization")
    
    // Send HTTP Request
    let task = URLSession.shared.dataTask(with: request)  (data, response, error) in
        
        // Check if Error took place
        if let error = error 
            print("Error took place \(error)")
            return
        
        
        // Read HTTP Response Status code
        if let response = response as? HTTPURLResponse 
            print("Response HTTP Status code: \(response.statusCode)")
        
        
        // Convert HTTP Response Data to a simple String
        if let data = data, let dataString = String(data: data, encoding: .utf8) 
            print("Response data string:\n \(dataString)")
        
        
    
    task.resume()
   
func randomString(length: Int) -> String 
  let letters = "2312312312312sadadadadNKSNSKMSLMXSX"
  return String((0..<length).map _ in letters.randomElement()! )



func computeSignature(baseString: String, keyString: String) -> String

    let hmacResult:String = keyString.hmac(algorithm: HMACAlgorithm.SHA1, key: keyString)
    return hmacResult



func mediate()-> String

    let base_url =  "https://rest.na1.netsuite.com/app/site/hosting/restlet.nl?"
    let http_method =  "GET"
    var token_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    var token_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    var consumerkey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    var consumer_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    var SIGNATURE_METHOD = "HMAC-SHA1"
    var OAUTH_NONCE = randomString(length: 20)
    var TIME_STAMP = String(Date().toMillis())
    var OAUTH_VERSION = "1.0"
    var SCRIPT_DEPLOYMENT_ID = "xxx"
    var SCRIPT_ID = "xxx"
    var REALM = "xxxxx"
        
    var encdata = ""
    encdata = encdata + "deploy=" + SCRIPT_DEPLOYMENT_ID + "&"
    encdata = encdata + "oauth_consumer_key=" + consumerkey + "&"
    encdata = encdata + "oauth_nonce=" + OAUTH_NONCE + "&"
    encdata = encdata + "oauth_signature_method=" + SIGNATURE_METHOD + "&"
    encdata = encdata + "oauth_timestamp=" + TIME_STAMP + "&"
    encdata = encdata + "oauth_token=" + token_id + "&"
    encdata = encdata + "oauth_version=" + OAUTH_VERSION + "&"
    encdata = encdata + "script=" + SCRIPT_ID
        
    let encodeData = encdata.urlEncoded()!
    
    let completeData = http_method + "&"  + base_url.urlEncoded()! + "&" + encodeData
    
    var key = "";
    
    key = consumer_secret.urlEncoded()! + "&" + token_secret.urlEncoded()!
    
    var signature = computeSignature(baseString: completeData, keyString: key)

                OAuth = "OAuth realm=\"" + REALM + "\",";
                OAuth = OAuth + "oauth_consumer_key=\"" + consumerkey + "\",";
                OAuth = OAuth + "oauth_token=\"" + token_id + "\",";
                OAuth = OAuth + "oauth_signature_method=\"HMAC-SHA1\",";
                OAuth = OAuth + "oauth_timestamp=\"" + TIME_STAMP + "\",";
                OAuth = OAuth + "oauth_nonce=\"" + OAUTH_NONCE + "\",";
                OAuth = OAuth + "oauth_version=\"" + "1.0" + "\",";
                OAuth = OAuth + "oauth_signature=\"" + signature + "\"";
                return OAuth;




func printCharactersInSet(set: NSCharacterSet) 
    var characters = ""
    let iSet = set.inverted
    for i: UInt32 in 32..<127 
        let c = Character(UnicodeScalar(UInt32(i))!)
        if iSet.hasMember(inPlane: UInt8(i))
        
            characters = characters + String(c)
        
    
    print("characters not in set: \'\(characters)\'")

 


 enum HMACAlgorithm 
case MD5, SHA1, SHA224, SHA256, SHA384, SHA512

func toCCHmacAlgorithm() -> CCHmacAlgorithm 
    var result: Int = 0
    switch self 
    case .MD5:
        result = kCCHmacAlgMD5
    case .SHA1:
        result = kCCHmacAlgSHA1
    case .SHA224:
        result = kCCHmacAlgSHA224
    case .SHA256:
        result = kCCHmacAlgSHA256
    case .SHA384:
        result = kCCHmacAlgSHA384
    case .SHA512:
        result = kCCHmacAlgSHA512
    
    return CCHmacAlgorithm(result)


func digestLength() -> Int 
    var result: CInt = 0
    switch self 
    case .MD5:
        result = CC_MD5_DIGEST_LENGTH
    case .SHA1:
        result = CC_SHA1_DIGEST_LENGTH
    case .SHA224:
        result = CC_SHA224_DIGEST_LENGTH
    case .SHA256:
        result = CC_SHA256_DIGEST_LENGTH
    case .SHA384:
        result = CC_SHA384_DIGEST_LENGTH
    case .SHA512:
        result = CC_SHA512_DIGEST_LENGTH
    
    return Int(result)

 
 extension String 
func hmac(algorithm: HMACAlgorithm, key: String) -> String 
    let cKey = key.cString(using: String.Encoding.utf8)
    let cData = self.cString(using: String.Encoding.utf8)
    var result = [CUnsignedChar](repeating: 0, count: Int(algorithm.digestLength()))
    CCHmac(algorithm.toCCHmacAlgorithm(), cKey!, strlen(cKey!), cData!, strlen(cData!), &result)
    let hmacData:NSData = NSData(bytes: result, length: (Int(algorithm.digestLength())))
    let hmacBase64 = hmacData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength76Characters)// Encoding76CharacterLineLength)
    return String(hmacBase64)


var urlEncoded: String 
    let customAllowedSet = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
    return self.addingPercentEncoding(withAllowedCharacters: customAllowedSet)!


var urlQueryEncoded: String? 
    return self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)


func substring(to offset: String.IndexDistance) -> String 
    let to = self.index(self.startIndex, offsetBy: offset)
    return String(self[..<to])

 

 extension Dictionary 

var urlEncodedQuery: String 
    var parts = [String]()

    for (key, value) in self 
        let keyString = "\(key)".urlEncoded
        let valueString = "\(value)".urlEncoded
        let query = "\(keyString)=\(valueString)"
        parts.append(query)
    

    return parts.joined(separator: "&")

 

 extension Date 
func toMillis() -> Int64! 
    return Int64(self.timeIntervalSince1970 * 1000)

 
 extension StringProtocol 
var containsLetters: Bool  contains  $0.isLetter  
 


 public extension CharacterSet 

static let urlQueryParameterAllowed = CharacterSet.urlQueryAllowed.subtracting(CharacterSet(charactersIn: "&?"))

static let urlQueryDenied           = CharacterSet.urlQueryAllowed.inverted()
static let urlQueryKeyValueDenied   = CharacterSet.urlQueryParameterAllowed.inverted()
static let urlPathDenied            = CharacterSet.urlPathAllowed.inverted()
static let urlFragmentDenied        = CharacterSet.urlFragmentAllowed.inverted()
static let urlHostDenied            = CharacterSet.urlHostAllowed.inverted()

static let urlDenied                = CharacterSet.urlQueryDenied
    .union(.urlQueryKeyValueDenied)
    .union(.urlPathDenied)
    .union(.urlFragmentDenied)
    .union(.urlHostDenied)


func inverted() -> CharacterSet 
    var copy = self
    copy.invert()
    return copy

 



 public extension String 
func urlEncoded(denying deniedCharacters: CharacterSet = .urlDenied) -> String? 
    return addingPercentEncoding(withAllowedCharacters: deniedCharacters.inverted())

 

【问题讨论】:

查看其他语言的关于 OAuth 和 NetSuite 的 SO 问题。前任。德尔福:***.com/questions/57957730Java:***.com/questions/57957730php:***.com/questions/32867476 请检查代码。 let base_url = "baseurl"需要真正的基本网址(不带参数)。 基本 URL 已更新。在这里更改以发布。但在 xcode 中是正确的。仍然得到与 "error" : "code" : "INVALID_LOGIN_ATTEMPT", "message" : "Invalid login attempt." 相同的响应。 您是否在 Postman 或类似工具中进行了测试调用,以确保它与签名相关而不是权限问题? 【参考方案1】:

目前看到一个已确认的错误。

var TIME_STAMP = String(Date().toMillis())

时间戳必须是自 1970 年以来的秒数,而不是毫秒。您引用的 Java 代码检索毫秒并将其除以 1000 得到秒。

String TIME_STAMP = String.valueOf(System.currentTimeMillis() / 1000);

您没有从下面代码中的 baseString 和 Key 生成 HMAC。

func computeSignature(baseString: String, keyString: String) -> String

    let hmacResult:String = keyString.hmac(algorithm: HMACAlgorithm.SHA1, key: keyString)
    return hmacResult

【讨论】:

即使在几秒钟内发送时间戳后,它仍然会出现同样的错误。 您使用了太多自己的代码 - 应该有易于访问和测试的代码来对字符串进行 URI 编码、生成 HMAC 等。

以上是关于来自 swift iOS 的 Netsuite Oauth 1.0 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

NetSuite 如何免Google Play取得安装包

来自动态创建的按钮的 Swift 3 IOS 访问类方法

来自对象的 JSON 在 iOS Swift 3 中出现数组问题

iOS8/Swift 奇怪的空行为与来自 REST 服务的响应

找到开普勒方程的解 (iOS) (swift)

iOS Swift 解析来自 Alamofire 的 JSON 响应