Registrering med Spring - Integrer reCAPTCHA

1. Oversikt

I denne opplæringen fortsetter vi vårsikkerhetsregistreringsserien ved å legge til GooglereCAPTCHA til registreringsprosessen for å skille menneske fra robot.

2. Integrering av Googles reCAPTCHA

For å integrere Googles reCAPTCHA-nettjeneste, må vi først registrere nettstedet vårt med tjenesten, legge til biblioteket på siden vår, og deretter bekrefte brukerens captcha-respons med webtjenesten.

La oss registrere nettstedet vårt på //www.google.com/recaptcha/admin. Registreringsprosessen genererer en nettstedsnøkkel og hemmelig nøkkel for tilgang til nettjenesten.

2.1. Lagring av API-nøkkelpar

Vi lagrer nøklene i application.properties:

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

Og utsett dem for våren ved hjelp av en bønne som er merket med @ConfigurationProperties:

@Component @ConfigurationProperties (prefix = "google.recaptcha.key") offentlig klasse CaptchaSettings {private String-nettsteder; privat streng hemmelighet; // standard getters og setters}

2.2. Viser widgeten

Basert på veiledningen fra serien, endrer vi nå registrering.html for å inkludere Googles bibliotek.

Inne i registreringsskjemaet vårt legger vi til reCAPTCHA-widgeten som forventer attributtet data-sitekey å inneholde nettstedsnøkkel.

Modulen legges til forespørselsparameteren g-recaptcha-respons når de sendes inn:

   ...    ...  ... 

3. Validering av serversiden

Den nye forespørselsparameteren koder nettstedsnøkkelen vår og en unik streng som identifiserer brukerens vellykkede gjennomføring av utfordringen.

Men siden vi ikke kan skjelne det selv, kan vi ikke stole på det brukeren har sendt inn er legitimt. Det blir gjort en forespørsel på serversiden for å validere captcha-respons med webtjenestens API.

Endepunktet godtar en HTTP-forespørsel på URL //www.google.com/recaptcha/api/siteverify, med søkeparametrene hemmelig, respons, og fjernkontroll. Det returnerer et json-svar som har skjemaet:

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

3.1. Hent brukerens svar

Brukerens svar på reCAPTCHA-utfordringen hentes fra forespørselsparameteren g-recaptcha-respons ved hjelp av HttpServletRequest og validert med vår CaptchaService. Ethvert unntak som blir kastet mens du behandler svaret, avbryter resten av registreringslogikken:

offentlig klasse RegistrationController {@Autowired privat ICaptchaService captchaService; ... @RequestMapping (verdi = "/ bruker / registrering", metode = RequestMethod.POST) @ResponseBody offentlig GenericResponse registerUserAccount (@Valid UserDto accountDto, HttpServletRequest request) {String response = request.getParameter ("g-recaptcha-response" ); captchaService.processResponse (respons); // Resten av implementeringen} ...}

3.2. Valideringstjeneste

Captcha-responsen som oppnås, bør rengjøres først. Det brukes et enkelt regulært uttrykk.

Hvis svaret ser ut som legitimt, ber vi deretter om en forespørsel til nettjenesten med hemmelig nøkkel, den captcha-respons, og klientens IP adresse:

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

3.3. Objektivisere validering

En Java-bønne dekorert med Jackson merknader innkapsler valideringssvaret:

@JsonInclude (JsonInclude.Include.NON_NULL) @JsonIgnoreProperties (ignoreUnknown = true) @JsonPropertyOrder ({"suksess", "utfordring_ts", "vertsnavn", "feilkoder"}) offentlig klasse GoogleResponse {@JsonProperty ("suksess") privat boolsk suksess; @JsonProperty ("challenge_ts") private strengutfordringer; @JsonProperty ("vertsnavn") privat streng vertsnavn; @JsonProperty ("error-codes") private ErrorCode [] errorCodes; @JsonIgnorer offentlig boolsk hasClientError () {ErrorCode [] feil = getErrorCodes (); hvis (feil == null) {return false; } for (ErrorCode error: feil) {switch (error) {case InvalidResponse: case MissingResponse: return true; }} returner falsk; } statisk enum ErrorCode {MissingSecret, InvalidSecret, MissingResponse, InvalidResponse; private statiske kartfeilMap = nye HashMap (4); statisk {errorMap.put ("manglende inngangshemmelig", MissingSecret); errorsMap.put ("ugyldig-input-hemmelig", InvalidSecret); errorMap.put ("mangler-input-respons", MissingResponse); errorsMap.put ("ugyldig-input-respons", InvalidResponse); } @JsonCreator offentlig statisk ErrorCode forValue (strengverdi) {return errorMap.get (value.toLowerCase ()); }} // standard getters og setters}

Som antydet, en sannhetsverdi i suksess egenskap betyr at brukeren er validert. Ellers den errorCodes eiendommen vil fylle med årsaken.

De vertsnavn refererer til serveren som omdirigerte brukeren til reCAPTCHA. Hvis du administrerer mange domener og ønsker at alle skal dele samme nøkkelpar, kan du velge å bekrefte vertsnavn eiendommen selv.

3.4. Valideringsfeil

I tilfelle validering mislykkes, kastes et unntak. ReCAPTCHA-biblioteket må instruere klienten om å skape en ny utfordring.

Vi gjør det i klientens registreringsfeilbehandler ved å påkalle tilbakestilling av biblioteket grecaptcha widget:

register (event) {event.preventDefault (); var formData = $ ('form'). serialize (); $ .post (serverContext + "bruker / registrering", formData, funksjon (data) {hvis (data.message == "suksess") {// suksessbehandling}}). mislykkedes (funksjon (data) {grecaptcha.reset ( ); ... hvis (data.responseJSON.error == "InvalidReCaptcha") {$ ("# captchaError"). viser (). html (data.responseJSON.message);} ...}}

4. Beskytte serverressurser

Ondsinnede klienter trenger ikke å overholde reglene i nettleserens sandkasse. Så vårt sikkerhetsinnstilling bør være på ressursene som er utsatt og hvordan de kan bli misbrukt.

4.1. Forsøk på hurtigbuffer

Det er viktig å forstå at ved å integrere reCAPTCHA, vil hver forespørsel som gjøres føre til at serveren oppretter en stikkontakt for å validere forespørselen.

Mens vi trenger en mer lagdelt tilnærming for en ekte DoS-avbøting, kan vi implementere en elementær cache som begrenser en klient til 4 mislykkede captcha-svar:

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

4.2. Refactoring valideringstjenesten

Cachen innlemmes først ved å avbryte hvis klienten har overskredet forsøksgrensen. Ellers når du behandler et mislykket GoogleResponse vi registrerer forsøkene som inneholder en feil med klientens svar. Vellykket validering tømmer hurtigbufferen for forsøk:

offentlig klasse CaptchaService implementerer ICaptchaService {@Autowired private ReCaptchaAttemptService reCaptchaAttemptService; ... @Override public void processResponse (String response) {... if (reCaptchaAttemptService.isBlocked (getClientIP ())) {throw new InvalidReCaptchaException ("Client oversteg maksimalt antall mislykkede forsøk"); } ... GoogleResponse googleResponse = ... hvis (! GoogleResponse.isSuccess ()) {if (googleResponse.hasClientError ()) {reCaptchaAttemptService.reCaptchaFailed (getClientIP ()); } kast nytt ReCaptchaInvalidException ("reCaptcha ble ikke vellykket validert"); } reCaptchaAttemptService.reCaptchaSucceeded (getClientIP ()); }}

5. Integrering av Googles reCAPTCHA v3

Googles reCAPTCHA v3 skiller seg fra de forrige versjonene fordi det ikke krever noen brukerinteraksjon. Det gir rett og slett en poengsum for hver forespørsel vi sender, og lar oss bestemme hvilke endelige tiltak vi skal ta for nettapplikasjonen.

Igjen, for å integrere Googles reCAPTCHA 3, må vi først registrere nettstedet vårt med tjenesten, legge til biblioteket deres på siden vår, og deretter bekrefte token-svaret med nettjenesten.

Så la oss registrere nettstedet vårt på //www.google.com/recaptcha/admin/create og etter å ha valgt reCAPTCHA v3, får vi de nye hemmelige og stednøklene.

5.1. Oppdaterer application.properties og CaptchaSettings

Etter registreringen må vi oppdatere application.properties med de nye tastene og den valgte poengsumverdien vår:

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

Det er viktig å merke seg at terskelen er satt til 0.5 er en standardverdi og kan stilles inn over tid ved å analysere de virkelige terskelverdiene i Googles administrasjonskonsoll.

Neste, la oss oppdatere vår CaptchaSettings klasse:

@Component @ConfigurationProperties (prefix = "google.recaptcha.key") offentlig klasse CaptchaSettings {// ... andre egenskaper privat flytterskel; // standard getters og setters}

5.2. Front-End-integrasjon

Vi endrer nå registrering.html å inkludere Googles bibliotek med nettstedsnøkkelen vår.

Inne i registreringsskjemaet vårt legger vi til et skjult felt som lagrer svaretoken mottatt fra samtalen til grecaptcha.execute funksjon:

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

5.3. Validering på serversiden

Vi blir nødt til å gjøre den samme forespørselen på serversiden i reCAPTCHA Validering på serversiden for å validere svarstokenet med webtjenestens API.

Svaret JSON-objektet vil inneholde to ekstra egenskaper:

{... "score": nummer, "action": streng}

Poengsummen er basert på brukerens interaksjoner og er en verdi mellom 0 (veldig sannsynlig en bot) og 1.0 (veldig sannsynlig et menneske).

Handling er et nytt konsept som Google introduserte slik at vi kan utføre mange reCAPTCHA-forespørsler på samme nettside.

En handling må spesifiseres hver gang vi kjører reCAPTCHA v3. Og vi må verifisere at verdien på handling eiendom i svaret tilsvarer forventet navn.

5.4. Hent svaretoken

ReCAPTCHA v3-svarstokenet blir hentet fra respons forespørselparameter ved hjelp av HttpServletRequest og validert med vår CaptchaService. Mekanismen er identisk med den som er sett ovenfor i reCAPTCHA:

offentlig klasse RegistrationController {@Autowired privat ICaptchaService captchaService; ... @RequestMapping (verdi = "/ bruker / registrering", metode = RequestMethod.POST) @ResponseBody offentlig GenericResponse registerUserAccount (@Valid UserDto accountDto, HttpServletRequest request) {String response = request.getParameter ("response"); captchaService.processResponse (svar, CaptchaService.REGISTER_ACTION); // resten av implementeringen} ...}

5.5. Refactoring valideringstjenesten med v3

Den ombygde CaptchaService valideringstjenesteklasse inneholder en processResponse metode analog med processResponse metoden til den forrige versjonen, men det tar seg å sjekke handling og score parametrene til GoogleResponse:

public class CaptchaService implementerer ICaptchaService {public static final String REGISTER_ACTION = "register"; ... @ Overstyr offentlig ugyldig prosessrespons (strengrespons, strenghandling) {... GoogleResponse googleResponse = restTemplate.getForObject (verifisereUri, GoogleResponse.class); hvis (! googleResponse.isSuccess () ||! googleResponse.getAction (). tilsvarer (handling) || googleResponse.getScore () <captchaSettings.getThreshold ()) {... kast ny ReCaptchaInvalidException ("reCaptcha ble ikke validert" ); } reCaptchaAttemptService.reCaptchaSucceeded (getClientIP ()); }}

I tilfelle validering mislykkes, kaster vi et unntak, men vær oppmerksom på at det med v3 ikke er noe nullstille metode for å påkalle JavaScript-klienten.

Vi vil fortsatt ha den samme implementeringen som vist ovenfor for å beskytte serverressurser.

5.6. Oppdaterer GoogleResponse Klasse

Vi må legge til de nye egenskapene score og handling til GoogleResponse Java bønne:

@JsonPropertyOrder ({"suksess", "score", "handling", "challenge_ts", "vertsnavn", "feilkoder"}) offentlig klasse GoogleResponse {// ... andre eiendommer @JsonProperty ("score") private float score; @JsonProperty ("handling") privat strenghandling; // standard getters og setters}

6. Konklusjon

I denne artikkelen integrerte vi Googles reCAPTCHA-bibliotek i registreringssiden vår og implementerte en tjeneste for å verifisere captcha-svaret med en forespørsel på serversiden.

Senere oppgraderte vi registreringssiden med Googles reCAPTCHA v3-bibliotek og så at registreringsskjemaet ble tynnere fordi brukeren ikke trenger å gjøre noe lenger.

Den fulle implementeringen av denne opplæringen er tilgjengelig på GitHub.


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