Автор оригинала: Cristian Rosu.
Вы можете найти код на Github: https://github.com/cristirosu/spring-boot-recaptcha-aop
Что такое recaptcha?
ReCAPTCHA – это бесплатный сервис, который защищает ваш сайт от спама и злоупотребления. Он использует передовые методы анализа рисков, чтобы рассказать людям и ботам.
Давайте посмотрим на следующий сценарий:
У вас есть интернет-магазин, где пользователи могут создавать учетную запись, заполнив их базовую информацию (имя, электронную почту и телефон). После этого шага они получит SMS, чтобы подтвердить свой номер телефона, а затем перейти к главной странице.
Проблема
Процесс создания отправляет SMS для проверки пользователя. Если ваш API не хватает защиты от спама, любой может просто злоупотреблять его и с тех пор, как вы (возможно) используете платный поставщик SMS, он может заставить вас заплатить большие счета для вашего провайдера или просто дросселировать свою SMS-систему, чтобы никто другой не мог зарегистрироваться.
Решение
Просто используйте recaptcha! Эта услуга попросит пользователю доказать, что он не робот, завершив несколько простых задач для человека (но тяжело для роботов), таких как визуальные (определение определенных аспектов в картинках) или аудио (распознавание текста речи).
Получение ваших ключей API
Чтобы использовать эту услугу, вам понадобится учетная запись Google, а затем просто перейдите к: https://www.google.com/recaptcha/admin#List, выберите recaptcha v2, установите флажок «Я не робот» Введите домен вашего сайта (ы) вашего сайта, вы сможете увидеть ваши 2 ключей: ключ клиента и ключ сервера (вы должны сохранить этот частный).
Серверная сторона с весенним загрузкой
Мы будем использовать следующие зависимости Maven:
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop org.webjars jquery 3.3.1
Давайте создадим следующую конечную точку отдыха (пост): http://localhost: 8080/hello, который получит запрос с именем пользователя и будет любезно приветствовать его.
@RestController @RequestMapping("/hello") public class HelloController { @PostMapping public String hello(@RequestBody HelloDTO helloDTO){ return new HelloResponseDTO("Hello, " + helloDTO.getName() + "!"); } }
Давайте проверим это!
curl -d '{"name":"Chris"}' -H "Content-Type: application/json" -X POST http://localhost:8080/hello
Мы получаем следующий ответ: {«Сообщение»: «Привет, Крис!»}
Добавление защиты reCAPTCHA в нашем файле «Application.Properties». Мы храним свой ключ от сервера:
google.recaptcha.secret=SERVER_KEY
Давайте создадим Captchavalidator:
@Service public class CaptchaValidator { private static final String GOOGLE_RECAPTCHA_ENDPOINT = "https://www.google.com/recaptcha/api/siteverify"; @Value("${google.recaptcha.secret}") private String recaptchaSecret; public boolean validateCaptcha(String captchaResponse){ RestTemplate restTemplate = new RestTemplate(); MultiValueMaprequestMap = new LinkedMultiValueMap<>(); requestMap.add("secret", recaptchaSecret); requestMap.add("response", captchaResponse); CaptchaResponse apiResponse = restTemplate.postForObject(GOOGLE_RECAPTCHA_ENDPOINT, requestMap, CaptchaResponse.class); if(apiResponse == null){ return false; } return Boolean.TRUE.equals(apiResponse.getSuccess()); } }
Это ответ API (GetTers и Getters, опущенные для ясности):
public class CaptchaResponse { private Boolean success; private Date timestamp; private String hostname; @JsonProperty("error-codes") private ListerrorCodes; }
В приведенном выше примере кода мы могли видеть, что мы впрыскиваем наш частный ключ сервера в CAPTCHAVALIALIALTATOR, затем мы отправляем запрос на почту на API Google REST с нашим секретным и клиентским ответом CAPTCHA.
Мы получаем ответ от Google, содержащего поле (среди других), называемых успехом, который мы можем использовать для проверки, если ответный ответ CAPTCHA действителен. Если недействительно, мы можем посмотреть на ErrorCodes, чтобы увидеть, почему.
Изменение оригинальной конечной точки, чтобы добавить логику CAPTCHAVALIALDATOR, добавив CAPTCHALESPONSE на нашу тело запроса:
public class HelloDTO { private String name; private String captchaResponse; }
@PostMapping public String hello(@RequestBody HelloDTO helloDTO){ Boolean isValidCaptcha = captchaService.validateCaptcha(helloDTO.getCaptchaResponse()); if(!isValidCaptcha){ throw new ForbiddenException("Captcha is not valid"); } return new HelloResponseDTO("Hello, " +helloDTO.getName() +"!"); }
Мы больше не можем проверить его с завитым, так как он требует проверки CAPTCHA сейчас, поэтому нам нужно кодировать клиент, чтобы он снова работал.
Сторона клиента с jquery
Spring Boot reCAPTCHA with AOP
Я выбрал jQuery, потому что я могу предоставить 1 файл-файл, но есть библиотеки для Angularjs, угловые 2+, реагируют и даже если вы не можете найти один для вашей библиотеки Frontend, вы можете просто использовать простой JavaScript.
Приведенный выше код приводит к этой простой странице, которая вызывает конечную точку Backenc Hello с именем и CAPTCHERESPONSOND в качестве тела запроса:
Если мы завершим имя, отметьте флажок «Я не робот», и нажмите на кнопку, мы получим приветствие!
Мы только что закрепили нашу конечную точку отдыха с помощью recaptcha, это круто!
Одна проблема, хотя …
Если бы мы хотели добавить проверку recaptcha на любую другую из наших конечных точек отдыха, нам придется следовать следующим шагам:
Включите поле в нашу тело запроса, чтобы получить Captcharesponse Добавить логику в нашем контроллере или услуге для проверки CAPTCHA, кажется довольно простой, но это довольно повторяется. А как насчет получения запросов? Мы не можем иметь тело на тех …
AOP к спасению!
AOP (Aspect ориентированное программирование) позволяет нам запустить какой-то код до (или после) одного из наших методов.
Мы собираемся внести следующие изменения в нашем коде:
Переместите клиент CAPTCHERESSESCOND из корпуса запроса на заголовок HTTP (чтобы быть более универсальным, а также иметь возможность включать его для запросов, которые не могут иметь тело), реализуйте аннотацию AOP, чтобы исключить код пластины котла и добиться гораздо более чистого кода Отказ
1. Перемещение капницы на заголовок
Мы изменяем клиента, чтобы отправить CAPTCHALESPONSE в заголовке вместо тела:
2. Создайте аннотацию AOP, чтобы справиться с Capthca более изящно
а. Создать аннотацию
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresCaptcha { }
б. Создайте аспект
@Aspect @Component public class CaptchaAspect { @Autowired private CaptchaValidator captchaValidator; private static final String CAPTCHA_HEADER_NAME = "captcha-response"; @Around("@annotation(RequiresCaptcha)") public Object validateCaptcha(ProceedingJoinPoint joinPoint) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String captchaResponse = request.getHeader(CAPTCHA_HEADER_NAME); boolean isValidCaptcha = captchaValidator.validateCaptcha(captchaResponse); if(!isValidCaptcha){ throw new ForbiddenException("Invalid captcha"); } return joinPoint.proceed(); } }
На основании вышеуказанного кода мы можем видеть, что логика валидации CAPTCHA сейчас находится в нашем аспекте.
Некоторые объяснения:
@Around("@annotation(RequiresCaptcha)")
Это говорит весна, чтобы запустить наш код до того, как называется метод, аннотированный с «@REQUIRESCAPTCHA».
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String captchaResponse = request.getHeader(CAPTCHA_HEADER_NAME); boolean isValidCaptcha = captchaValidator.validateCaptcha(captchaResponse); if(!isValidCaptcha){ throw new ForbiddenException("Invalid captcha"); }
Этот код извлекает заголовок CAPTCHA из текущего httpservletrequest и отправляет его на валидатор для проверки. Если все в порядке, следующая строка будет выполняться:
return joinPoint.proceed();
Это позвонит оригинальный метод, который вернет приветствие клиенту. Если CAPTCHA недействителен, исключение будет повышено.
Вернуться к нашему контроллеру
@PostMapping @RequiresCaptcha public HelloResponseDTO hello(@RequestBody HelloDTO helloDTO){ return new HelloResponseDTO("Hello, " + helloDTO.getName() + "!"); }
Как мы видим, мы удалили код котла пластины для проверки для проверки CAPTCHA и вместо этого мы добавили аннотацию «@requirescaptcha».
Теперь мы можем использовать эту аннотацию всякий раз, когда мы хотим добавить проверку CAPTCHA на один из наших конечных точек отдыха, который отлично!
Вы можете увидеть полный код: https://github.com/cristirosu/spring-boot-recaptcha-aop