Angular 6 + Spring Boot + reCAPTCHA w przesyłanym formularzu
Po pierwsze należy wygenerować indywidulany dla domeny (może być też localhost dla testów lokalnych) sitekey i secret –> Google reCAPTCHA
Integracja po stronie klienta
W formularzu dodajemy kod reCAPTCHA z wygenerowanym sitekey
<div class="form-group"> <div class="g-recaptcha" data-sitekey="6Lf7anUUAAAAAHobMeDJkbR_xxxasasasa"></div> </div>
Następnie w komponencie z formularzem w metodzie ngOnInit() dodajemy do strony skrypt z api reCAPTCHA. Jeśli skrypt zostałby dodany do index.html, to reCAPTCHA pojawiałaby się tylko po odświeżeniu strony.
ngOnInit(){ this.addScript(); } addScript() { let script = document.createElement('script'); script.src = 'https://www.google.com/recaptcha/api.js'; script.async = true; script.defer = true; document.body.appendChild(script); }
Przed wysłaniem formularza należy sprawdzić czy pole “Nie jestem robotem” zostało zaznaczone.
onSubmit() { this.submitted = true; const response = grecaptcha.getResponse(); if (response.length === 0) { alert('Recaptcha not verified.'); return; } this.save(); } save() { this.accountService.createNewUser(this.registerForm.value, grecaptcha.getResponse()) .subscribe( data => { console.log(data); }, error => { console.log(error); }) }
Integracja po stronie serwera – tutaj przyda się przydzielony wcześniej kod secret
@PostMapping("/new") @ResponseStatus(HttpStatus.CREATED) public void createUser(@RequestBody UserDTO user, @RequestParam(name="response") String recaptchaResponse) { captchaVerification.verify(recaptchaResponse); userRepository.findByEmailIgnoreCase(user.getEmail()) .ifPresent(u -> {throw new ResourceExistsException("User with login: "+user.getEmail()+" exist");}); User createdUser = userService.createUser(user); }
Przed dodaniem nowego użytkownika do bazy danych należy sprawdzić poprawność recaptchaResponse, wysyłając zapytanie przez recaptcha api.
import java.net.URI; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service("captchaVerification") public class CaptchaVerification { @Autowired private CaptchaSettings captchaSettings; public void verify(String response) { URI verifyUri = URI.create(String.format( "https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s", captchaSettings.getSecret(), response)); RestTemplate restTemplate = new RestTemplate(); GoogleResponse googleResponse = restTemplate.getForObject(verifyUri, GoogleResponse.class); if(!googleResponse.isSuccess()) { throw new InvalidRecaptchaException(); } } }
Sitekey i Secret umieszczone są w application.properties
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "google.recaptcha.key") public class CaptchaSettings { private String site; private String secret; public String getSite() { return site; } public void setSite(String site) { this.site = site; } public String getSecret() { return secret; } public void setSecret(String secret) { this.secret = secret; } }
Klasa GoogleResponse jest schematem reprezentującym odpowiedź przesłaną od Google Api i pomaga stwierdzić poprawność walidacji reCAPRTCHA.
import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) @JsonPropertyOrder({ "success", "challenge_ts", "hostname", "error-codes" }) public class GoogleResponse { @JsonProperty("success") private boolean success; @JsonProperty("challenge_ts") private String challengeTs; @JsonProperty("hostname") private String hostname; @JsonProperty("error-codes") private ErrorCode[] errorCodes; @JsonIgnore public boolean hasClientError() { ErrorCode[] errors = getErrorCodes(); if(errors == null) { return false; } for(ErrorCode error : errors) { switch(error) { case InvalidResponse: case MissingResponse: return true; } } return false; } static enum ErrorCode { MissingSecret, InvalidSecret, MissingResponse, InvalidResponse; private static Map<String, ErrorCode> errorsMap = new HashMap<String, ErrorCode>(4); static { errorsMap.put("missing-input-secret", MissingSecret); errorsMap.put("invalid-input-secret", InvalidSecret); errorsMap.put("missing-input-response", MissingResponse); errorsMap.put("invalid-input-response", InvalidResponse); } @JsonCreator public static ErrorCode forValue(String value) { return errorsMap.get(value.toLowerCase()); } } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getChallengeTs() { return challengeTs; } public void setChallengeTs(String challengeTs) { this.challengeTs = challengeTs; } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } public ErrorCode[] getErrorCodes() { return errorCodes; } public void setErrorCodes(ErrorCode[] errorCodes) { this.errorCodes = errorCodes; } }
Powyższe fragmenty kodu są też zawarte w projekcie Clinic (clinic-server, clinic-client) na GitHub >>tutaj<<