Itt jársz most: Kezdőlap > Alkalmazásfejlesztés > Google ReCaptcha backend oldali validálása

Szűrő megjelenítése

Google ReCaptcha backend oldali validálása

Mindannyian ismerjük a ReCaptcha frontend oldali működését - az API most éri el a 3.0-s verzióját, többféle változata is jelen van, a leggyakoribb talán a “Nem vagyok robot” checkbox-bejelölős változat. A widgettel való interakció után látszólag nem történik semmi különös, azonban persze a háttérben egy komplex ellenőrzés történik a kliens és a Google ReCaptcha kiszolgálója között. Ha a szolgáltatás esetleg bizonytalan a felhasználó érvényességében, megkéri, hogy végezzen el egy apró “feladványt”, mely már jó eséllyel zárja ki bot-interakció lehetőségét. A gond mindezzel az, hogy ily módon csupán a form elküldésének lehetőségét tudjuk megakadályozni (ami persze a legtöbb esetben elégséges), de egy szemfüles felhasználó (vagy bot) könnyedén megkerülheti a validációt. Természetesen erre a ReCaptcha fejlesztői is gondoltak, így validálás után a kliens egy tokent kap vissza, melyet a formba írva a validáció megismételhető - immáron megkerülhetetlen módon, az adott web alkalmazás kiszolgálóján.

A token persze a felhasználónak nem mond semmit, a ReCaptcha számára viszont annál többet. Az alapján ugyanis azonosítható az előzetes validációs folyamat, beleértve annak sikerességét is. A form feldolgozása során természetesen validációs lépésként vizsgálható így a token jelenléte (ez persze önmagában még kevés, lehet egy tokennek látszó véletlen karaktersorozat is, amit a felhasználó/bot csak úgy beírt). A token hiánya már árulkodó jel, a feldolgozás azonnal visszautasítható. Amennyiben viszont jelen van, kezdődhet a validálás lényegi része.

A Google nem ad önálló klienst a ReCaptcha service-ének eléréséhez, de alapvetően nincs is rá szükség, könnyedén írhatunk hozzá (a cikk végén linkelem majd a fejlesztői dokumentációt is). A service maga REST alapú, bár kicsit megtévesztő módon a validációs paramétereket nem request bodyban, hanem form-data-ként várja. A szükséges paraméterek a regisztrált domain private API kulcsa, a kliens oldali validáció eredményeként kapott token és opcionálisan a kliens IP címe. A service válaszában egy boolean flag-et találunk, mely a validáció sikerességét jelzi, továbbá a kliens oldali validálást indító host nevét, a validáció időpontját és sikertelen validálás esetén a hibakódot vagy hibakódokat. Fontos megjegyezni, hogy minden token csak egyszer validálható, egy második kérésre már sikertelen választ kapunk majd - ezzel elkerülhető a token újrafelhasználása. Ugyancsak fontos megjegyezni, hogy a válasz fieldjei eléggé össze-vissza vannak elnevezve, sajnos az API picit összecsapottnak tűnik emiatt - deszerializálásnál tehát figyeljünk oda a fieldek megfelelő felolvasására. Szerencsére azonban a tesztelés könnyen kivitelezhető, mivel a privát API kulcshoz localhost cím is hozzárendelhető, így a fejlesztői gépekről is hívhatóvá válik a service - a widgetet pedig akár egy teszt HTML scriptbe is elég belerakni.

Ha kész a kliens, többféle módon is elvégezhetjük a formok validálását - mivel szerettem volna automatizálni a folyamatot, úgy döntöttem egy konfigurálható filterre bízom azt. Az elkészült filter több különböző biztonsági ellenőrzési lépés működését koordinálja, az egyik ilyen ellenőrzési lépés (stratégia) a ReCaptcha token ellenőrzése. Az implementáció az alkalmazás konfigurációjából várja azon útvonalakat (és mivel REST interfészről beszélünk, a hozzátartozó HTTP metódust is), ahol az ellenőrzés szükséges. Ha a kérés ezen útvonalak egyikéről érkezik, ellenőrzi az X-Captcha-Response header érték jelenlétét, és amennyiben az nem található, automatikusan HTTP 403-as üzenettel díjazza a szemétkedni próbáló bot erőfeszítéseit. Ellenkező esetben összeállítja a kérést a ReCaptcha service számára és elküldi annak. Sikeres visszatérés esetén a feldolgozás mindenféle látható, érezhető megszakítás nélkül folytatódik, egyéb esetben újfent HTTP 403 lesz a válasz.

/* A ClientAcceptorFilter feladata a kérést feladó kliens validálása.
 * Amennyiben a kliens azonosító nem felismerhető, megszakítja
 * a kérés további feldolgozását - ez a filter magas precedenciája miatt
 * gyakorlatilag a feldolgozás teljes megszakítását eredményezi
 */
