我试图通过以下代码用TIdHTTP(Indy 10.6.0和Delphi XE5)测试我的Web服务:
GIdDefaultTextEncoding := encUTF8;
HTTP.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
Http.Request.UserName := AUser;
Http.Request.Password := APass;
Http.Request.Accept := 'text/javascript';
Http.Request.ContentType := 'application/json';
Http.Request.ContentEncoding := 'utf-8';
Http.Request.URL := 'https://sameService';
Http.MaxAuthRetries := 1;
Http.Request.BasicAuthentication := True;
TIdSSLIOHandlerSocketOpenSSL(HTTP.IOHandler).SSLOptions.Method := sslvSSLv3;
HTTP.HandleRedirects := True;
UTF-8中的"AUser"one_answers"APass"。当"APass"有相同的俄语字符时,我无法登录。通过"HTTP分析"我看到:
...
Authorization: Basic cDh1c2VyOj8/Pz8/PzEyMw==
从Base64解码(base64decode.org)我们可以看到:
p8user:??????123
为什么DefStringEncoding不起作用?
TIdHTTP
的身份验证系统没有TIdIOHandler
或其DefStringEncoding
属性的概念。
在内部,TIdBasicAuthentication
使用TIdEncoderMIME.Encode()
,但没有指定任何编码。TIdEncoder.Encode()
默认为8位编码,因此不受GIdDefaultTextEncoding
的影响。
如果您需要发送带有BASIC
身份验证的UTF-8编码密码,则必须手动对UTF-8数据进行编码,并将生成的八位字节存储到string
中,然后8位编码器可以按原样处理八位字节,例如:
Http.Request.Password := BytesToStringRaw(IndyTextEncoding_UTF8.GetBytes(APass));
另一方面,例如,Indy的DIGEST
身份验证使用TIdHashMessageDigest5.HashStringAsHex()
,而TIdHash.HashString()
不默认任何特定编码,它取决于GIdDefaultTextEncoding
。
因此,您必须小心如何对密码进行编码,并根据使用的身份验证进行编码。为了解释这种差异,您可以尝试不对TIdHTTP.Request.Password
本身进行编码,而是在使用BASIC
身份验证时在TIdHTTP.OnAuthorization
事件内对密码进行编码,例如:
Http.Request.Password := APass;
...
procedure TMyForm.HttpAuthorization(Sender: TObject;
Authentication: TIdAuthentication; var Handled: Boolean);
begin
if Authentication is TIdBasicAuthentication then
begin
Authentication.Password := BytesToStringRaw(IndyTextEncoding_UTF8.GetBytes(TheDesiredPasswordHere));
Handled := True;
end;
end;
更新:
在内部,
TIdBasicAuthentication
使用TIdEncoderMIME.Encode()
,但未指定任何编码。
最后一部分不再是真的。TIdBasicAuthentication
于2016年更新,现在将编码传递给TIdEncoderMIME.Encode()
。当HTTP服务器请求BASIC
身份验证时,TIdBasicAuthentication
现在检查服务器的WWW-Authenticate
标头是否包括以下属性之一:charset
、accept-charset
、encoding
或enc
(按顺序)。如果找到一个,则指定的字符集将传递给Encode()
,否则将使用ISO-8859-1
(如果用户名或密码包含ISO-8859-1
中不存在的任何字符,则代码中会有一个TODO
来使用UTF-8
)。
如果要确保在BASIC
身份验证中使用UTF-8,最好将Request.BasicAuthentication
设置为False
,并使用Request.CustomHeaders
提供自己的Authorization
标头,例如:
Http.Request.BasicAuthentication := False;
Http.Request.CustomHeaders.Values['Authorization'] := 'Basic ' + TIdEncoderMIME.EncodeString(AUser + ':' + APass, IndyTextEncoding_UTF8);
或者,可以在TIdHTTP.OnAuthorization
事件(在解析服务器的WWW-Authenticate
标头后激发)内更新受保护的TIdBasicAuthentication.FCharset
成员,例如:
Http.Request.Password := APass;
...
type
TIdBasicAuthenticationAccess = class(TIdBasicAuthentication)
end;
procedure TMyForm.HttpAuthorization(Sender: TObject;
Authentication: TIdAuthentication; var Handled: Boolean);
begin
if Authentication is TIdBasicAuthentication then
begin
TIdBasicAuthenticationAccess(Authentication).FCharset := 'utf-8';
Authentication.Password := TheDesiredPasswordHere;
Handled := True;
end;
end;