Рубрики
Без рубрики

Регистрация на Spring – интеграция reCAPTCHA

Наконец, доступна реализация всех этих примеров и фрагментов кода ||

Автор оригинала: 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 Map errorsMap = 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 LoadingCache attemptsCache;

    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 в ключ нашего сайта.






...





    ...

    
... ...
...