如何在 Flutter 中使用 http 进行摘要认证?
Posted
技术标签:
【中文标题】如何在 Flutter 中使用 http 进行摘要认证?【英文标题】:How to make Digest Authentication with http in Flutter? 【发布时间】: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
【问题讨论】:
【参考方案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
【讨论】:
以上是关于如何在 Flutter 中使用 http 进行摘要认证?的主要内容,如果未能解决你的问题,请参考以下文章