public class ClientAcceptorFilter extends OncePerRequestFilter {
	
	// ...
 
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
			FilterChain filterChain) throws ServletException, IOException {
 
		try {
			if (!isRouteExcluded(request)) {
				UUID clientID = extractClientID(request);
				
				// a requestből kinyert azonosító alapján megtörténik a
				// megfelelő validálási stratégiák meghívása
				Optional.ofNullable(clientValidationMapping.get(clientID))
						.orElseThrow(() -> new UnknownClientException(clientID, request))
						.forEach(strategy -> strategy.validate(request));
			}
			filterChain.doFilter(request, response);
		} catch (SecurityRestrictionViolationException exc) {
			LOGGER.error("Error occurred while performing security validation of request", exc);
			// a validálás sikertelen - a válasz HTTP 403 lesz ez esetben
			response.sendError(HttpServletResponse.SC_FORBIDDEN, exc.getMessage());
		}
	}
}
/* A ReCaptcha validációs stratégia feladata kettős:
 * - egyrészt eldönti, szükséges-e az adott metódus-útvonal kombináción a validálás;
 * - másrészt pedig kinyeri a headerből a captcha tokent és átadja validálásra.
 * Sikertelen validálás esetén kivétellel reagál, mely a ClientAcceptorFilter-ben
 * a feldolgozás megszakítását eredményezi.
 */
public class ReCaptchaRestrictionValidatorStrategy implements RestrictionValidatorStrategy {
 
	// ...
 
	@Override
	public void validate(HttpServletRequest request) throws ClientSecurityViolationException {
 
		if (isCaptchaValidationRequired(request)) {
 
			String reCaptchaResponse = request.getHeader(CAPTCHA_RESPONSE_HEADER);
			if (!StringUtils.isEmpty(reCaptchaResponse)) {
 
				ReCaptchaRequest reCaptchaRequest = ReCaptchaRequest.getBuilder()
						.withResponse(reCaptchaResponse)
						.withSecret(secret)
						.withRemoteIp(request.getRemoteAddr())
						.build();
 
				if (!reCaptchaValidationService.isValid(reCaptchaRequest)) {
					LOGGER.error("ReCaptcha validation failed for request from client [{}]",
						request.getHeader(HEADER_CLIENT_ID));
					throw new ClientSecurityViolationException();
				}
 
			} else {
				LOGGER.error("Missing mandatory ReCaptcha response in request from client [{}]",
					request.getHeader(HEADER_CLIENT_ID));
				throw new ClientSecurityViolationException();
			}
		}
	}
	
	// ...
}
/* A service feladata a validálás elvégzése a kliens meghívásával.
 * Sikertelen válasz vagy kommunikációs hiba esetén a service false értékkel tér vissza.
 */
public class ReCaptchaValidationServiceImpl implements ReCaptchaValidationService {
 
	// ...
 
	@Override
	public boolean isValid(ReCaptchaRequest reCaptchaRequest) {
 
		boolean reCaptchaValid = false;
		try {
			ReCaptchaResponse reCaptchaResponse = reCaptchaClient.validate(reCaptchaRequest);
			reCaptchaValid = reCaptchaResponse.isSuccessful();
 
			if (!reCaptchaValid) {
				LOGGER.warn("ReCaptcha service response for validation token [{}] is {}",
						stripResponseToken(reCaptchaRequest.getResponse()),
						reCaptchaResponse.getErrorCodes());
			}
 
		} catch (CommunicationFailureException e) {
			LOGGER.error("Failed to reach ReCaptcha service", e);
		}
 
		return reCaptchaValid;
	}
}

Felmerülhet persze a kérdés, hogy ha a backend service minden esetben ellenőrizni akarja a captcha tokent az adott útvonalakon, akkor mi a helyzet például egy mobilos klienssel (mondjuk egy natív Android alkalmazással)? A natív alkalmazás felhasználóinak “Nem vagyok robot”-ellenőzése kicsit túlzásnak tűnhet - a fentebb említett konfigurálható filter erre ad megoldást. A filter ugyanis egy másik lépésben ellenőrzi a kliens azonosítóját - ez minden kliens számára kötelező és csak a backend alkalmazásban regisztrált azonosítók érvényesek. Az azonosítókhoz konfigurálhatóak az elvárt biztonsági ellenőrzések, így adott kliensekről teljesen kikapcsolható a captcha ellenőrzés, míg másokról kötelező lesz - egy idegen kliens kérései pedig automatikusan elutasításra kerülnek.

ReCaptcha fejlesztői dokumentáció

ReCaptcha kliens implementáció

Kliens validáció implementáció

Kommentek

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

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