Projekto tikslai
Toliau išvardyti tikslai apibrėžia esminius saugios ir plečiamos naudotojų autentifikavimo sistemos komponentus Progress OpenEdge programoje.
- Saugus slaptažodžių maišymas naudojant SHA-256 su druskomis (salts), gaunamomis iš aplinkos kintamųjų.
- JWT pagrindu veikianti „stateless“ sesijų autentifikacija, skirta plečiamam ir saugiam naudotojų valdymui.
- Jautrių konfigūracijos duomenų atskyrimas naudojant aplinkos kintamųjų prieigą.
- Praktinis trečiųjų šalių bibliotekų pritaikymas OpenEdge galimybėms išplėsti (pvz., JWT apdorojimui).
- Patikimas klaidų tvarkymas ir aiškus HTTP atsakymų valdymas, pritaikytas API naudojimui.
Pagrindiniai Progress OpenEdge modulio elementai
Siekiant atitikti šiuolaikinius saugumo standartus, autentifikavimo modulis buvo sukurtas saugiam naudotojų registracijos ir prisijungimo valdymui Progress OpenEdge aplinkoje.
Įgyvendinimas apima šiuos pagrindinius elementus:
- Slaptažodžių maišymas ir „salting“, naudojant MESSAGE-DIGEST ir GENERATE-PBE-SALT.
- Aplinkos kintamieji per OS-GETENV(), skirti saugiam konfigūracijos tvarkymui (.env).
- JWT žetonai „stateless“ autentifikacijai, įgyvendinti naudojant trečiosios šalies biblioteką.
- Saugus sesijų valdymas, prijungiant JWT prie HTTP-only ir secure slapukų (cookies).
- Moduliškas ir plečiamas kodas, skirtas integracijai į platesnę verslo logiką ar API.
Kodėl JWT?
JSON Web Tokens (JWT) suteikia „stateless“ autentifikavimo mechanizmą, idealiai tinkantį paskirstytoms sistemoms. Pagrindiniai privalumai:
- Savarankiškas (self-contained). Turi naudotojo teiginius (claims) (naudotojo vardą, rolę ir t.t.), eliminuojant DB užklausas.
- Apsaugotas nuo klastojimo. Skaitmeniniu būdu pasirašytas naudojant HS256 algoritmą.
- Galiojimo laikas. Integruotas žetono galiojimo laikas.
- Suderinamumas tarp paslaugų. Žetonus galima patikrinti skirtingose paslaugose, nereikalaujant bendros sesijos saugyklos.
Pagrindinės saugumo funkcijos:
- Druskos ir JWT saugumo raktas. Druska ir JWT saugumo raktas saugiai laikomi .env faile.
- SHA-256 maišymas. Pramonės standartinis kriptografinis maišymas.
- Saugus saugojimas. Saugomos tik maišos reikšmės, niekada – grynojo teksto slaptažodžiai.
Procesai diagramose
Diagramos iliustruoja pagrindinius tikslus ir naudotojo registracijos/prisijungimo funkcijos eigą.
Registracijos procesas

Prisijungimo procesas

