Flutter OAuth1.0 验签心路历程

218 阅读1分钟

实现OAuth1签名生成

能力一般,技术有限,不喜勿喷!!!

Flutter对接Netsuite RESTlet 接口,卡了一天终于解决了

同样的数据在ApiFox上就可以执行成功,而我一直是 Error Code: 403,在网上找了很多文章最终都是 oauth_signature 签名与ApiFox不一致,但这种问题一般只有两种情况:
  • 签名基串参数或排序不对
  • 加密错误

排错后发现是参数不对导致签名不一致,POST请求方式,BaseUrl中queryParameters也需要加入签名参数中

Step 1: 收集参数

用于请求头Authorization参数

Map<String, String> parameters = {
      'oauth_consumer_key': _consumerKey, // 消费者密钥
      'oauth_token': _token, // 访问令牌
      'oauth_signature_method': oauthSignatureMethod, // 签名方法
      'oauth_timestamp': timestamp, // 时间戳
      'oauth_nonce': nonce, // 随机字符串
      'oauth_version': '1.0', // OAuth版本
    };

合并OAuth参数和queryParameters参数,此数据只用于生成签名,我就是在这里栽了跟头,在网上找了很多文章都没有明确的说明请求url中的查询参数需要加入签名,在这里需要复制一个新的Map数据。

// 合并请求参数
Map<String, String> parametersWithQuery = Map.from(parameters) ..addAll(uri.queryParameters);

Step 2: 创建签名基字符串

static String _generateSignatureBaseString(
      String method, String baseUrl, Map<String, String> parameters) {
      
    // Step 1: 对参数排序
    var sortedParams = parameters.entries.toList()
      ..sort((a, b) => a.key.compareTo(b.key)); // 按照参数键进行排序

    // Step 2: 构造规范参数字符串
    String paramString = sortedParams
        .map((e) =>
            '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}') // 编码参数键和值
        .join('&'); //拼接为参数字符串

    // Step 3: 构造签名基字符串
    return '$method&${Uri.encodeComponent(baseUrl)}&${Uri.encodeComponent(paramString)}'; // 返回签名基字符串
  }

Step 3: 创建签名密钥

// 创建签名基础字符串
String signingKey ='${Uri.encodeComponent(_consumerSecret)}&${Uri.encodeComponent(_tokenSecret)}';

Step 4: 生成HMAC-SHA256签名

static String _generateHmacSha256(String data, String key) {
    var hmac = Hmac(sha256, utf8.encode(key)); // 创建HMAC对象
    var digest = hmac.convert(utf8.encode(data)); // 生成摘要
    return base64.encode(digest.bytes); // 返回Base64编码的签名
  }
}

Step 5: 创建Authorization头值

//返回Authorization头值
'OAuth realm="$_realm", ${parameters.entries.map((e) => '${e.key}="${Uri.encodeComponent(e.value)}"').join(',')}';

完整代码

class OAuth1 {
  static const String _consumerKey = 'your consumerKey';        //消费者密钥
  static const String _consumerSecret = 'your consumerSecret';  //消费者密钥的密钥
  static const String _token = 'your token';                    //访问令牌
  static const String _tokenSecret = 'your tokenSecret';        //令牌密钥
  static const String _realm = 'your realm';                    //领域

  // 生成OAuth签名
  // [method] HTTP请求方法(GET、POST等)
  // [url] 请求url
  static String generateOAuthSignature({
    required String method,
    required String url,
    String oauthSignatureMethod = 'HMAC-SHA256',
  }) {
    String timestamp =
        (DateTime.now().millisecondsSinceEpoch / 1000).floor().toString();
    String nonce = DateTime.now().millisecondsSinceEpoch.toString();
    Uri uri = Uri.parse(url);

    // Step 1: 收集参数
    Map<String, String> parameters = {
      'oauth_consumer_key': _consumerKey,
      'oauth_token': _token,
      'oauth_signature_method': oauthSignatureMethod,
      'oauth_timestamp': timestamp,
      'oauth_nonce': nonce,
      'oauth_version': '1.0',
    };
    // Step 2: 合并OAuth参数和查询参数
    Map<String, String> parametersWithQuery = Map.from(parameters)
      ..addAll(uri.queryParameters);

    // Step 3: 创建签名基字符串
    String baseString = _generateSignatureBaseString(
        method, '${uri.origin}${uri.path}', parametersWithQuery);

    // Step 4: 创建签名密钥
    String signingKey =
        '${Uri.encodeComponent(_consumerSecret)}&${Uri.encodeComponent(_tokenSecret)}';

    // Step 5: 生成HMAC-SHA256签名
    String signature = _generateHmacSha256(baseString, signingKey);
    parameters['oauth_signature'] = signature;

    // Step 6: 创建Authorization头值
    return 'OAuth realm="$_realm", ${parameters.entries.map((e) => '${e.key}="${Uri.encodeComponent(e.value)}"').join(',')}';
  }

  // 创建签名基字符串
  static String _generateSignatureBaseString(
      String method, String baseUrl, Map<String, String> parameters) {
    // Step 1: 对参数排序
    var sortedParams = parameters.entries.toList()
      ..sort((a, b) => a.key.compareTo(b.key));

    // Step 2: 构造规范参数字符串
    String paramString = sortedParams
        .map((e) =>
            '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
        .join('&');

    // Step 3: 构造签名基字符串
    return '$method&${Uri.encodeComponent(baseUrl)}&${Uri.encodeComponent(paramString)}';
  }

  /// 生成HMAC-SHA256签名
  static String _generateHmacSha256(String data, String key) {
    var hmac = Hmac(sha256, utf8.encode(key));
    var digest = hmac.convert(utf8.encode(data));
    return base64.encode(digest.bytes);
  }
}

最终

感谢阅读, 各路大神如有错误欢迎指出,愿意修改,或有更好的办法欢迎评论留言。