【问题标题】:How to make Digest Authentication with http in Flutter?如何在 Flutter 中使用 http 进行摘要认证?
【发布时间】:2019-12-22 08:31:59
【问题描述】:

我正在尝试使用摘要式身份验证发出 API 请求。我找到了上述问题FLUTTER How to implement Digest Authentification 的答案,但不是很清楚。摘要的文档非常少。

以下是我的代码

import 'package:http/io_client.dart' as io_client;
import 'package:http/http.dart' as http;

try {
  HttpClient authenticatingClient = HttpClient();

  authenticatingClient.authenticate = (uri, scheme, realm) {

    authenticatingClient.addCredentials(
        uri,
        realm,
        HttpClientDigestCredentials(
            DIGEST_AUTH_USERNAME, DIGEST_AUTH_PASSWORD));

    return Future.value(true);
  };

  http.Client client = io_client.IOClient(authenticatingClient);

  final response = await client.post(LOGIN_URL, body: {
    "username": userName,
    "password": password,
    "user_group": 2
  }).timeout(const Duration(seconds: 20));
  if (response.statusCode == 200) {

    debugPrint(response.body);
    CurvesLoginModel curvesLoginModel = standardSerializers.deserializeWith(
        CurvesLoginModel.serializer, json.decode(response.body));
    return curvesLoginModel;
  } else {
    return null;
  }
} on TimeoutException catch (_) {

  return null;
} on SocketException catch (_) {

  return null;
}


}

但是addCredentials 中的realm 是什么。

这也是在http 中为Flutter 实现Digest Authentication 的方法吗? 一旦我到达终点,我就会收到以下错误Unhandled Exception: type 'int' is not a subtype of type 'String' in type cast