Veiksmų seka
- Registracijos metu sistema renka naudotojo informaciją, įskaitant slaptažodį. Slaptažodis maišomas naudojant saugią maišymo funkciją (šiuo atveju SHA-256) ir druską iš aplinkos kintamųjų. Maišytas slaptažodis ir kita naudotojo informacija saugomi duomenų bazėje.
- Kai naudotojas bando prisijungti, įvestas slaptažodis maišomas su saugomu druska ir lyginamas su išsaugota maiša duomenų bazėje. Tai užtikrina, kad slaptažodis liktų saugus, ir užkerta kelią grynojo teksto saugojimui.
- Sėkmingai prisijungus, naudotojui sugeneruojamas JWT (JSON Web Token), kuris saugomas slapuke (Cookie). JWT apima tokius teiginius kaip naudotojo vardas, el. paštas ir rolė, ir veikia kaip sesijos žetonas. Šį JWT tuomet gali patikrinti bet kuri paslauga, kuriai reikia autentifikacijos, todėl naudotojo patikrinimas tampa lengvas ir saugus.
Įgyvendinimo iššūkiai Progress OpenEdge autentifikacijoje
Saugios autentifikavimo sistemos kūrimas Progress OpenEdge aplinkoje kelia keletą techninių ir praktinių iššūkių, įskaitant kriptografinį įgyvendinimą, bibliotekų apribojimus ir klaidų tvarkymą.
- Slaptažodžių saugumas. Saugaus slaptažodžių maišymo įgyvendinimas reikalauja dėmesio tiek maišos stiprumui, tiek druskos naudojimui. Būtina užtikrinti, kad maišymo ir druskos generavimo funkcijos veiktų teisingai.
- JWT valdymas. JWT generavimas ir valdymas Progress ABL kalboje gali būti sudėtingas. Progress OpenEdge neturi integruotos JWT generavimo funkcijos, todėl jai įgyvendinti naudojama trečiosios šalies biblioteka. Nuoroda pateikta Šaltinių skiltyje.
- Klaidų tvarkymas. Kai autentifikacija nepavyksta (neteisingas slaptažodis ar pasibaigęs žetono galiojimas), sistema turi sklandžiai tvarkyti klaidas ir pateikti naudingus pranešimus.
- Riboti ištekliai. Santykinis lengvai prieinamų išteklių, bibliotekų ir geriausių praktikų trūkumas, įgyvendinant šiuolaikinę autentifikaciją ir autorizaciją Progress ABL kalboje, gali padidinti kūrimo laiką ir sudėtingumą. Programuotojams dažnai tenka kurti pasirinktinius sprendimus nuo nulio, o tai gali sukelti nenuoseklumų ir potencialių saugumo spragų.
Autentifikacijos įgyvendinimo poveikis ir nauda
Šios funkcijos įgyvendinimas pagerina tiek saugumą, tiek naudojamumą autentifikacijos procese Progress OpenEdge programoje. Pagrindiniai rezultatai:
- Saugi autentifikacija. Slaptažodžiai saugiai maišomi ir saugomi, sumažinant slaptažodžių vagystės riziką.
- JWT generavimas. Trečiosios šalies biblioteka naudojama JWT žetonams generuoti po sėkmingos naudotojo autentifikacijos.
- Lengvas sesijų valdymas. JWT žetonai supaprastina naudotojo sesijų valdymą ir gali būti lengvai patikrinti skirtingose programos dalyse.
Pagrindiniai saugumo komponentai
1. Slaptažodžių saugumas
„Salting“ ir maišymo eiga
Naudotojo slaptažodis → Druskos gavimas iš aplinkos kintamųjų → SHA-256 maiša → Saugojimas (maiša)
method public character HashPassword(input pcPassword as character):
define variable rHash as raw no-undo.
define variable rSalt as raw no-undo.
/* Retrieve salt from environment variables */
rSalt = hex-decode(os-getenv("SALT")).
/* Generate SHA-256 hash with salt */
rHash = message-digest("SHA-256", pcPassword, rSalt).
return hex-encode(rHash).
end method.
2. Slaptažodžio patikrinimas
Šis metodas saugiai patikrina naudotojo įvestą slaptažodį, palygindamas jį su saugoma maiša, naudojant SHA-256 ir druską, gautą iš aplinkos kintamojo.
method public logical MatchPassword(input pcUserPassword as character, input pcHashedPassword as character):
if HashPassword(pcUserPassword) = pcHashedPassword then
return true.
return false.
end method.
JWT generavimas
Žetono kūrimas
Šis metodas (login) autentifikuoja naudotoją, sugeneruoja JSON Web Token (JWT) su naudotojo duomenimis ir 2 valandų galiojimo laiku, ir grąžina žetoną, kuris vėliau nustatomas slapuke. Pasirašymui naudojamas slaptas raktas iš aplinkos kintamojo.
using jwtoe.Jwt.
using jwtoe.JwtBuilder.
method public character login(input pcUsername as character, input pcPassword as character):
/* ... Password validation ... */
cJwtToken = Jwt:builder()
:setClaim("username", ttAccount.username)
:setClaim("role", ttAccount.role)
:setExpiresInSeconds(7200) // 2h expiry
:signWithHS256Key(os-getenv("JWT_SECRET")) // Key from .env
:compact().
return cJwtToken.
end method.
Reikšmių naudojimas iš JWT turinio (payload)
Šis ABL kodo fragmentas parodo, kaip analizuoti ir patikrinti JSON Web Token (JWT), naudojant trečiosios šalies biblioteką. Jis gauna teiginius (duomenis) iš patikrinto žetono ir juos parodo. Taip pat įtraukiamas klaidų tvarkymas netinkamiems žetonams.
using Progress.Lang.*.
using jwtoe.Jwt.
using jwtoe.JwtError.
using Progress.Json.ObjectModel.JsonObject.
method public void ValidateJWT( input pcJwtToken as character ):
def var oClaims as JsonObject no-undo.
oClaims = Jwt:parseBuilder()
:setSigningKeyHS256(os-getenv("JWT_SECRET")) // Key from .env
:parseClaimsJws(pcJwtToken). // Token we created in login() method
// here you are sure that token was valid
message oClaims:GetCharacter("username").
message oClaims:GetCharacter("role").
end method.
4. Aplinkos kintamųjų saugumas
Svarbu suprasti, kad .env failas turi itin jautrius duomenis, įskaitant API raktus, duomenų bazės prisijungimo duomenis ir kriptografines paslaptis.
Šis failas jokiu būdu neturi būti įkeltas į jokią internetinę saugyklą ar viešai pasidalintas.
.env failo pavyzdys:
# .env
# Example how content of .env looks in this case
SALT=DEADBEEF12345678
JWT_SECRET=my_super_secret_key_here
Aplinkos kintamųjų gavimas ABL kalboje
define variable cSalt as character no-undo.
define variable cSecret as character no-undo.
cSalt = hex-decode(os-getenv("SALT")).
cSecret = os-getenv("JWT_SECRET").
Pagrindiniai kodo fragmentai
1. Atsakymo formatavimas
Šis ABL metodas (FormatResponse) supaprastina HTTP atsakymo kūrimą: sukuria JSON atsakymą su nurodytu statuso kodu ir turiniu, pasirinktinai prideda JWT slapuką ir įrašo atsakymą, sumažindamas pasikartojantį (boilerplate) kodą.
Svarbu paminėti:
- HttpOnly: tai neleidžia kliento pusės JavaScript pasiekti slapuko. Tai sumažina cross-site scripting atakų riziką, kai kenkėjiški skriptai galėtų pavogti slapuką ir pažeisti naudotojo sesiją.
- SecureOnly: tai užtikrina, kad slapukas siunčiamas tik per HTTPS ryšius. Tai apsaugo nuo užpuolikų, galinčių perimti slapuką ir jame esantį JWT.
method protected void FormatResponse(input poStatusEnum as StatusCodeEnum, input pcBody as character):
define variable oWriter as WebResponseWriter no-undo.
define variable oResponse as WebResponse no-undo.
define variable oCookie as Cookie no-undo.
define variable dtExpires as datetime-tz no-undo.
dtExpires = now + 3600.
assign
oResponse = new WebResponse()
oResponse:StatusCode = integer(poStatusEnum)
oResponse:Entity = new String(pcBody)
oResponse:ContentType = "application/json"
.
if this-object:cJwtToken <> "" then
do:
oCookie = new Cookie(
"token", // CookieName
"", // Domain (empty string if not needed)
"/", // Path
this-object:cJwtToken, // CookieValue
3600, // MaxAge in seconds
dtExpires, // ExpiresAt: one hour from now
true, // SecureOnly: true for HTTPS
true, // HttpOnly: true for security
1.0 // Version: typically 1.0
).
oResponse:SetCookie(oCookie).
this-object:cJwtToken = "".
end.
oWriter = new WebResponseWriter(oResponse).
oWriter:Open().
oWriter:Close().
end method.
2. Registracijos apdorojimas (handler)
Šis ABL metodas tvarko naudotojo registraciją: analizuoja JSON, sukuria paskyrą ir grąžina HTTP atsakymą (201 sėkmės atveju, 400 ar 500 klaidų atveju).
method private integer HandleRegister(poRequest as IWebRequest):
define variable hAccount as handle no-undo.
define variable oJsonBody as JsonObject no-undo.
define variable oAccountArray as JsonArray no-undo.
empty temp-table ttAccount.
oJsonBody = cast(poRequest:Entity, JsonObject).
hAccount = temp-table ttAccount:handle.
oAccountArray = cast(oJsonBody:GetJsonArray("ttAccount"), JsonArray).
hAccount:read-json("JsonArray", oAccountArray, "append").
oAccountService:accountForm(table ttAccount).
// Successful response
this-object:FormatResponse(StatusCodeEnum:Created,"Account Created").
// Responses on errors
catch e as Progress.Lang.Error:
if e:GetClass() = get-class(AppError) then
this-object:FormatResponse(StatusCodeEnum:BadRequest,"Registration Failed: " + e:GetMessage(1)).
else
this-object:FormatResponse(StatusCodeEnum:InternalServerError,"Server Error").
end catch.
end method.
Registracijos front-end imitavimas su Postman:
- klientas siunčia POST užklausą į /register galinį tašką (endpoint);
- užklausos turinyje yra JSON masyvas, pavadintas ttAccount, su vienu JsonObject objektu, atspindinčiu naudotojo duomenis.
Svarbu pastebėti, kad šioje užklausoje siunčiami grynojo teksto slaptažodžiai. Todėl būtina naudoti HTTPS, kad duomenys būtų užšifruoti ir prisijungimo duomenys nebūtų atskleisti perdavimo metu.
Klaidos tvarkymo pavyzdys, kai nepateiktas el. paštas:

3. Prisijungimo apdorojimas (handler)
Šis ABL metodas tvarko naudotojo prisijungimą: analizuoja JSON prisijungimo duomenis, autentifikuoja, sugeneruoja JWT ir grąžina HTTP atsakymą (200 su žetonu, 401 ar 500 klaidų atveju).
method private integer HandleLogin(poRequest as IWebRequest):
define variable cUsername as character no-undo.
define variable cPassword as character no-undo.
define variable oJsonBody as JsonObject no-undo.
define variable oAccountArray as JsonArray no-undo.
empty temp-table ttAccount.
oJsonBody = cast(poRequest:Entity, JsonObject).
oAccountArray = cast(oJsonBody:GetJsonArray("ttAccount"), JsonArray).
oJsonBody = cast(oAccountArray:GetJsonObject(1), JsonObject).
cUsername = oJsonBody:GetCharacter("username").
cPassword = oJsonBody:GetCharacter("password").
this-object:cJwtToken = oAccountService:login(cUsername, cPassword).
// Successful response
this-object:FormatResponse(StatusCodeEnum:OK, "Login Successful").
// Responses on errors
catch e as Progress.Lang.Error:
this-object:cJwtToken = "".
if e:GetClass() = get-class(AppError) then
this-object:FormatResponse(StatusCodeEnum:Unauthorized,"Login Failed: " + e:GetMessage(1)).
else
this-object:FormatResponse(StatusCodeEnum:InternalServerError,"Server Error").
end catch.
end method.
Prisijungimo front-end imitavimas su Postman:
- klientas siunčia POST užklausą į /login galinį tašką;
- užklausos turinyje yra JSON masyvas, pavadintas ttAccount, su vienu JsonObject objektu, atspindinčiu naudotojo prisijungimo duomenis (naudotojo vardą ir slaptažodį).
Svarbu pastebėti, kad šioje užklausoje siunčiami grynojo teksto slaptažodžiai. Todėl būtina naudoti HTTPS, kad duomenys būtų užšifruoti ir prisijungimo duomenys nebūtų atskleisti perdavimo metu.
Klaidos tvarkymo pavyzdys, kai naudotojas nerastas:

4. Paskyros sukūrimas
Šis metodas sukuria naujus Account įrašus iš ttAccount temp-table, priskirdamas unikalų ID ir perkeldamas duomenis.
method public void createAccount(input table ttAccount):
define buffer bAccount for Account.
find first ttAccount.
if this-object:checkAccountAlreadyExists(ttAccount.userName) then
undo, throw new AppError('Username already exists', 1).
create bAccount.
assign
bAccount.id = next-value(AccountId)
bAccount.username = ttAccount.username
bAccount.email = ttAccount.email
bAccount.password = ttAccount.password
bAccount.role = ttAccount.role
.
release bAccount.
end method.
JWT autorizacija ir prieigos kontrolė
Ši pagalbinė (utility) klasė tvarko JWT pagrindu veikiančią autentifikaciją.
Ji nuskaito žetoną iš užklausos slapuko, patikrina jo parašą ir galiojimo laiką, naudodama JwtParser klasę, ir grąžina iškoduotus naudotojo teiginius.
using jwtoe.JwtParser.
using jwtoe.JwtError.
using Progress.Json.ObjectModel.JsonObject.
using OpenEdge.Net.HTTP.Cookie.
using OpenEdge.Web.IWebRequest.
using Progress.Lang.*.
class JwtGuard:
/* This method checks if the token is valid and returns the claims */
method public static JsonObject RequireValidTokenFromCookie(poRequest as IWebRequest):
define variable oCookie as Cookie no-undo.
define variable cToken as character no-undo.
define variable oClaims as JsonObject no-undo.
define variable oParser as JwtParser no-undo.
/* Get cookie */
oCookie = poRequest:GetCookie("token").
if not valid-object(oCookie) or oCookie:Value = "" then
undo, throw new AppError("Missing cookie. You are not logged in", 401).
cToken = oCookie:Value.
/* Parse the JWT token */
oParser = new JwtParser().
oParser:setSigningKeyHS256(os-getenv("JWT_SECRET")).
oClaims = oParser:parseClaimsJws(cToken).
return oClaims.
end method.
end class.
Jei žetono trūksta, jis netinkamas, ar pasibaigęs jo galiojimas, metama klaida AppError, kuri sukelia Unauthorized (401)atsakymą. Tai efektyviai nustato, ar naudotojas yra prisijungęs.
define variable oClaims as JsonObject no-undo.
oClaims = JwtGuard:RequireValidTokenFromCookie(poRequest).
Gavus teiginius, lengvai galima pritaikyti rolėmis paremtą prieigos kontrolę. Pavyzdžiui, leisti prieigą tik administratoriams:
if oClaims:GetCharacter("role") <> "admin" then
do:
this-object:FormatResponse(StatusCodeEnum:Forbidden, "Access Denied: Only admin can view all accounts").
return 0.
end.
Tai paverčia JwtGuard aiškiu ir centralizuotu būdu tiek autentifikuoti naudotojus, tiek įgyvendinti rolėmis paremtus leidimus apsaugotuose galiniuose taškuose (endpoints).
Norint atsijungti, tiesiog išvalome slapuką, nustatydami jo galiojimo laiką į nulį:
method protected integer HandleLogout(poRequest as IWebRequest):
define variable oResponse as IHttpResponse no-undo.
define variable oWriter as WebResponseWriter no-undo.
define variable oCookie as Cookie no-undo.
define variable dtExpired as datetime-tz no-undo.
dtExpired = now - 1. /* Expired timestamp */
oCookie = new Cookie(
"token", "", "/", "", 0, dtExpired, false, true, 1.0
).
oResponse = new WebResponse().
oResponse:SetCookie(oCookie).
oResponse:StatusCode = int(StatusCodeEnum:OK).
oResponse:Entity = new OpenEdge.Core.String("Logged out").
oResponse:ContentType = "application/json".
oWriter = new WebResponseWriter(oResponse).
oWriter:Open().
oWriter:Close().
return 0.
end method.
Autentifikacijos eiga veiksme
1. Prisijungimas kaip įprastas naudotojas
Naudotojas prisijungia su galiojančiais duomenimis, bet turi ne-administratoriaus rolę (pvz., "role": "user"). Grąžinamas JWT žetonas ir išsaugomas slapuke.

