Itt jársz most: Kezdőlap > Alkalmazásfejlesztés > JWT alapú authentikálás II - Session meghosszabbítás

Szűrő megjelenítése

JWT alapú authentikálás II - Session meghosszabbítás

A session meghosszabbítása

Nyilván a legfontosabb kérdés, hogy miért van erre szükség, milyen előnyünk származhat belőle. Természetesen ez a funkció is csak a felhasználó kényelmét szolgálja - és ezzel a kijelentéssel le is zárhatnám akár a bejegyzést is, de persze nem fogom. Akár standard session alapú, akár egy a Leaflethez hasonló REST interfészt biztosító és így egyszersmind állapotmentes rendszer alatt dolgozunk, a felhasználó munkafolyamata biztonsági okokból véges. Előbb-utóbb a session lejár, és így a felhasználó kijelentkeztetésre kerül. Egy REST alapú rendszernél, ahol tokenekkel történik az azonosítás, ez általában még szigorúbb: a tokent tipikusan rövid lejárati idejűre szokás beállítani, ezzel is minimalizálva a token esetleges ellopásával járó illetéktelen behatolási kísérletek esélyét. Persze, a felhasználó nem szeret fél óránként újra bejelentkezni, ezért egy aktívan használt sessiont, ahonnan nem észlelhető furcsa felhasználói aktivitás (például váltakozó forrás IP cím), érdemes lehet meghosszabbítani.

Stratégiák

A Leaflet-ben implementált session meghosszabbítás tervezése során két egymással éles ellentétben álló stratégia közül dönthettem. Az egyik - melyet végül elvetettem - az volt, hogy a meghosszabbítást a backend rendszerre bízom. Kézenfekvő megoldásnak tűnt, hiszen a session store segítségével már tudtam követni a tokeneket, a lejárati idő ellenőrzése a token kinyitása nélkül is lehetséges volt. A meghosszabbítás így történhetett volna oly módon is, hogy a rendszer egy megadott idő-küszöbön belül, a kérés feldolgozása során kiállít egy új tokent és visszaküldi azt a kliensnek. A tokenek kliens felé történő visszakommunikálása már egyébként is adott volt addigra a rendszerben, így teljesen megvalósíthatónak látszott a koncepció. Ami miatt végül azonban elvetettem ezt a megközelítést, az pont az volt, hogy a kliens így nem feltétlenül számít rá, hogy ő új tokent fog kapni. Ha a kliens pedig nem kezdi el használni az új tokent, hanem megpróbálkozok a korábbival, az sikertelen kérést eredményezhet. Mindemellett pedig úgy éreztem, az új token automatikus kiállítása egy felesleges felelősséget róhat a backend rendszerre - ha szükségesnek “érzi”, igényeljen a kliens új tokent.

Így döntöttem végül a másik lehetséges stratégia mellett, azaz hogy a kliens igényeljen tokent a szükséges pillanatban. A Leaflet admin rendszere már implementálja ezt a megvalósítást, méghozzá az alábbi módon.

A megvalósítás

Az admin rendszer egy érvényes session alatt küldi kéréseit a backend felé - tehát nyilván aktív tokennel rendelkezik, mely sem szándékosan (kijelentkezéssel) a felhasználó által, sem automatikusan (lejárati idő, token lopás miatt) a backend által nem lett még visszavonva. Minden sikeres kérés után az alábbi filter megvizsgálja az éppen aktív token lejárati idejét. Amennyiben pedig a megadott thresholdon belül lejár a token, elindítja a meghosszabbítási folyamatot, melynek első lépéseként új tokent igényel a backendtől.

// a SessionExtensionFilter osztály tartalma
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
			FilterChain filterChain) throws ServletException, IOException {
 
	if (enabled) {
	
		// szükségünk van az aktív Authentication objektumra, mivel a token ott van tárolva
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		
		// ellenőrizzük, hogy a token lejár-e a közeljövőben
		if (isExpiringSoon(authentication)) {
			try {
				// ha igen, elkezdjük a meghosszabbítási folyamatot
				userFacade.renewToken(authentication);
			} catch (CommunicationFailureException e) {
				LOGGER.error("Leaflet unreachable - failed to renew user session.", e);
			}
		}
	}
 
	filterChain.doFilter(request, response);
}
private boolean isExpiringSoon(Authentication authentication) {
 
	boolean expiringSoon = false;
	if (authentication instanceof JWTTokenAuthentication) {
		Date expirationDate = ((AuthenticationUserDetailsModel) authentication
				.getDetails()).getExpiration();
		long difference = expirationDate.getTime() - System.currentTimeMillis();
		
		// a lejárati időküszöb az alkalmazás konfigurációjában szabályozható
		// a küszöbérték jelenleg 30 perc, tehát ha a token legfeljebb 30 percen
		// belül lejár, akkor új tokent fog igényelni az alkalmazás
		expiringSoon = TimeUnit.MILLISECONDS.toMinutes(difference) <= threshold;
	}
 
	return expiringSoon;
}

