Автор оригинала: Eugen Paraschiv.
1. Обзор
В этом уроке мы продолжим серию Spring Security Registration , добавив Google /reCAPTCHA в процесс регистрации, чтобы отличить человека от ботов.
2. Интеграция рекапчи Google
Чтобы интегрировать веб-сервис Google reCAPTCHA, нам сначала нужно зарегистрировать наш сайт в сервисе, добавить их библиотеку на нашу страницу, а затем проверить ответ пользователя на капчу с помощью веб-сервиса.
Давайте зарегистрируем наш сайт по адресу https://www.google.com/recaptcha/admin . В процессе регистрации генерируется ключ сайта и секретный ключ для доступа к веб-сервису.
2.1. Хранение пары ключей API
2.1. Хранение пары ключей API
google.recaptcha.key.site=6LfaHiITAAAA... google.recaptcha.key.secret=6LfaHiITAAAA...
2.1. Хранение пары ключей API
@Component @ConfigurationProperties(prefix = "google.recaptcha.key") public class CaptchaSettings { private String site; private String secret; // standard getters and setters }
2.2. Отображение виджета
2.2. Отображение виджета
2.2. Отображение виджета
2.2. Отображение виджета
... ...
3. Проверка на стороне Сервера
Новый параметр запроса кодирует ключ нашего сайта и уникальную строку, идентифицирующую успешное выполнение пользователем задачи.
Новый параметр запроса кодирует ключ нашего сайта и уникальную строку, идентифицирующую успешное выполнение пользователем задачи.
Новый параметр запроса кодирует ключ нашего сайта и уникальную строку, идентифицирующую успешное выполнение пользователем задачи.
{ "success": true|false, "challenge_ts": timestamp, "hostname": string, "error-codes": [ ... ] }
Новый параметр запроса кодирует ключ нашего сайта и уникальную строку, идентифицирующую успешное выполнение пользователем задачи.
Новый параметр запроса кодирует ключ нашего сайта и уникальную строку, идентифицирующую успешное выполнение пользователем задачи.
public class RegistrationController { @Autowired private ICaptchaService captchaService; ... @RequestMapping(value = "/user/registration", method = RequestMethod.POST) @ResponseBody public GenericResponse registerUserAccount(@Valid UserDto accountDto, HttpServletRequest request) { String response = request.getParameter("g-recaptcha-response"); captchaService.processResponse(response); // Rest of implementation } ... }
3.2. Служба валидации
Полученный ответ на капчу должен быть сначала очищен. Используется простое регулярное выражение.
Если ответ выглядит законным, мы затем делаем запрос к веб-сервису с секретным ключом , ответом captcha и IP-адресом клиента :
public class CaptchaService implements ICaptchaService { @Autowired private CaptchaSettings captchaSettings; @Autowired private RestOperations restTemplate; private static Pattern RESPONSE_PATTERN = Pattern.compile("[A-Za-z0-9_-]+"); @Override public void processResponse(String response) { if(!responseSanityCheck(response)) { throw new InvalidReCaptchaException("Response contains invalid characters"); } URI verifyUri = URI.create(String.format( "https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s", getReCaptchaSecret(), response, getClientIP())); GoogleResponse googleResponse = restTemplate.getForObject(verifyUri, GoogleResponse.class); if(!googleResponse.isSuccess()) { throw new ReCaptchaInvalidException("reCaptcha was not successfully validated"); } } private boolean responseSanityCheck(String response) { return StringUtils.hasLength(response) && RESPONSE_PATTERN.matcher(response).matches(); } }
Если ответ выглядит законным, мы затем делаем запрос к веб-сервису с || секретным ключом || , ответом || captcha || и || IP-адресом клиента ||:
Java-боб, украшенный аннотациями Jackson , инкапсулирует ответ проверки:
@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 MaperrorsMap = new HashMap (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()); } } // standard getters and setters }
Как подразумевается, значение истинности в свойстве success означает, что пользователь был проверен. В противном случае свойство Коды ошибок будет заполнено причиной.
имя хоста относится к серверу, на который перенаправлен пользователь
|| имя хоста || относится к серверу, на который перенаправлен пользователь
В случае сбоя проверки создается исключение. Библиотека reCAPTCHA должна дать клиенту указание создать новую задачу.
Мы делаем это в обработчике ошибок регистрации клиента, вызывая сброс в виджете библиотеки grecaptcha :
register(event){ event.preventDefault(); var formData= $('form').serialize(); $.post(serverContext + "user/registration", formData, function(data){ if(data.message == "success") { // success handler } }) .fail(function(data) { grecaptcha.reset(); ... if(data.responseJSON.error == "InvalidReCaptcha"){ $("#captchaError").show().html(data.responseJSON.message); } ... } }
4. Защита ресурсов Сервера
Вредоносные клиенты не должны подчиняться правилам песочницы браузера. Таким образом, наше мышление в области безопасности должно быть сосредоточено на открытых ресурсах и на том, как они
Вредоносные клиенты не должны подчиняться правилам песочницы браузера. Таким образом, наше мышление в области безопасности должно быть сосредоточено на открытых ресурсах и на том, как они
Важно понимать, что при интеграции reCAPTCHA каждый сделанный запрос приведет к тому, что сервер создаст сокет для проверки запроса.
В то время как нам нужен более многоуровневый подход для истинного смягчения DoS, мы можем реализовать элементарный кэш, который ограничивает клиента 4 неудачными ответами на капчу:
public class ReCaptchaAttemptService { private int MAX_ATTEMPT = 4; private LoadingCacheattemptsCache; public ReCaptchaAttemptService() { super(); attemptsCache = CacheBuilder.newBuilder() .expireAfterWrite(4, TimeUnit.HOURS).build(new CacheLoader () { @Override public Integer load(String key) { return 0; } }); } public void reCaptchaSucceeded(String key) { attemptsCache.invalidate(key); } public void reCaptchaFailed(String key) { int attempts = attemptsCache.getUnchecked(key); attempts++; attemptsCache.put(key, attempts); } public boolean isBlocked(String key) { return attemptsCache.getUnchecked(key) >= MAX_ATTEMPT; } }
4.2. Рефакторинг Службы валидации
Кэш сначала включается путем прерывания, если клиент превысил лимит попыток. В противном случае при обработке неудачного GoogleResponse мы записываем попытки, содержащие ошибку, с ответом клиента. Успешная проверка очищает кэш попыток:
public class CaptchaService implements ICaptchaService { @Autowired private ReCaptchaAttemptService reCaptchaAttemptService; ... @Override public void processResponse(String response) { ... if(reCaptchaAttemptService.isBlocked(getClientIP())) { throw new InvalidReCaptchaException("Client exceeded maximum number of failed attempts"); } ... GoogleResponse googleResponse = ... if(!googleResponse.isSuccess()) { if(googleResponse.hasClientError()) { reCaptchaAttemptService.reCaptchaFailed(getClientIP()); } throw new ReCaptchaInvalidException("reCaptcha was not successfully validated"); } reCaptchaAttemptService.reCaptchaSucceeded(getClientIP()); } }
5. Интеграция Google reCAPTCHA v3
reCAPTCHA v3 от Google отличается от предыдущих версий тем, что не требует какого-либо взаимодействия с пользователем. Он просто дает оценку каждому запросу, который мы отправляем, и позволяет нам решить, какие окончательные действия предпринять для нашего веб-приложения.
Опять же, чтобы интегрировать reCAPTCHA 3 от Google, нам сначала нужно зарегистрировать наш сайт
Опять же, чтобы интегрировать reCAPTCHA 3 от Google, нам сначала нужно зарегистрировать наш сайт
5.1. Обновление приложения.свойства и CaptchaSettings
После регистрации нам нужно обновить application.properties новыми ключами и выбранным нами пороговым значением оценки:
google.recaptcha.key.site=6LefKOAUAAAAAE... google.recaptcha.key.secret=6LefKOAUAAAA... google.recaptcha.key.threshold=0.5
Важно отметить, что пороговое значение равно 0.5 это значение по умолчанию, и его можно настроить с течением времени, проанализировав реальные пороговые значения в консоли администратора Google .
Важно отметить, что пороговое значение равно 0.5 это значение по умолчанию, и его можно настроить с течением времени, проанализировав реальные пороговые значения в
@Component @ConfigurationProperties(prefix = "google.recaptcha.key") public class CaptchaSettings { // ... other properties private float threshold; // standard getters and setters }
5.2. Фронтальная интеграция
Теперь мы изменим registration.html включить библиотеку Google в ключ нашего сайта.
Теперь мы изменим registration.html включить библиотеку Google в ключ нашего сайта.
... ......