Безопасное управление аутентификацией в Delphi приложениях: как хранить и обновлять OAuth 2.0 токены и безопасно управлять API ключами в REST клиентах?
В современном мире интеграции с REST API безопасная обработка аутентификации становится критически важной задачей для разработчиков Delphi. В этой статье мы рассмотрим лучшие практики работы с OAuth 2.0, JWT и API ключами, а также безопасные способы хранения и обновления токенов.
Основные проблемы аутентификации в Delphi
При работе с REST API разработчики сталкиваются с несколькими ключевыми проблемами:
Безопасное хранение учетных данных
Управление сроком действия токенов
Защита от несанкционированного доступа
Обновление истекших токенов
Хранение API ключей и токенов
1. Шифрование чувствительных данных
Как правильно отметил Angus Robertson, API ключи и токены следует рассматривать как пароли и хранить их в зашифрованном виде. Вот пример использования шифрования в Delphi:
uses System.NetEncoding, DCPcrypt2, DCPsha256, DCPrijndael;
function EncryptString(const AText, APassword: string): string;
var
Cipher: TDCP_rijndael;
begin
Cipher := TDCP_rijndael.Create(nil);
try
Cipher.InitStr(APassword, TDCP_sha256);
Result := Cipher.EncryptString(AText);
Cipher.Burn;
finally
Cipher.Free;
end;
end;
function DecryptString(const AText, APassword: string): string;
var
Cipher: TDCP_rijndael;
begin
Cipher := TDCP_rijndael.Create(nil);
try
Cipher.InitStr(APassword, TDCP_sha256);
Result := Cipher.DecryptString(AText);
Cipher.Burn;
finally
Cipher.Free;
end;
end;
2. Использование Windows Credential Manager
Как предложил Die Holländer, для хранения учетных данных можно использовать встроенный Credential Manager Windows. Библиотека VSoft.WindowsCredentialManager (https://github.com/VSoftTechnologies/VSoft.WindowsCredentialManager) упрощает эту задачу:
uses VSoft.WindowsCredentialManager;
procedure SaveCredentials;
var
CredMgr: ICredentialManager;
begin
CredMgr := TCredentialManager.Create;
CredMgr.SaveCredential('MyApp_API', 'client_id', 'secret_value');
end;
function LoadCredentials: string;
var
CredMgr: ICredentialManager;
begin
CredMgr := TCredentialManager.Create;
Result := CredMgr.GetCredential('MyApp_API');
end;
Работа с OAuth 2.0 токенами
1. Хранение и обновление токенов
При работе с OAuth 2.0 важно правильно обрабатывать истечение срока действия токенов. Вот пример класса для управления токенами:
type
TOAuthTokenManager = class
private
FAccessToken: string;
FRefreshToken: string;
FExpiresAt: TDateTime;
FClientID: string;
FClientSecret: string;
public
constructor Create(const AClientID, AClientSecret: string);
function IsTokenExpired: Boolean;
function GetAccessToken: string;
procedure RefreshToken;
end;
constructor TOAuthTokenManager.Create(const AClientID, AClientSecret: string);
begin
FClientID := AClientID;
FClientSecret := AClientSecret;
end;
function TOAuthTokenManager.IsTokenExpired: Boolean;
begin
Result := Now >= FExpiresAt;
end;
procedure TOAuthTokenManager.RefreshToken;
var
RESTClient: TRESTClient;
RESTRequest: TRESTRequest;
RESTResponse: TRESTResponse;
begin
RESTClient := TRESTClient.Create('https://oauth.provider.com');
RESTRequest := TRESTRequest.Create(nil);
RESTResponse := TRESTResponse.Create(nil);
try
RESTRequest.Client := RESTClient;
RESTRequest.Response := RESTResponse;
RESTRequest.Method := rmPOST;
RESTRequest.Resource := 'token';
RESTRequest.AddParameter('grant_type', 'refresh_token', pkGETorPOST);
RESTRequest.AddParameter('refresh_token', FRefreshToken, pkGETorPOST);
RESTRequest.AddParameter('client_id', FClientID, pkGETorPOST);
RESTRequest.AddParameter('client_secret', FClientSecret, pkGETorPOST);
RESTRequest.Execute;
if RESTResponse.StatusCode = 200 then
begin
FAccessToken := RESTResponse.JSONValue.GetValue<string>('access_token');
FRefreshToken := RESTResponse.JSONValue.GetValue<string>('refresh_token');
FExpiresAt := Now + RESTResponse.JSONValue.GetValue<Integer>('expires_in') / SecsPerDay;
end
else
raise Exception.Create('Token refresh failed');
finally
RESTResponse.Free;
RESTRequest.Free;
RESTClient.Free;
end;
end;
function TOAuthTokenManager.GetAccessToken: string;
begin
if IsTokenExpired then
RefreshToken;
Result := FAccessToken;
end;
2. Использование Azure Key Vault
Как упомянул Lars Fosdal, для облачных приложений хорошим решением может быть Azure Key Vault. Вот пример работы с ним:
uses Azure.API, System.Threading;
function GetSecretFromAzureVault(const VaultURL, SecretName, ClientID, ClientSecret: string): string;
var
AzureClient: TAzureClient;
begin
AzureClient := TAzureClient.Create;
try
AzureClient.SetCredentials(ClientID, ClientSecret);
Result := AzureClient.GetSecret(VaultURL, SecretName);
finally
AzureClient.Free;
end;
end;
Альтернативные решения
1. Использование аппаратных идентификаторов
Как отметил GabrielMoraru, получение стабильного аппаратного идентификатора на Windows может быть сложной задачей. Однако можно использовать комбинацию нескольких параметров:
function GetDeviceFingerprint: string;
var
VolumeSerial: DWORD;
ComputerName: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
Size: DWORD;
begin
// Получаем серийный номер системного диска
GetVolumeInformation('C:\', nil, 0, @VolumeSerial, nil, nil, nil, 0);
// Получаем имя компьютера
Size := MAX_COMPUTERNAME_LENGTH;
GetComputerName(ComputerName, Size);
// Комбинируем для создания "отпечатка"
Result := Format('%s-%x', [ComputerName, VolumeSerial]);
end;
2. Многоуровневая аутентификация
Как предложил Patrick PREMARTIN, можно реализовать многоуровневую систему аутентификации:
type
TAPIAuthLevel = (alAnonymous, alUser, alAdmin, alSystem);
TAPIClient = class
private
FUserID: string;
FAppID: string;
FDeviceID: string;
FAuthLevel: TAPIAuthLevel;
public
constructor Create(const AUserID, AAppID, ADeviceID: string; AAuthLevel: TAPIAuthLevel);
function GenerateRequestSignature(const RequestData: string): string;
end;
constructor TAPIClient.Create(const AUserID, AAppID, ADeviceID: string; AAuthLevel: TAPIAuthLevel);
begin
FUserID := AUserID;
FAppID := AAppID;
FDeviceID := ADeviceID;
FAuthLevel := AAuthLevel;
end;
function TAPIClient.GenerateRequestSignature(const RequestData: string): string;
var
Hash: TDCP_sha256;
Digest: array[0..31] of Byte;
I: Integer;
begin
Hash := TDCP_sha256.Create(nil);
try
Hash.Init;
Hash.UpdateStr(FUserID + FAppID + FDeviceID + RequestData);
Hash.Final(Digest);
Result := '';
for I := 0 to 31 do
Result := Result + IntToHex(Digest[I], 2);
finally
Hash.Free;
end;
end;
Лучшие практики безопасности
Никогда не храните секреты в коде - как отметил Patrick PREMARTIN, даже приватные репозитории не являются безопасным местом для хранения ключей.
Используйте HTTPS - все API вызовы должны выполняться только через защищенные соединения.
Реализуйте минимальные необходимые права - предоставляйте приложению только те права, которые ему действительно необходимы.
Регулярно обновляйте токены - не ждите истечения срока действия, обновляйте токены заранее.
Логируйте события аутентификации - но не сами учетные данные.
Заключение
Безопасная аутентификация в Delphi приложениях требует комплексного подхода. В зависимости от требований вашего приложения вы можете выбрать один из рассмотренных методов или их комбинацию. Windows Credential Manager и Azure Key Vault предоставляют надежные решения для хранения секретов, а правильная реализация OAuth 2.0 клиента обеспечит безопасное взаимодействие с REST API.
Помните, что безопасность - это процесс, а не разовое мероприятие. Регулярно пересматривайте и обновляйте свои решения в соответствии с новыми угрозами и лучшими практиками.
В статье рассматриваются лучшие практики безопасной аутентификации в Delphi приложениях при работе с REST API, включая хранение API ключей и токенов, работу с OAuth 2.0, использование Azure Key Vault и другие альтернативные решения.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.