Registrering hos Spring - Integrer reCAPTCHA

1. Oversigt

I denne vejledning fortsætter vi Spring Security Registration-serien ved at tilføje GooglereCAPTCHA til registreringsprocessen for at skelne menneske fra bots.

2. Integrering af Googles reCAPTCHA

For at integrere Googles reCAPTCHA-webservice skal vi først registrere vores websted med tjenesten, føje deres bibliotek til vores side og derefter kontrollere brugerens captcha-svar med webservicen.

Lad os registrere vores websted på //www.google.com/recaptcha/admin. Registreringsprocessen genererer en webstedsnøgle og hemmelig nøgle for at få adgang til webservicen.

2.1. Lagring af API-nøglepar

Vi gemmer nøglerne i application.properties:

google.recaptcha.key.site = 6LfaHiITAAAA ... google.recaptcha.key.secret = 6LfaHiITAAAA ...

Og udsæt dem for foråret ved hjælp af en bøn, der er kommenteret med @ConfigurationProperties:

@Component @ConfigurationProperties (præfiks = "google.recaptcha.key") offentlig klasse CaptchaSettings {privat streng websted; privat String hemmelighed; // standard getters og setter}

2.2. Visning af widgeten

Baseret på tutorial fra serie, vil vi nu ændre registrering.html at inkludere Googles bibliotek.

Inde i vores registreringsformular tilføjer vi reCAPTCHA-widgeten, som forventer attributten data-sitekey at indeholde webstedsnøgle.

Widgeten tilføjes anmodningsparameteren g-recaptcha-respons når indsendt:

   ...    ...  ... 

3. Validering af serversiden

Den nye anmodningsparameter koder vores webstedsnøgle og en unik streng, der identificerer brugerens vellykkede afslutning af udfordringen.

Da vi imidlertid ikke kan skelne fra os selv, kan vi ikke stole på, hvad brugeren har indsendt, er legitimt. En anmodning på serversiden foretages for at validere captcha-svar med web-service API.

Slutpunktet accepterer en HTTP-anmodning på URL'en //www.google.com/recaptcha/api/siteverify med forespørgselsparametrene hemmelighed, responsog fjernbetjening. Det returnerer et json-svar med skemaet:

false, "challenge_ts": tidsstempel, "hostname": string, "error-codes": [...] 

3.1. Hent brugers svar

Brugerens svar på reCAPTCHA-udfordringen hentes fra anmodningsparameteren g-recaptcha-respons ved brug af HttpServletRequest og valideret med vores CaptchaService. Enhver undtagelse, der smides under behandlingen af ​​svaret, afbryder resten af ​​registreringslogikken:

offentlig klasse RegistrationController {@Autowired privat ICaptchaService captchaService; ... @RequestMapping (værdi = "/ bruger / registrering", metode = RequestMethod.POST) @ResponseBody offentlig GenericResponse registerUserAccount (@Valid UserDto accountDto, HttpServletRequest anmodning) {String response = request.getParameter ("g-recaptcha-response" ); captchaService.processResponse (svar); // Resten af ​​implementeringen} ...}

3.2. Valideringstjeneste

Det opnåede captcha-svar skal først desinficeres. Der bruges et simpelt regulært udtryk.

Hvis svaret ser legitimt ud, fremsætter vi en anmodning til webservicen med hemmelig nøgle, det captcha-svarog klientens IP-adresse:

offentlig klasse CaptchaService implementerer ICaptchaService {@Autowired private CaptchaSettings captchaSettings; @Autowired private RestOperations restTemplate; privat statisk mønster RESPONSE_PATTERN = Mønster.kompil ("[A-Za-z0-9 _-] +"); @Override public void processResponse (String response) {if (! ResponseSanityCheck (response)) {throw new InvalidReCaptchaException ("Respons indeholder ugyldige tegn"); } URI verificere = URI.create (String.format ("//www.google.com/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s", getReCaptchaSecret (), svar, getClientIP ())) ; GoogleResponse googleResponse = restTemplate.getForObject (verificereUri, GoogleResponse.class); hvis (! googleResponse.isSuccess ()) {kast ny ReCaptchaInvalidException ("reCaptcha blev ikke valideret med succes"); }} privat boolsk responsSanityCheck (strengrespons) {return StringUtils.hasLength (respons) && RESPONSE_PATTERN.matcher (svar) .matches (); }}

3.3. Objektivisering af validering

En Java-bønne dekoreret med Jackson annoteringer indkapsler valideringssvaret:

@JsonInclude (JsonInclude.Include.NON_NULL) @JsonIgnoreProperties (ignoreUnknown = true) @JsonPropertyOrder ({"success", "challenge_ts", "hostname", "error-codes"}) offentlig klasse GoogleResponse {@JsonProperty ("succes") privat boolsk succes; @JsonProperty ("challenge_ts") private String-challengeTs; @JsonProperty ("værtsnavn") privat strengværtsnavn; @JsonProperty ("error-codes") private ErrorCode [] errorCodes; @JsonIgnorer offentlig boolsk hasClientError () {ErrorCode [] fejl = getErrorCodes (); hvis (fejl == null) {returner falsk; } for (ErrorCode-fejl: fejl) {switch (error) {case InvalidResponse: case MissingResponse: return true; }} returner falsk; } statisk enum ErrorCode {MissingSecret, InvalidSecret, MissingResponse, InvalidResponse; private statiske kort errorMap = nye HashMap (4); statisk {errorMap.put ("manglende input-hemmelighed", MissingSecret); errorMap.put ("ugyldig-input-hemmelighed", InvalidSecret); errorMap.put ("manglende input-respons", MissingResponse); errorMap.put ("ugyldigt input-svar", InvalidResponse); } @JsonCreator offentlig statisk ErrorCode forValue (strengværdi) {return errorMap.get (value.toLowerCase ()); }} // standard getters og setters}

Som antydet er en sandhedsværdi i succes egenskab betyder, at brugeren er blevet valideret. Ellers errorCodes ejendom udfyldes med årsagen.

Det værtsnavn henviser til den server, der omdirigerede brugeren til reCAPTCHA. Hvis du administrerer mange domæner og ønsker, at de alle deler det samme nøglepar, kan du vælge at bekræfte værtsnavn ejendom selv.

3.4. Valideringsfejl

I tilfælde af en valideringsfejl smides en undtagelse. ReCAPTCHA-biblioteket skal instruere klienten i at skabe en ny udfordring.

Vi gør det i klientens registreringsfejlhåndterer ved at påkalde nulstilling til bibliotekets grecaptcha widget:

register (event) {event.preventDefault (); var formData = $ ('form'). serialize (); $ .post (serverContext + "bruger / registrering", formData, funktion (data) {hvis (data.message == "succes") {// succeshåndtering}}). mislykket (funktion (data) {grecaptcha.reset ( ); ... hvis (data.responseJSON.error == "InvalidReCaptcha") {$ ("# captchaError"). viser (). html (data.responseJSON.message);} ...}}

4. Beskyttelse af serverressourcer

Ondsindede klienter behøver ikke at overholde reglerne i browsersandkassen. Så vores sikkerhedstankegang skal være på de eksponerede ressourcer, og hvordan de kan blive misbrugt.

4.1. Forsøger cache

Det er vigtigt at forstå, at ved at integrere reCAPTCHA, vil enhver anmodning, der foretages, få serveren til at oprette et stik til validering af anmodningen.

Mens vi har brug for en mere lagdelt tilgang til en ægte DoS-afbødning, kan vi implementere en elementær cache, der begrænser en klient til 4 mislykkede captcha-svar:

offentlig klasse ReCaptchaAttemptService {privat int MAX_ATTEMPT = 4; private LoadingCache forsøg Cache; offentlig ReCaptchaAttemptService () {super (); forsøgCache = CacheBuilder.newBuilder () .expireAfterWrite (4, TimeUnit.HOURS) .build (ny CacheLoader () {@ Override offentlig heltal belastning (strengnøgle) {return 0;}}) } offentlig ugyldig reCaptchaSucceeded (strengnøgle) {forsøgCache.invalidate (nøgle); } offentlig ugyldig reCaptchaFailed (strengnøgle) {int forsøg = forsøgCache.getUnchecked (nøgle); forsøg ++; forsøgCache.put (nøgle, forsøg); } offentlig boolsk isBlocked (strengnøgle) {returforsøgCache.getUnchecked (nøgle)> = MAX_ATTEMPT; }}

4.2. Refactoring af valideringstjenesten

Cachen inkorporeres først ved at afbryde, hvis klienten har overskredet forsøgsgrænsen. Ellers ved behandling af en mislykket GoogleResponse vi registrerer de forsøg, der indeholder en fejl med klientens svar. Vellykket validering rydder forsøgets cache:

offentlig klasse CaptchaService implementerer ICaptchaService {@Autowired private ReCaptchaAttemptService reCaptchaAttemptService; ... @ Overstyr offentlig ugyldig procesResponse (strengrespons) {... hvis (reCaptchaAttemptService.isBlocked (getClientIP ())) {kast nyt InvalidReCaptchaException ("Klienten overskred det maksimale antal mislykkede forsøg"); } ... GoogleResponse googleResponse = ... hvis (! GoogleResponse.isSuccess ()) {hvis (googleResponse.hasClientError ()) {reCaptchaAttemptService.reCaptchaFailed (getClientIP ()); } smid ny ReCaptchaInvalidException ("reCaptcha blev ikke valideret med succes"); } reCaptchaAttemptService.reCaptchaSucceeded (getClientIP ()); }}

5. Integrering af Googles reCAPTCHA v3

Googles reCAPTCHA v3 adskiller sig fra de tidligere versioner, fordi det ikke kræver nogen brugerinteraktion. Det giver simpelthen en score for hver anmodning, vi sender, og lader os beslutte, hvilke endelige handlinger vi skal tage for vores webapplikation.

Igen, for at integrere Googles reCAPTCHA 3 skal vi først registrere vores websted med tjenesten, føje deres bibliotek til vores side og derefter kontrollere token-svaret med webservicen.

Så lad os registrere vores websted på //www.google.com/recaptcha/admin/create og efter at have valgt reCAPTCHA v3, får vi de nye hemmelige og stednøgler.

5.1. Opdaterer application.properties og Captcha Indstillinger

Efter registreringen skal vi opdatere application.properties med de nye taster og vores valgte tærskelværdi:

google.recaptcha.key.site = 6LefKOAUAAAAAE ... google.recaptcha.key.secret = 6LefKOAUAAAA ... google.recaptcha.key.threshold = 0.5

Det er vigtigt at bemærke, at tærsklen er indstillet til 0.5 er en standardværdi og kan indstilles over tid ved at analysere de reelle tærskelværdier i Googles admin-konsol.

Lad os derefter opdatere vores Captcha Indstillinger klasse:

@Component @ConfigurationProperties (præfiks = "google.recaptcha.key") offentlig klasse CaptchaSettings {// ... andre egenskaber privat float tærskel; // standard getters og setter}

5.2. Front-End integration

Vi ændrer nu registrering.html at inkludere Googles bibliotek med vores webstedsnøgle.

Inde i vores registreringsformular tilføjer vi et skjult felt, der gemmer svaretoken modtaget fra opkaldet til grecaptcha.execute fungere:

   ... ... ... ... ... ... var siteKey = /* [[${@captchaService.getReCaptchaSite()}]] **; grecaptcha.execute (siteKey, {action: /* Px [${T(com.baeldung.captcha.CaptchaService).REGISTER_ACTION}] ]*/}).then(function(response) {$ ('# response'). val (respons); var formData = $ ('form'). serialize ();

5.3. Validering af serversiden

Vi bliver nødt til at foretage den samme server-side-anmodning set i reCAPTCHA-validering af serversiden for at validere svarstokenet med webtjenestens API.

Svaret JSON-objektet indeholder yderligere to egenskaber:

{... "score": antal, "handling": streng}

Scoren er baseret på brugerens interaktioner og er en værdi mellem 0 (meget sandsynligt en bot) og 1.0 (meget sandsynligt et menneske).

Handling er et nyt koncept, som Google introducerede, så vi kan udføre mange reCAPTCHA-anmodninger på den samme webside.

En handling skal angives, hver gang vi udfører reCAPTCHA v3. Og vi skal kontrollere, at værdien af handling egenskab i svaret svarer til det forventede navn.

5.4. Hent svaretoken

ReCAPTCHA v3-svar-token hentes fra respons anmodningsparameter ved hjælp af HttpServletRequest og valideret med vores CaptchaService. Mekanismen er identisk med den, der ses ovenfor i reCAPTCHA:

offentlig klasse RegistrationController {@Autowired privat ICaptchaService captchaService; ... @RequestMapping (værdi = "/ bruger / registrering", metode = RequestMethod.POST) @ResponseBody offentlig GenericResponse registerUserAccount (@Valid UserDto accountDto, HttpServletRequest anmodning) {String response = request.getParameter ("respons"); captchaService.processResponse (svar, CaptchaService.REGISTER_ACTION); // resten af ​​implementeringen} ...}

5.5. Refactoring af valideringstjenesten med v3

Den refaktoriserede CaptchaService valideringstjenesteklasse indeholder en processResponse metode analog til processResponse metode til den tidligere version, men det er omhyggeligt med at kontrollere handling og score parametre for GoogleResponse:

public class CaptchaService implementerer ICaptchaService {public static final String REGISTER_ACTION = "register"; ... @ Overstyr offentlig ugyldig procesResponse (strengrespons, strenghandling) {... GoogleResponse googleResponse = restTemplate.getForObject (verificere, GoogleResponse.class); hvis (! googleResponse.isSuccess () ||! googleResponse.getAction (). er lig med (handling) || googleResponse.getScore () <captchaSettings.getThreshold ()) {... smid ny ReCaptchaInvalidException ("reCaptcha blev ikke valideret korrekt") ); } reCaptchaAttemptService.reCaptchaSucceeded (getClientIP ()); }}

Hvis validering mislykkes, kaster vi en undtagelse, men bemærk at der med v3 er der ingen Nulstil metode til at påberåbe sig JavaScript-klienten.

Vi har stadig den samme implementering set ovenfor til beskyttelse af serverressourcer.

5.6. Opdatering af GoogleResponse Klasse

Vi er nødt til at tilføje de nye egenskaber score og handling til GoogleResponse Java bønne:

@JsonPropertyOrder ({"success", "score", "action", "challenge_ts", "hostname", "error-codes"}) offentlig klasse GoogleResponse {// ... andre egenskaber @JsonProperty ("score") privat float score; @JsonProperty ("handling") privat strenghandling; // standard getters og setter}

6. Konklusion

I denne artikel integrerede vi Googles reCAPTCHA-bibliotek i vores registreringsside og implementerede en tjeneste til at verificere captcha-svaret med en anmodning på serversiden.

Senere opgraderede vi registreringssiden med Googles reCAPTCHA v3-bibliotek og så, at registreringsformularen blev slankere, fordi brugeren ikke længere behøver at foretage sig noget.

Den fulde implementering af denne vejledning er tilgængelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found