A backenden azonban ez egy trükkös lépés, mivel úgy kell új tokent kiállítani, hogy a felhasználó jelszavával jelenleg nem rendelkezik a rendszer - hiszen csak a tokennel tudjuk azonosítani a felhasználót, az pedig nem tartalmazza - nem is tartalmazhatja - a jelszót. A teljes bejelentkezési folyamatot tehát ez esetben nem játszhatjuk végig, a tokenben azonban jelen van a felhasználó azonosítója (email címe), mellyel a felhasználó újra megkereshető, és azzal számára új token generálható. A meghosszabbítást végző endpoint a backenden így védett kell, hogy legyen, mivel az egyetlen biztos pontunk a felhasználó kilétét illetően ugyanaz a token, amivel addig azonosította magát. Nyilván emiatt a session meghosszabbítása visszavont, lejárt tokennel lehetetlen, legalábbis semmilyen biztonsági kapaszkodónk nem maradna.

// a folyamat backend oldali feldolgozó kódja

@Override
// nagyon fontos, hogy az endpoint védett legyen!
// az alábbi custom PreAuthorize annotáció megköveteli,
// hogy a felhasználó aktív tokennel hívja az endpointot
@PermitAuthenticated
public String extendSession(LoginContextVO loginContextVO) {
 
	// 1. újrageneráljuk a tokent
	JWTAuthenticationAnswerModel authenticationAnswerModel = 
			jwtComponent.generateToken(retrieveAuthenticatedUserDetails());
			
	// 2. visszavonjuk a korábbit
	revokeToken();
	
	// 3. eltároljuk az újat a Session Store-ban
	storeToken(loginContextVO, authenticationAnswerModel);
 
	return authenticationAnswerModel.getToken();
}

Szintén fontos lépés ekkor a régi token visszavonása, mely rögtön az új token kiállítása után megtörténik. Mivel a Security Context-ben ebben a pillanatban még a régi token szerepel, a session store megkerülése és/vagy megerőszakolása nélkül egyszerűen revoke-olható a token. Záróakkordként a token még mentésre kerül a store-ban, majd a Leaflet visszatér a kigenerált új tokennel. Innentől a kliens feladata, hogy az új tokent eltárolja valamilyen formában és onnantól azzal küldje kéréseit. Mivel az admin rendszer jelenleg egy standard sessiont épít fel, mindössze a Security Context-ben kell kicserélni a tokent és a folyamat ezzel be is fejeződött. A felhasználó az egészből pedig semmit nem vesz észre, azonban a háttérben a munkafolyamata már (beállítástól függően) néhány órával tovább tart.

// a UserFacade::renewToken metódus implementációja

@Override
public void renewToken(Authentication authentication) throws CommunicationFailureException {
	// a userBridgeService.renewToken() hívás meghívja a backend alkalmazást,
	// amivel megigényli az új tokent
	// ha sikeres a kérés, az authenticationUtility.replace(...) hívás
	// kicseréli az aktív tokent az újonnan megigényeltre
	// ekkor a régi token már érvénytelen
	authenticationUtility.replace(authentication.getPrincipal().toString(),
			userBridgeService.renewToken().getToken());
}
// az AuthenticationUtility::replace metódus implementációja

void replace(String username, String token) {
 
	// a token cseréje elég egyszerű folyamat: a tokent újracsomagoljuk
	// majd tároljuk a SecurityContext-ben
	Authentication authentication = new JWTTokenAuthentication.Builder()
			.withEmailAddress(username)
			.withDetails(jwtTokenPayloadReader.readPayload(token))
			.withToken(token)
			.build();
	store(authentication);
}
 
void store(Authentication authentication) {
	SecurityContextHolder.getContext().setAuthentication(authentication);
}

A folyamat még akár tovább is biztosítható azzal, ha a token legutóbbi használati idejét is naplózza a rendszer. Ekkor a lejárati thresholdon túl inaktivitási thresholdot is figyelhetünk, mely mondjuk 1-2 óra inaktivitás után visszautasítja a meghosszabbítási kérelmet, vagy akár vissza is vonhatja a tokent, ezzel kényszerítve a klienst az újra-authentikálásra. Ez is egy a számtalan lehetőség közül, melyet a session store biztosít, azonban a sorozat következő (befejező) részében a jelszó helyreállításról lesz szó.

Kommentek

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

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