Itt jársz most: Kezdőlap > Alkalmazásfejlesztés > JWT alapú authentikálás III - Jelszó helyreállítása

Szűrő megjelenítése

JWT alapú authentikálás III - Jelszó helyreállítása

A klasszikus jelszó-helyreállítási folyamat többnyire két jól elkülöníthető lépésből áll. Az első lépésben a rendszer bekéri a felhasználó valamilyen egyedi azonosítóját. Rendszertől függ, de többnyire a felhasználó regisztrált email címe lesz a kért egyedi azonosító - ennek persze kettős szerepe van. Egyrészt ez egy validálási lépés: a felhasználó rendelkezik egyáltalán fiókkal? Ha a megadott cím nem található az adatbázisban, az egyértelmű jel a rendszer és a felhasználó számára is: itt valami óriási félreértés történik. Ha azonban a cím létezik, a rendszer egy értesítést küld a megadott címre, mellyel folytatható a helyreállítási folyamat. A kapott hivatkozás többnyire egy temporális azonosítót tartalmaz, mellyel a felhasználó átmenetileg jogot kap jelszavának megváltoztatására. Ekkor már a helyreállítási folyamat második lépéséről beszélünk, amikor is a felhasználó egy űrlapon megadhatja új jelszavát, és végre újra bejelentkezhet.

Azonban itt el is értünk a folyamat legkevésbé sem biztonságos pontjához. Természetesen a folyamat minden esetben bízik annyira a felhasználóban, hogy nem hagyta bejelentkezve egy publikus helyen az email fiókját - ez nagyjából egy megkerülhetetlen probléma, a felhasználó figyelmetlensége minden authentikálást igénylő rendszer gyenge pontja. Az igazi problémát azonban sokkal inkább a kiküldött azonosító okozza, mely authentikálatlan felhasználó számára enged írási hozzáférést, ráadásul teszi ezt annak érdekében, hogy a felhasználó jelszót tudjon váltani a korábbi jelszavának ismerete hiányában. A felvetülő biztonsági probléma legalább annyira kacifántos, mint az előző mondatom. Az azonosító megfelelő követése és kezelése hiányában akár illetéktelen jelszó-csere is megtörténhet.

Ennek elkerülése érdekében a helyreállító azonosító fontos, hogy legyen

  • követhető: A rendszernek pontosan tudnia kell, melyik felhasználónak osztotta ki a helyreállításhoz szükséges azonosítót.
  • önmagától elévülő: Az azonosító kiosztása után azt a lehető leghamarabb “fel kell használni”, nem szabad sokáig nyitva tartani ezt a szándékos biztonsági rést.
  • egyszer használható: Egy és csak egy alkalommal lehessen felhasználni az azonosítót.

A Leafletben található implementáció a fenti három kritériumot a JWT tokenek követési lehetőségével éri el.

A folyamat kezdetén, tehát az email cím megadása után, annak sikeres validálása esetén a felhasználó adataiból egy token kerül kigenerálásra. Ez a token ugyanúgy viselkedik, mint az összes többi, tehát ugyanúgy a session store-ba kerül, azonban két apró dologban különböznek. Egyrészt a lejárati idejük mindössze egy óra, tehát a kiállítástól számított egy órán belül fel kell őket használni. A másik különbség, hogy a tokenben tárolt felhasználói szerepkör a felhasználó eredeti szerepkörétől függetlenül minden esetben a RECLAIM értéket veszi fel. Természetesen a token aláírása miatt a szerepkör ugyanúgy nem módosítható, mint bármely más a tokenben tárolt érték, tehát ezzel az egyébként érvényes tokennel a felhasználó a rendszernek csak azon végpontjait éri el, melyek elfogadják a RECLAIM szerepkört. Ez gyakorlatilag csak egy esetben igaz, mégpedig a jelszóhelyreállítást feldolgozó végponton. Így tehát a felhasználó csak ezt a végpontot használhatja ezen token birtokában. A követés szempontjából egyébként fontos megemlíteni, hogy mivel a token az összes többi tokennel megegyező validáláson esik át, a token csak azon kliens és IP cím alól használható, ahol a jelszóhelyreállítás kezdeményezve lett.

@Override
public void demandPasswordReset(LoginContextVO loginContextVO) {
 
	// a felhasználót megkeressük az adatbázisban
	// ha a felhasználó nem található, ez a kérés kivételt dob
	UserDetails userDetails = userDetailsService
			.loadUserByUsername(loginContextVO.getUsername());
			
	// itt lesz a felhasználó szerepköre kicserélve
	UserDetails reclaimUserDetails = generateReclaimUserDetails(userDetails);
	
	// majd legeneráljuk az 1 órás lejáratú tokent ...
	JWTAuthenticationAnswerModel authenticationAnswerModel = jwtComponent
			.generateToken(reclaimUserDetails, RECLAIM_TOKEN_EXPIRATION_IN_HOURS);
			
	// ... és tároljuk azt a Session Store-ban
	storeToken(loginContextVO, authenticationAnswerModel);
 
	// majd értesítjük a felhasználót, illetve elküldjük neki a tokent
	notificationService.passwordResetRequested(PasswordResetRequest.getBuilder()
			.withParticipant(loginContextVO.getUsername())
			.withUsername(((ExtendedUserDetails) reclaimUserDetails).getName())
			.withElevated(isElevatedUser(userDetails))
			.withExpiration(RECLAIM_TOKEN_EXPIRATION_IN_HOURS)
			.withToken(authenticationAnswerModel.getToken())
			.build());
}

A kiállított token nemes egyszerűséggel a helyreállítási folyamat kezdetét visszaigazoló emailben kerül a felhasználóhoz. A felhasználó eredeti szerepköre alapján a rendszer eldönti, melyik helyreállítási útvonalra irányítsa majd a felhasználót (ezzel tartva az azonos kliens megszorítást), és az útvonal végén foglal majd helyet a generált token. Az űrlap elküldésével a kliensnek a kérés Authorization headerjébe kell írnia a tokent, hogy a túloldalon (a backend alkalmazásban) megtörténhessen rajta a validálás. Amennyiben a token még nincs lejárva, és nincs érvénytelenítve sem, illetve a felhasználó is megfelelően írta be az új jelszavát (kétszer ugyanaz, klasszikus), az új jelszó hashelésre és eltárolásra kerül a token által azonosított felhasználó rekordjában. Ezután a token azonnal érvénytelenítésre kerül, így egy újabb jelszócsere azzal már nem lehetséges. Zárólépésként a felhasználó emailben kap értesítést a folyamat sikeres lezárultáról - amely remélhetőleg nem lepi majd meg.

@Override
@PermitReclaim
public Long confirmPasswordReset() {
 
	// megkeressük a token által azonosított felhasználót az adatbázisban
	ExtendedUserDetails userDetails = retrieveAuthenticatedUserDetails();
	
	// értesítjük a folyamat befejezéséről
	notificationService.successfulPasswordReset(PasswordResetSuccess.getBuilder()
			.withParticipant(userDetails.getUsername())
			.withUsername(userDetails.getName())
			.build());
			
	// érvénytelenítjük a tokent
	revokeToken();
 
	return userDetails.getId();
}
 
@Override
public void confirmPasswordReset(String password) throws EntityNotFoundException {
	Long userID = authenticationService.confirmPasswordReset();
	
	// és persze cseréljük a jelszót
	userService.reclaimPassword(userID, passwordEncoder.encode(password));
}
Kommentek

Komment írásához jelentkezz be
Bejelentkezés

Még senki nem szólt hozzá ehhez a bejegyzéshez.