BSOne.SFC/Tocsg.Module/UserAuthentification/UserAuthentification.pas

287 lines
8.0 KiB
Plaintext

unit UserAuthentification;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Winapi.ShellAPI,
superobject, IdHTTP, IdSSLOpenSSL, System.Math, System.IOUtils, System.RegularExpressions,
Tocsg.Safe, Tocsg.COLib, Tocsg.COLib.Encrypt;
const
CODE_AUTH_OK = 100;
type
// 로그인 성공 시 호출될 이벤트 타입 정의
TNotifyLoginSuccess = procedure(Sender: TObject; ResultCode: Integer) of object;
TAuthForm = class(TForm)
Label1, Label2: TLabel;
EditID, EditPW: TEdit;
ButtonLogin: TButton;
procedure FormCreate(Sender: TObject);
procedure FromClose(Sender: TObject; var Action: TCloseAction);
procedure ButtonLoginClick(Sender: TObject);
private
{ Private declarations }
iFailCount: Integer; // 로그인 실패 횟수
BlockStartTime: TDateTime; // 차단 시작 시간
FOnSuccess: TNotifyLoginSuccess; // 이벤트를 담을 변수
public
{ Public declarations }
sAgentId, sDestIPort: string;
property OnSuccess: TNotifyLoginSuccess read FOnSuccess write FOnSuccess;
end;
var
AuthForm: TAuthForm;
implementation
{$R *.dfm}
procedure TAuthForm.FormCreate(Sender: TObject);
begin
Self.Position := poScreenCenter;
Self.BorderStyle := bsSingle;
Self.FormStyle := fsStayOnTop;
end;
procedure TAuthForm.FromClose(Sender: TObject; var Action: TCloseAction);
begin
// 폼이 닫힐 때 메모리에서 자동으로 제거되도록 설정
Action := caFree;
end;
function IsValidInput(const AText: string): Boolean;
const
// 영문, 숫자, 특수문자만 허용
ALLOWED_PATTERN = '^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};'':"\\|,.<>\/?]+$';
begin
// 빈 값 체크 후 패턴 검사
Result := (AText <> '') and TRegEx.IsMatch(AText, ALLOWED_PATTERN);
end;
function GetPostData(sDestIPort, sUrl, sData: String; sReqType: String = ''): String;
var
HTTP: TIdHTTP;
SSL: TIdSSLIOHandlerSocketOpenSSL;
ss: TStringStream;
begin
Result := '';
try
if sUrl = '' then
exit;
if sUrl[1] = '/' then
Delete(sUrl, 1, 1);
Guard(SSL, TIdSSLIOHandlerSocketOpenSSL.Create(nil));
SSL.SSLOptions.Method := sslvSSLv23;
SSL.SSLOptions.SSLVersions := [sslvTLSv1_2, sslvTLSv1_1, sslvTLSv1];
Guard(HTTP, TIdHTTP.Create(nil));
HTTP.IOHandler := SSL;
with HTTP do
begin
HandleRedirects := true;
Request.Clear;
Request.UserAgent := 'Mozilla/5.0';
Request.ContentType := 'application/xml';
Request.AcceptCharSet := 'UTF-8';
// Request.Connection := 'Keep-Alive';
// Request.CustomHeaders.Values['Keep-Alive'] := 'timeout=300, max=100';
Request.Connection := 'close';
HTTPOptions := HTTPOptions - [hoKeepOrigProtocol];
if sReqType <> '' then
Request.CustomHeaders.Values['requestType'] := sReqType;
HTTPOptions := HTTP.HTTPOptions + [hoForceEncodeParams];
ConnectTimeout := 5000;
ReadTimeout := 5000;
end;
Guard(ss, TStringStream.Create(sData, TEncoding.UTF8));
Result := HTTP.Post(sDestIPort + sUrl, ss);
except
on E: EIdHTTPProtocolException do
begin
Result := E.ErrorMessage;
end;
on E: Exception do
begin
Result := 'Error: ' + E.Message;
end;
end;
end;
procedure TAuthForm.ButtonLoginClick(Sender: TObject);
var
O: ISuperObject;
pSalt, pKek, pDek : TBytes;
sResult, sSalt: String;
nResult: integer;
CurrentTime: TDateTime;
begin
if iFailCount >= 5 then
begin
CurrentTime := Now;
// 차단 시작 시간으로부터 5분이 지났는지 확인
if (CurrentTime - BlockStartTime) < (5 / (24 * 60)) then
begin
MessageBox(0,'로그인 5회 실패로 인해 5분간 로그인이 차단됩니다', 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
Exit;
end
else
begin
// 5분이 지났으면 카운트 초기화
iFailCount := 0;
end;
end;
if EditID.Text = '' then
begin
MessageBox(0,'ID를 입력하세요', 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
EditID.SetFocus;
Exit;
end;
if not IsValidInput(EditID.Text) then
begin
MessageBox(0,'ID에는 영문, 숫자, 특수기호를 제외하고 들어갈 수 없습니다', 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
Exit;
end;
if EditPW.Text = '' then
begin
MessageBox(0,'패스워드를 입력하세요', 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
EditPW.SetFocus;
Exit;
end;
if not IsValidInput(EditPW.Text) then
begin
MessageBox(0,'패스워드에는 영문, 숫자, 특수기호를 제외하고 들어갈 수 없습니다', 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
Exit;
end;
try
O := SO;
// O.S['empId'] := '0505';
// O.S['pw'] := 'Q!w2e3r4t5';
// O.S['agentId'] := '25111809085600FF96644E47';
O.S['empId'] := EditID.Text;
O.S['pw'] := EditPW.Text;
O.S['agentId'] := sAgentId;
sResult := GetPostData(sDestIPort, 'aapi/auth/refresh-token/issue', O.AsJSon);
//showmessage(sResult);
if sResult <> '' then
begin
// 일반 예외(Exception)
if sResult.Contains('Error') then
begin
//_Trace('Error .. GetPostData() .. Msg="%s"', [Result]);
MessageBox(0, LPCWSTR(sResult), 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
exit;
end
// 프로토콜 예외(EIdHTTPProtocolException)
else if sResult.Contains('err') then
begin
O := SO(sResult);
sResult := O.S['err'];
MessageBox(0, LPCWSTR('Error: ' + sResult), 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
Inc(iFailCount);
if iFailCount >= 5 then
begin
BlockStartTime := Now; // 현재 시간 기록
MessageBox(0, '인증 실패 5회 연속으로 5분간 로그인이 차단됩니다.', 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
exit;
end
else
begin
MessageBox(0, PChar(format('아이디 또는 비밀번호가 일치하지 않습니다' + sLineBreak +
'(현재 %d회 실패 / 5회 연속실패 시 차단)', [iFailCount])), 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
end;
exit;
end
else if sResult.Contains('passwordChange') then
begin
O := SO(sResult);
sResult := O.S['passwordChangeRequired'];
if sResult = 'true' then
begin
var sUrl := O.S['passwordChangeUrl'];
if sUrl.StartsWith('/') then
sUrl := sUrl.Substring(1);
var sFullUrl := sDestIPort + sUrl;
ShellExecute(0, 'open', PChar(sFullUrl), nil, nil, SW_SHOWNORMAL);
//sResult := GetPostData(sDestIPort, sUrl, O.AsJSon);
end;
//MessageBox(0, LPCWSTR(sResult), 'BSOne Login', MB_ICONINFORMATION or MB_TOPMOST or MB_SETFOREGROUND);
exit;
end
else
begin
O := SO(sResult);
//O.S['refreshToken']
iFailCount := 0;
//ShowMessage('인증 완료');
end;
end
else
begin
end;
except
on E: Exception do
begin
ShowMessage('오류 발생: Fail .. GetPostData()' + sLineBreak + E.Message);
exit;
end;
end;
// salt 생성
pSalt := TTgColibDrm.CreateSalt(COLIB_128BIT_KEY_LEN);
if pSalt = nil then
begin
ShowMessage('CreateSalt is failed');
exit;
end;
for var i := 0 to COLIB_128BIT_KEY_LEN - 1 do
sSalt := sSalt + Format('%.2x', [pSalt[i]]);
//ShowMessage('Salt : ' + sSalt);
// KEK 생성 및 암호화(사용자 로그인 계정 암호)
pKek := TTgColibDrm.CreateKEK(EditPW.Text, sSalt, 1000, SHA256_DIGEST_SIZE);
if pKek = nil then
begin
ShowMessage('CreateKEK is failed');
exit;
end;
// KEK 복호화 디버그
// var pTemp := TTgColibDrm.GetKEK('kek.bin');
// if (Length(pKek) = Length(pTemp)) and
// CompareMem(@pKek[0], @pTemp[0], Length(pKek)) then
// begin
// ShowMessage('KEK 복호화 성공');
// end;
// DEK 생성 및 암호화(KEK)
TTgColibDrm.CreateDEK(SHA256_DIGEST_SIZE, pKek);
if Assigned(FOnSuccess) then
FOnSuccess(Self, CODE_AUTH_OK); // 메인 서비스에 알림 발송
Close;
end;
end.