2. Bandymas pasiekti /account kaip įprastam naudotojui
Šis apsaugotas galinis taškas prieinamas tik admin rolei. Kai ne-administratorius bando jį pasiekti, serveris patikrina JWT teiginius ir atsako:

3. Prisijungimas kaip administratorius
Naudotojas prisijungia dar kartą, šįkart su administratoriaus duomenimis. Išduodamas naujas JWT žetonas ir saugomas slapuke.

4. Prieiga prie /account kaip administratoriui
Šįkart JWT turi teisingą administratoriaus rolę. Užklausa leidžiama, ir serveris atsako su paskyrų sąrašu.

5. Atsijungimas
Naudotojas siunčia užklausą į /logout galinį tašką, kuris išvalo slapuką, nustatydamas jam pasibaigusią laiko žymą.

6. Bandymas pasiekti /account po atsijungimo
Kadangi JWT dabar pasibaigęs arba jo nėra, kita užklausa į /account yra atmetama.

Reikia pagalbos įgyvendinant saugią autentifikaciją Progress OpenEdge aplinkoje?
„Baltic Amadeus“ Progress OpenEdge komanda pasiruošusi jus palaikyti – ar planuojate naują architektūrą, modernizuojate senas (legacy) logikas, ar integruojate autentifikaciją su išorinėmis sistemomis. Susisiekite su mumis jau šiandien, kad gautumėte jūsų tikslams pritaikytą ekspertų konsultaciją.
Projekto pavyzdys:
Dažniausiai užduodami klausimai
Kodėl JWT geriau tinka autentifikacijai nei tradicinės sesijos?
JWT yra „stateless“, todėl jį galima patikrinti bet kurioje paslaugoje be bendros sesijos saugyklos. Jis savyje turi naudotojo teiginius, yra skaitmeniniu būdu pasirašytas (apsaugotas nuo klastojimo) ir turi integruotą galiojimo laiką.
Kaip Progress OpenEdge saugiai saugo slaptažodžius?
Slaptažodžiai niekada nesaugomi grynuoju tekstu. Jie maišomi naudojant SHA-256 algoritmą kartu su druska, gaunama iš aplinkos kintamųjų, ir tik gauta maiša saugoma duomenų bazėje.
Kodėl .env failo niekada negalima įkelti į viešą saugyklą?
`.env` failas turi itin jautrius duomenis – API raktus, duomenų bazės prisijungimo duomenis ir kriptografines paslaptis (pvz., JWT raktą ir druską). Jei šis failas patektų į viešą saugyklą, užpuolikas galėtų suklastoti JWT žetonus ar pasiekti duomenų bazę.
Kodėl HttpOnly ir SecureOnly slapukų nustatymai svarbūs JWT saugojimui?
HttpOnly neleidžia kliento pusės JavaScript pasiekti slapuko, sumažinant cross-site scripting atakų riziką. SecureOnly užtikrina, kad slapukas siunčiamas tik per HTTPS, apsaugant nuo žetono perėmimo perdavimo metu.