【问题讨论】:

    标签: http flutter dart digest-authentication


    【解决方案1】:

    Realm 是 Web 服务器提供的任意字符串,可帮助您决定使用哪个用户名,以防您有多个用户名。这有点类似于域。在一个域中,您的用户名可能是 fbloggs,而在另一个 fredb 中。通过告诉您您知道要提供哪个领域/域。

    您的转换问题是由于在正文中使用值 2 引起的。那一定是Map<String, String>,但您提供了一个整数。将其替换为2.toString()

    【讨论】:

      【解决方案2】:

      如果有人想知道如何用http做digest auth,那么如下

      import 'dart:async';
      import 'dart:convert';
      import 'dart:math' as math;
      import 'package:convert/convert.dart';
      import 'package:crypto/crypto.dart' as crypto;
      import 'package:http/http.dart' as http;
      
      class DigestAuthClient extends http.BaseClient {
        DigestAuthClient(String username, String password, {inner})
            : _auth = DigestAuth(username, password),
              // ignore: prefer_if_null_operators
              _inner = inner == null ? http.Client() : inner;
      
        final http.Client _inner;
        final DigestAuth _auth;
      
        void _setAuthString(http.BaseRequest request) {
          request.headers['Authorization'] =
              _auth.getAuthString(request.method, request.url);
        }
      
        @override
        Future<http.StreamedResponse> send(http.BaseRequest request) async {
          final response = await _inner.send(request);
      
          if (response.statusCode == 401) {
            final newRequest = copyRequest(request);
            final String authInfo = response.headers['www-authenticate'];
            _auth.initFromAuthorizationHeader(authInfo);
      
            _setAuthString(newRequest);
      
            return _inner.send(newRequest);
          }
      
          // we should reach this point only with errors other than 401
          return response;
        }
      }
      
      Map<String, String> splitAuthenticateHeader(String header) {
        if (header == null || !header.startsWith('Digest ')) {
          return null;
        }
        String token = header.substring(7); // remove 'Digest '
      
        var ret = <String, String>{};
      
        final components = token.split(',').map((token) => token.trim());
        for (final component in components) {
          final kv = component.split('=');
          ret[kv[0]] = kv.getRange(1, kv.length).join('=').replaceAll('"', '');
        }
        return ret;
      }
      
      String md5Hash(String data) {
        var content = const Utf8Encoder().convert(data);
        var md5 = crypto.md5;
        var digest = md5.convert(content).toString();
        return digest;
      }
      
      // from http_retry
      /// Returns a copy of [original].
      http.Request _copyNormalRequest(http.Request original) {
        var request = http.Request(original.method, original.url)
          ..followRedirects = original.followRedirects
          ..persistentConnection = original.persistentConnection
          ..body = original.body;
        request.headers.addAll(original.headers);
        request.maxRedirects = original.maxRedirects;
      
        return request;
      }
      
      http.BaseRequest copyRequest(http.BaseRequest original) {
        if (original is http.Request) {
          return _copyNormalRequest(original);
        } else {
          throw UnimplementedError(
              'cannot handle yet requests of type ${original.runtimeType}');
        }
      }
      
      // Digest auth
      
      String _formatNonceCount(int nc) {
        return nc.toRadixString(16).padLeft(8, '0');
      }
      
      String _computeHA1(String realm, String algorithm, String username,
          String password, String nonce, String cnonce) {
        String ha1;
      
        if (algorithm == null || algorithm == 'MD5') {
          final token1 = "$username:$realm:$password";
          ha1 = md5Hash(token1);
        } else if (algorithm == 'MD5-sess') {
          final token1 = "$username:$realm:$password";
          final md51 = md5Hash(token1);
          final token2 = "$md51:$nonce:$cnonce";
          ha1 = md5Hash(token2);
        }
      
        return ha1;
      }
      
      Map<String, String> computeResponse(
          String method,
          String path,
          String body,
          String algorithm,
          String qop,
          String opaque,
          String realm,
          String cnonce,
          String nonce,
          int nc,
          String username,
          String password) {
        var ret = <String, String>{};
      
        // ignore: non_constant_identifier_names
        String HA1 = _computeHA1(realm, algorithm, username, password, nonce, cnonce);
      
        // ignore: non_constant_identifier_names
        String HA2;
      
        if (qop == 'auth-int') {
          final bodyHash = md5Hash(body);
          final token2 = "$method:$path:$bodyHash";
          HA2 = md5Hash(token2);
        } else {
          // qop in [null, auth]
          final token2 = "$method:$path";
          HA2 = md5Hash(token2);
        }
      
        final nonceCount = _formatNonceCount(nc);
        ret['username'] = username;
        ret['realm'] = realm;
        ret['nonce'] = nonce;
        ret['uri'] = path;
        ret['qop'] = qop;
        ret['nc'] = nonceCount;
        ret['cnonce'] = cnonce;
        if (opaque != null) {
          ret['opaque'] = opaque;
        }
        ret['algorithm'] = algorithm;
      
        if (qop == null) {
          final token3 = "$HA1:$nonce:$HA2";
          ret['response'] = md5Hash(token3);
        } else if (qop == 'auth' || qop == 'auth-int') {
          final token3 = "$HA1:$nonce:$nonceCount:$cnonce:$qop:$HA2";
          ret['response'] = md5Hash(token3);
        }
      
        return ret;
      }
      
      class DigestAuth {
        DigestAuth(this.username, this.password);
      
        String username;
        String password;
      
        // must get from first response
        String _algorithm;
        String _qop;
        String _realm;
        String _nonce;
        String _opaque;
      
        int _nc = 0; // request counter
        String _cnonce; // client-generated; should change for each request
      
        String _computeNonce() {
          math.Random rnd = math.Random();
      
          List<int> values = List<int>.generate(16, (i) => rnd.nextInt(256));
      
          return hex.encode(values);
        }
      
        String getAuthString(String method, Uri url) {
          _cnonce = _computeNonce();
          _nc += 1;
          // if url has query parameters, append query to path
          var path = url.hasQuery ? "${url.path}?${url.query}" : url.path;
      
          // after the first request we have the nonce, so we can provide credentials
          var authValues = computeResponse(method, path, '', _algorithm, _qop,
              _opaque, _realm, _cnonce, _nonce, _nc, username, password);
          final authValuesString = authValues.entries
              .where((e) => e.value != null)
              .map((e) => [e.key, '="', e.value, '"'].join(''))
              .toList()
              .join(', ');
          final authString = 'Digest $authValuesString';
          return authString;
        }
      
        void initFromAuthorizationHeader(String authInfo) {
          Map<String, String> values = splitAuthenticateHeader(authInfo);
          _algorithm = values['algorithm'];
          _qop = values['qop'];
          _realm = values['realm'];
          _nonce = values['nonce'];
          _opaque = values['opaque'];
        }
      
        bool isReady() {
          return _nonce != null;
        }
      }
      

      然后在调用你的 api 时

      final response =
                await DigestAuthClient(DIGEST_AUTH_USERNAME, DIGEST_AUTH_PASSWORD)
                    .post(LOGIN_URL, body: {
              "USERNAME": userName,
              "PASSWORD": password,
              "USER_GROUP": "2"
            }).timeout(const Duration(seconds: 20));
      

      所有功劳归于以下图书馆https://pub.dev/packages/http_auth

      【讨论】:

        猜你喜欢
        • 2020-09-17
        • 1970-01-01
        • 2017-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-03
        • 2013-03-27
        相关资源
        最近更新 更多