הרשמה באביב - שילוב reCAPTCHA

1. סקירה כללית

במדריך זה נמשיך את סדרת רישום האבטחה באביב על ידי הוספה גוגלreCAPTCHA לתהליך הרישום על מנת להבדיל בין אדם לבוטים.

2. שילוב ה- reCAPTCHA של גוגל

כדי לשלב את שירות האינטרנט reCAPTCHA של Google, ראשית עלינו לרשום את האתר שלנו בשירות, להוסיף את הספרייה שלהם לדף שלנו ואז לאמת את תגובת ה- captcha של המשתמש בשירות האינטרנט.

בוא נרשום את האתר שלנו בכתובת //www.google.com/recaptcha/admin. תהליך הרישום מייצר א מפתח האתר ו מפתח סודי לגישה לשירות האינטרנט.

2.1. אחסון צמד המפתחות של ה- API

אנו מאחסנים את המפתחות בתא application.properties:

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

וחשוף אותם לאביב באמצעות שעועית שמסומנת עם @ConfigurationProperties:

@Component @ConfigurationProperties (קידומת = "google.recaptcha.key") בכיתה ציבורית CaptchaSettings {אתר מחרוזת פרטי; סוד מחרוזת פרטי; // סטרים וקובעים סטנדרטיים}

2.2. הצגת היישומון

בהתבסס על המדריך מסדרה, כעת נשנה את registration.html לכלול את הספרייה של גוגל.

בתוך טופס ההרשמה שלנו, אנו מוסיפים את היישומון reCAPTCHA המצפה לתכונה sitekey נתונים להכיל את מפתח האתר.

היישומון יצורף פרמטר הבקשה תגובת g-recaptcha כאשר מוגש:

   ...    ...  ... 

3. אימות מצד השרת

פרמטר הבקשה החדש מקודד את מפתח האתר שלנו ומחרוזת ייחודית המזהה את השלמת האתגר המוצלחת של המשתמש.

עם זאת, מכיוון שאיננו יכולים להבחין בעצמנו, איננו יכולים לסמוך על מה שהמשתמש הגיש הוא לגיטימי. בקשה בצד השרת מתבצעת לאימות ה- תגובת captcha עם ה- API של שירות האינטרנט.

נקודת הקצה מקבלת בקשת HTTP בכתובת URL //www.google.com/recaptcha/api/siteverify, עם פרמטרי השאילתה סוֹד, תְגוּבָה, ו מרחוק. זה מחזיר תגובת json עם הסכימה:

false, "challenge_ts": חותמת זמן, "host host": string, "codes-error": [...] 

3.1. אחזר את תגובת המשתמש

תגובת המשתמש לאתגר reCAPTCHA מתקבלת מפרמטר הבקשה תגובת g-recaptcha באמצעות HttpServletRequest ותוקף באמצעות שלנו CaptchaService. כל חריג שנזרק במהלך עיבוד התגובה יבטל את שאר לוגיקת הרישום:

מחלקה ציבורית RegistrationController {@ אוטומטית 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 (תגובה); // שאר היישום} ...}

3.2. שירות אימות

יש לנקות תחילה את תגובת הקפטצ'ה. משתמשים בביטוי רגולרי פשוט.

אם התגובה נראית לגיטימית, אנו מגישים בקשה לשירות האינטרנט עם ה- מפתח סודי, ה תגובת captchaושל הלקוח כתובת ה - IP:

מחלקה ציבורית CaptchaService מיישמת את ICaptchaService {@ CaptchaSettings פרטיים מאוירים captchaSettings; @ RestOperations פרטיים אוטומטיים restTemplate; דפוס סטטי פרטי RESPONSE_PATTERN = דפוס.קומפילציה ("[A-Za-z0-9 _-] +"); @ ביטול הריק הציבורי processResponse (מחרוזת תגובה) {if (! ResponseSanityCheck (תגובה)) {זרוק InvalidReCaptchaException חדש ("התגובה מכילה תווים לא חוקיים"); } URI verifyUri = URI.create (String.format ("//www.google.com/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s", getReCaptchaSecret (), תגובה, getClientIP ())) ; GoogleResponse googleResponse = restTemplate.getForObject (VerifiedUri, GoogleResponse.class); אם (! googleResponse.isSuccess ()) {זרוק ReCaptchaInvalidException חדש ("reCaptcha לא אומת בהצלחה"); }} תגובה בוליאנית פרטיתSanityCheck (מחרוזת תגובה) {החזר StringUtils.hasLength (תגובה) && RESPONSE_PATTERN.matcher (תגובה) .matches (); }}

3.3. ייעוד לאימות

שעועית ג'אווה מעוטרת ב ג'קסון הערות עוטפות את תגובת האימות:

@JsonInclude (JsonInclude.Include.NON_NULL) @ JsonIgnoreProperties (ignoreUnknown = true) @ JsonPropertyOrder ({"success", "challenge_ts", "host host", "codes-error"}) public class GoogleResponse {@JsonProperty ("הצלחה") הצלחה בוליאנית פרטית; @JsonProperty ("challenge_ts") אתגרי מחרוזת פרטיים; @JsonProperty ("שם מארח") שם מארח מחרוזת פרטי; @JsonProperty ("קודי שגיאה") קוד שגיאה פרטי [] שגיאות; @Json התעלם מ- hasClientError () {ErrorCode [] שגיאות = getErrorCodes (); אם (שגיאות == null) {return false; } עבור (ErrorCode error: שגיאות) {switch (error) {case InvalidResponse: case MissingResponse: return true; }} להחזיר שקר; } enum ErrorCode enatic {MissingSecret, InvalidSecret, MissingResponse, InvalidResponse; שגיאות מפה סטטיות פרטיותMap = HashMap חדש (4); סטטי {errorMap.put ("חסר קלט-סוד", MissingSecret); errorsMap.put ("לא חוקי-קלט-סוד", InvalidSecret); errorsMap.put ("חסר-קלט-תגובה", MissingResponse); errorsMap.put ("תגובה לא חוקית-קלט", InvalidResponse); } @ JsonCreator שגיאת קוד סטטי ציבורית עבור ערך (ערך מחרוזת) {החזרת errorMap.get (value.toLowerCase ()); }} // סטרים וקובעים סטנדרטיים}

כמשתמע, ערך אמת ב הַצלָחָה מאפיין פירושו שהמשתמש אומת. אחרת ה errorCodes הנכס יאכלס את הסיבה.

ה שם מארח מתייחס לשרת שהפנה את המשתמש מחדש ל- reCAPTCHA. אם אתה מנהל תחומים רבים וברצונך שכולם ישתפו אותו צמד מפתחות, תוכל לבחור לאמת את שם מארח רכוש בעצמך.

3.4. כשל באימות

במקרה של כשל באימות, נזרק חריג. ספריית reCAPTCHA צריכה להורות ללקוח ליצור אתגר חדש.

אנו עושים זאת בטיפול בשגיאות הרישום של הלקוח, על ידי הפעלת איפוס לספרייה grecaptcha יישומון:

הרשמה (אירוע) {event.preventDefault (); var formData = $ ('טופס'). סידור (); $ .post (serverContext + "משתמש / רישום", formData, פונקציה (נתונים) {אם (data.message == "הצלחה") {// מטפל בהצלחה}}). fail (פונקציה (נתונים) {grecaptcha.reset ( ); ... אם (data.responseJSON.error == "InvalidReCaptcha") {$ ("# captchaError"). show (). html (data.responseJSON.message);} ...}}

4. הגנה על משאבי שרתים

לקוחות זדוניים אינם צריכים לציית לכללים של ארגז החול בדפדפן. לכן הלך הרוח הביטחוני שלנו צריך להיות במשאבים שנחשפו וכיצד הם עשויים להתעלל.

4.1. ניסיונות מטמון

חשוב להבין שעל ידי שילוב reCAPTCHA, כל בקשה שתעשה תגרום לשרת ליצור שקע לאימות הבקשה.

אמנם אנו זקוקים לגישה מרובדת יותר להפחתת DoS אמיתית, אך אנו יכולים ליישם מטמון אלמנטרי המגביל את הלקוח ל -4 תגובות captcha כושלות:

מחלקה ציבורית ReCaptchaAttemptService {private int MAX_ATTEMPT = 4; LoadingCache נסה מטמון פרטי; ReCaptchaAttemptService ציבורי () {סופר (); triedCache = CacheBuilder.newBuilder () .expireAfterWrite (4, TimeUnit.HOURS) .build (CacheLoader חדש () {@Override עומס שלם ציבורי (מפתח מחרוזת) {חזרה 0;}}); } בטל ציבורי reCaptchaSucceeded (מפתח מחרוזת) {probeCache.invalidate (מפתח); } חלל ציבורי reCaptchaFailed (מפתח מחרוזת) {int נסיונות = נסיונות Cache.getUnchecked (מפתח); ניסיונות ++; triedCache.put (מפתח, נסיונות); } בוליאני ציבורי isBlocked (מפתח מחרוזת) {החזרת נסיונות Cache.getUnchecked (מפתח)> = MAX_ATTEMPT; }}

4.2. שיקום מחדש של שירות האימות

המטמון משולב תחילה על ידי ביטול אם הלקוח חרג ממגבלת הניסיון. אחרת בעת עיבוד לא מוצלח תגובה של גוגל אנו מתעדים את הניסיונות המכילים שגיאה עם תגובת הלקוח. אימות מוצלח מנקה את מטמון הניסיונות:

מחלקה ציבורית CaptchaService מיישמת את ICaptchaService {@Autowired פרטי ReCaptchaAttemptService reCaptchaAttemptService; ... @ ביטול הריק הציבורי processResponse (תגובת מחרוזת) {... if (reCaptchaAttemptService.isBlocked (getClientIP ())) {זרוק InvalidReCaptchaException חדש ("הלקוח חרג ממספר הניסיונות הכושלים המרבי"); } ... GoogleResponse googleResponse = ... אם (! GoogleResponse.isSuccess ()) {if (googleResponse.hasClientError ()) {reCaptchaAttemptService.reCaptchaFailed (getClientIP ()); } השליך ReCaptchaInvalidException חדש ("reCaptcha לא אומת בהצלחה"); } reCaptchaAttemptService.reCaptchaSucceeded (getClientIP ()); }}

5. שילוב ה- reCAPTCHA v3 של גוגל

ה- reCAPTCHA v3 של גוגל שונה מהגרסאות הקודמות מכיוון שהוא אינו דורש שום אינטראקציה של המשתמש. זה פשוט נותן ציון לכל בקשה שאנו שולחים ומאפשר לנו להחליט אילו פעולות סופיות לבצע עבור יישום האינטרנט שלנו.

שוב, כדי לשלב את reCAPTCHA 3 של גוגל, ראשית עלינו לרשום את האתר שלנו בשירות, להוסיף את הספרייה שלהם לדף שלנו, ואז לאמת את תגובת האסימונים עם שירות האינטרנט.

אז בואו נרשום את האתר שלנו בכתובת //www.google.com/recaptcha/admin/create ולאחר בחירת reCAPTCHA v3, נקבל את המפתחות הסודיים ואתרים החדשים.

5.1. עִדכּוּן application.properties ו CaptchaSettings

לאחר ההרשמה עלינו לעדכן application.properties עם המקשים החדשים וערך סף הניקוד שבחרנו:

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

חשוב לציין כי הסף מוגדר 0.5 הוא ערך ברירת מחדל וניתן לכוון אותו לאורך זמן על ידי ניתוח ערכי הסף האמיתיים במסוף הניהול של גוגל.

לאחר מכן, בואו נעדכן את שלנו CaptchaSettings מעמד:

@Component @ConfigurationProperties (קידומת = "google.recaptcha.key") CaptchaSettings בכיתה ציבורית {// ... נכסים אחרים סף צף פרטי; // סטרים וקובעים סטנדרטיים}

5.2. שילוב חזיתי

כעת נשנה את ה- registration.html לכלול את הספרייה של גוגל עם מפתח האתר שלנו.

בתוך טופס ההרשמה שלנו, אנו מוסיפים שדה נסתר שיאחסן את אסימון התגובה שהתקבל מהשיחה אל ה- grecaptcha.execute פוּנקצִיָה:

   ... ... ... ... ... ... var siteKey = /* [[$ {@ captachaService.getReCaptchaSite()}]]; grecaptcha.execute (siteKey, {action: /* [[$ {T(com.baeldung.captcha.CaptchaService).REGISTER_ACTION}] ]*/}).then(function(response) {$ ('# response'). val (תגובה); var formData = $ ('טופס'). סידורי ();

5.3. אימות מצד השרת

נצטרך לבצע את אותה בקשה בצד השרת הנמצאת באימות Validation reCAPTCHA בצד השרת כדי לאמת את אסימון התגובה באמצעות ה- API של שירות האינטרנט.

אובייקט JSON בתגובה יכיל שני מאפיינים נוספים:

{... "ציון": מספר, "פעולה": מחרוזת}

הציון מבוסס על האינטראקציות של המשתמש והוא ערך בין 0 (סביר מאוד שבוט) ל -1.0 (סביר מאוד לאדם).

פעולה היא מושג חדש שגוגל הציגה כדי שנוכל לבצע בקשות reCAPTCHA רבות באותו דף אינטרנט.

יש לציין פעולה בכל פעם שאנחנו מבצעים את ה- reCAPTCHA v3. ועלינו לוודא שערך ה- פעולה המאפיין בתגובה תואם לשם הצפוי.

5.4. אחזר את אסימון התגובה

אסימון התגובה reCAPTCHA v3 נשלף מה- תְגוּבָה פרמטר בקשה באמצעות HttpServletRequest ותוקף באמצעות שלנו CaptchaService. המנגנון זהה לזה שנראה לעיל ב- reCAPTCHA:

מחלקה ציבורית RegistrationController {@ אוטומטית ICaptchaService פרטי captchaService; ... @RequestMapping (value = "/ user / registration", method = RequestMethod.POST) @ResponseBody public GenericResponse registerUserAccount (@Valid UserDto accountDto, HttpServletRequest request) {String response = request.getParameter ("תגובה"); captchaService.processResponse (תגובה, CaptchaService.REGISTER_ACTION); // שאר היישום} ...}

5.5. שיקום מחדש של שירות האימות באמצעות v3

משוחזר CaptchaService מחלקת שירות אימות מכילה processResponse שיטה אנלוגית ל- processResponse השיטה של ​​הגרסה הקודמת, אך היא מקפידה לבדוק את פעולה וה ציון פרמטרים של תגובה של גוגל:

מחלקה ציבורית CaptchaService מיישמת את ICaptchaService {public static final מחרוזת REGISTER_ACTION = "הרשמה"; ... @ ביטול הריק הציבורי processResponse (תגובת מחרוזת, פעולת מחרוזת) {... GoogleResponse googleResponse = restTemplate.getForObject (verifyUri, GoogleResponse.class); אם (! googleResponse.isSuccess () ||! googleResponse.getAction (). שווה (פעולה) || googleResponse.getScore () <captchaSettings.getThreshold ()) {... זרוק ReCaptchaInvalidException חדש ("reCaptcha לא אומת בהצלחה" ); } reCaptchaAttemptService.reCaptchaSucceeded (getClientIP ()); }}

במקרה שהאימות נכשל, נזרוק חריג, אך שימו לב שעם v3, אין אִתחוּל שיטה להפעלת לקוח JavaScript.

עדיין תהיה לנו אותה יישום שנראה לעיל להגנה על משאבי השרת.

5.6. מעדכן את תגובה של גוגל מעמד

עלינו להוסיף את המאפיינים החדשים ציון ו פעולה אל ה תגובה של גוגל שעועית ג'אווה:

@JsonPropertyOrder ({"הצלחה", "ציון", "פעולה", "challenge_ts", "שם מארח", "קודי שגיאה"}) GoogleResponse ציבורי בכיתה {// ... נכסים אחרים @JsonProperty ("ניקוד") ציון צף; @JsonProperty ("פעולה") פעולת מחרוזת פרטית; // סטרים וקובעים סטנדרטיים}

6. מסקנה

במאמר זה שילבנו את ספריית reCAPTCHA של גוגל בדף הרישום שלנו והטמענו שירות לאימות תגובת ה- captcha באמצעות בקשה בצד השרת.

מאוחר יותר, שדרגנו את דף ההרשמה בספריית reCAPTCHA v3 של גוגל וראינו שטופס ההרשמה נהיה רזה יותר מכיוון שהמשתמש כבר לא צריך לעשות שום פעולה.

היישום המלא של מדריך זה זמין ב- GitHub.


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