如何在Flutter中使用Here来实现oauth

问题描述

当我尝试通过Here APi获取oauth令牌时出现此错误

这是错误

wrong.","error":"invalid_client","error_description":"errorCode: '401300'. Signature mismatch. Authorization signature or client credential is wrong."}

这是我在Flutter中的oauth代码

import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:let_log/let_log.dart';

class Oauthv1 {
  String oauthv1BaseUrl = "account.api.here.com";

  bool isJson = false;

  final String consumerKey,consumerKeySecret,accessToken,accessTokenSecret;

  Hmac _sigHasher;

  Oauthv1(this.consumerKey,this.consumerKeySecret,this.accessToken,this.accessTokenSecret) {
    var bytes = utf8.encode("$consumerKeySecret");
    _sigHasher = new Hmac(sha256,bytes);
  }

  Oauthv1 forceXml() {
    this.isJson = false;
    return this;
  }

  Future<http.Response> request(Map<String,String> data) {
    if (isJson) {
      data["format"] = "json";
    }
    return _callGetApi("oauth2/token",data);
  }

  Future<http.Response> _callGetApi(String url,Map<String,String> data) {
    Uri requestUrl = Uri.https(oauthv1BaseUrl,url);

    print(data["grant_type"]);
    _setAuthParams("POST",requestUrl.toString(),data);

    requestUrl = Uri.https(requestUrl.authority,requestUrl.path,data);

    String oAuthHeader = _generateOAuthHeader(data);

    Map<String,String> _headers = {
      'Content-Type': 'application/x-www-form-urlencoded','Authorization': '$oAuthHeader'
    };

    //Logger.debug(_headers);

    // Build the OAuth HTTP Header from the data.
    // Build the form data (exclude OAuth stuff that's already in the header).
//    var formData = _filterMap(data,(k) => !k.startsWith("oauth_"));

    //Logger.debug(requestUrl);
    //Logger.warn(data);
    Logger.debug(_headers);

    return _sendGetRequest(requestUrl,{'grant_type': 'client_credentials'},_headers);
  }

  void _setAuthParams(
      String requestMethod,String url,String> data) {
    // Timestamps are in seconds since 1/1/1970.
    // var timestamp = new DateTime.now().toUtc().difference(_epochUtc).inSeconds;
    /* var millisecondsSinceEpoch =
        new DateTime.now().toUtc().millisecondsSinceEpoch;
    var timestamp = (millisecondsSinceEpoch / 100).round(); */

    var ms = (new DateTime.now()).millisecondsSinceEpoch;
    var timestamp = (ms / 1000).round();

    // Add all the OAuth headers we'll need to use when constructing the hash.
    data["oauth_consumer_key"] = consumerKey;
    data["oauth_signature_method"] = "HMAC-SHA256";
    data["oauth_timestamp"] = timestamp.toString();
    data["oauth_nonce"] =
        _randomString(8); // Required,but Twitter doesn't appear to use it
    if (accessToken != null && accessToken.isNotEmpty)
      data["oauth_token"] = accessToken;
    data["oauth_version"] = "1.0";

    // Generate the OAuth signature and add it to our payload.
    data["oauth_signature"] =
        _generateSignature(requestMethod,Uri.parse(url),data);
  }

  /// Generate an OAuth signature from OAuth header values.
  String _generateSignature(
      String requestMethod,Uri url,String> data) {
    var sigString = _toQueryString(data);
    var fullSigData =
        "$requestMethod&${_encode(url.toString())}&${_encode(sigString)}";

    return base64.encode(_hash(fullSigData));
  }

  /// Generate the raw OAuth HTML header from the values (including signature).
  String _generateOAuthHeader(Map<String,String> data) {
    var oauthHeaderValues = _filterMap(data,(k) => k.startsWith("oauth_"));

    return "OAuth " + _toOAuthHeader(oauthHeaderValues);
  }

  /// Send HTTP Request and return the response.
  Future<http.Response> _sendGetRequest(Uri fullUrl,String> data,String> headers) async {
    return await http.post("https://account.api.here.com/oauth2/token",body: 'grant_type=client_credentials',headers: headers);
  }

  Map<String,String> _filterMap(
      Map<String,String> map,bool test(String key)) {
    return new Map.fromIterable(map.keys.where(test),value: (k) => map[k]);
  }

  String _toQueryString(Map<String,String> data) {
    var items = data.keys.map((k) => "$k=${_encode(data[k])}").toList();
    items.sort();

    return items.join("&");
  }

  String _toOAuthHeader(Map<String,String> data) {
    var items = data.keys.map((k) => "$k=\"${_encode(data[k])}\"").toList();
    items.sort();

    return items.join(",");
  }

  List<int> _hash(String data) => _sigHasher.convert(data.codeUnits).bytes;

  String _encode(String data) => percent.encode(data.codeUnits);

  String _randomString(int length) {
    var rand = new Random();
    var codeUnits = new List.generate(length,(index) {
      return rand.nextInt(26) + 97;
    });

    return new String.fromCharCodes(codeUnits);
  }
}

我发送了所有标头和正文请求,但响应失败。正文请求是

grant_type = client_credentials

我真的不知道什么时候不发送哪些数据或错过了哪些参数。我正在使用Flutter Oauth 1.0。 auth在邮递员中可以正常工作,但在Flutter中不能集成。

当我从sendgetrequest()方法打印_headers时,它会打印以下内容: {内容类型:应用程序/ x-www-form-urlencoded,授权:OAuth oauth_consumer_key =“ o2zr ***** bXuA”,oauth_nonce =“ oaqpiovg”,oauth_signature =“ H7M92BoEneotellYHqJCMkMfLOq9sMrm1R5 %3D”, oauth_signature_method =“ HMAC-SHA256”,oauth_timestamp =“ 1601165843”,oauth_version =“ 1.0”}

您可以看到标题格式正确

解决方法

我通过follow step by step: Create OAuth 1.0 signature section on here Documentation and it worked fine

解决了这个问题

问题是oauth参数中的顺序未正确遵循。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...