שדות כניסה נוספים עם אבטחת אביב

1. הקדמה

במאמר זה, אנו מיישמים תרחיש אימות מותאם אישית עם Spring Security מאת הוספת שדה נוסף לטופס הכניסה הסטנדרטי.

אנו מתמקדים 2 גישות שונות, כדי להראות את צדדיות המסגרת ואת הדרכים הגמישות בהן אנו יכולים להשתמש בה.

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

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

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

2. הגדרת Maven

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

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

 org.springframework.boot spring-boot-starter-parent 2.2.6.RELEASE org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot- starter-thymeleaf org.thymeleaf.extras thymeleaf-extras-springsecurity5 

הגרסה העדכנית ביותר של מתג האבטחה של Spring Boot ניתן למצוא באתר Maven Central.

3. הגדרת פרויקט פשוטה

בגישה הראשונה שלנו, נתמקד בשימוש חוזר ביישומים המסופקים על ידי Spring Security. בפרט, נעשה שימוש חוזר DaoAuthenticationProvider ו UsernamePasswordToken כפי שהם קיימים "מחוץ לקופסה".

מרכיבי המפתח יכללו:

  • SimpleAuthenticationFilterהרחבה של UsernamePasswordAuthenticationFilter
  • SimpleUserDetailsServiceיישום של UserDetailsService
  • לָנוּאההרחבה של מִשׁתַמֵשׁ שיעור שמספק Spring Security שמצהיר על התוספת שלנו תְחוּם שדה
  • SecurityConfigתצורת האבטחה האביבית שלנו שמכניסה את שלנו SimpleAuthenticationFilter לתוך שרשרת המסננים, מצהיר על כללי אבטחה ומחבר תלות
  • login.htmlדף כניסה שאוסף את שם משתמש, סיסמה, ו תְחוּם

3.1. מסנן אימות פשוט

בשלנו SimpleAuthenticationFilter, שדות הדומיין ושם המשתמש מופקים מהבקשה. אנו מצרפים ערכים אלה ומשתמשים בהם ליצירת מופע של UsernamePasswordAuthenticationToken.

האסימון מועבר לאחר מכן אל ה- אימות ספק לאימות:

מחלקה ציבורית SimpleAuthenticationFilter מרחיב את UsernamePasswordAuthenticationFilter {@Override Public AuthenticationAuthentication (HttpServletRequest request, HttpServletResponse response) זורק AuthenticationException {// ... UsernamePasswordAuthenticationToken authRequest = getAuthRequest; setDetails (בקשה, authRequest); להחזיר this.getAuthenticationManager () .authenticate (authRequest); } UsernamePasswordAuthenticationToken פרטי getAuthRequest (HttpServletRequest בקשה) {String username = obtainUsername (בקשה); סיסמת מחרוזת = obtainPassword (בקשה); תחום מחרוזת = obtainDomain (בקשה); // ... מחרוזת usernameDomain = String.format ("% s% s% s", username.trim (), String.valueOf (Character.LINE_SEPARATOR), תחום); להחזיר UsernamePasswordAuthenticationToken חדש (usernameDomain, סיסמה); } // שיטות אחרות}

3.2. פָּשׁוּט פרטי המשתמש שֵׁרוּת

ה UserDetailsService חוזה מגדיר שיטה אחת הנקראת loadUserByUsername. היישום שלנו מחלץ את שם משתמש ו תְחוּם. הערכים מועברים לאחר מכן ל- U שלנוserRepository להשיג את מִשׁתַמֵשׁ:

מחלקה ציבורית SimpleUserDetailsService מיישם את UserDetailsService {// ... @Override UserDetails ציבוריים loadUserByUsername (שם משתמש מחרוזת) זורק UsernameNotFoundException {String [] usernameAndDomain = StringUtils.split (שם משתמש, String.valueOf (Character.LINE_SEPAR;) אם (usernameAndDomain == null || usernameAndDomain.length! = 2) {זרוק שם משתמש חדשNotFoundException ("יש לספק שם משתמש ודומיין"); } משתמש משתמש = userRepository.findUser (usernameAndDomain [0], usernameAndDomain [1]); if (user == null) {throw new UsernameNotFoundException (String.format ("שם משתמש לא נמצא עבור דומיין, שם משתמש =% s, דומיין =% s", usernameAndDomain [0], usernameAndDomain [1])); } להחזיר משתמש; }} 

3.3. תצורת אבטחה באביב

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

תצורת הריקות המוגנת על ידי @Override (HttpSecurity http) זורקת חריגה {http .addFilterBefore (authenticationFilter (), UsernamePasswordAuthenticationFilter.class) .authorizeRequests () .antMatchers ("/ css / **", "/ index"). PermitAll (). ("/ user / **"). מאומת () .and () .formLogin (). loginPage ("/ login") .and () .logout () .logoutUrl ("/ logout"); }

אנו יכולים להשתמש בתנאים המוצעים DaoAuthenticationProvider כי אנו מגדירים את זה עם שלנו SimpleUserDetailsService. נזכיר את זה שֶׁלָנוּ SimpleUserDetailsService יודע לנתח את שלנו שם משתמש ו תְחוּם שדות והחזירו את המתאים מִשׁתַמֵשׁ לשימוש בעת אימות:

AuthenticationProvider ציבורי authProvider () {DaoAuthenticationProvider provider = חדש DaoAuthenticationProvider (); provider.setUserDetailsService (userDetailsService); provider.setPasswordEncoder (passwordEncoder ()); ספק החזר; } 

מכיוון שאנחנו משתמשים ב- SimpleAuthenticationFilter, אנו מגדירים את עצמנו AuthenticationFailureHandler כדי להבטיח כי ניסיונות כניסה כושלים מטופלים כראוי:

ציבורי SimpleAuthenticationFilter authenticationFilter () זורק Exception {SimpleAuthenticationFilter filter = new SimpleAuthenticationFilter (); filter.setAuthenticationManager (authenticationManagerBean ()); filter.setAuthenticationFailureHandler (failureHandler ()); מסנן החזרה; }

3.4. דף כניסה

דף הכניסה שאנו משתמשים בו אוסף את התוספת שלנו תְחוּם שדה שמופק על ידי שלנו SimpleAuthenticationFilter:

אנא היכנס

דוגמה: משתמש / תחום / סיסמה

משתמש, סיסמה או דומיין לא חוקיים

שם משתמש

תְחוּם

סיסמה

להתחבר

בחזרה לעמוד הבית

כאשר אנו מריצים את היישום וניגשים להקשר ב- // localhost: 8081, אנו רואים קישור לגישה לדף מאובטח. לחיצה על הקישור תביא להצגת דף הכניסה. כצפוי, אנו רואים את שדה התחום הנוסף:

3.5. סיכום

בדוגמה הראשונה שלנו הצלחנו לעשות שימוש חוזר DaoAuthenticationProvider ו UsernamePasswordAuthenticationToken על ידי "זיוף" שדה שם המשתמש.

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

4. הגדרת פרויקט מותאם אישית

הגישה השנייה שלנו תהיה דומה מאוד לראשונה אך עשויה להתאים יותר למקרי שימוש לא טריוויאליים.

מרכיבי המפתח בגישה השנייה שלנו יכללו:

  • CustomAuthenticationFilterהרחבה של UsernamePasswordAuthenticationFilter
  • CustomUserDetailsServiceממשק מותאם אישית המצהיר על loadUserbyUsernameAndDomain שיטה
  • CustomUserDetailsServiceImplיישום של שלנו CustomUserDetailsService
  • CustomUserDetailsAuthenticationProviderהרחבה של תקציר UserDetailsAuthenticationProvider
  • CustomAuthenticationTokenהרחבה של UsernamePasswordAuthenticationToken
  • לָנוּאההרחבה של מִשׁתַמֵשׁ שיעור שמספק Spring Security שמצהיר על התוספת שלנו תְחוּם שדה
  • SecurityConfigתצורת האבטחה האביבית שלנו שמכניסה את שלנו CustomAuthenticationFilter לתוך שרשרת המסננים, מצהיר על כללי אבטחה ומחבר תלות
  • login.htmlדף הכניסה שאוסף את שם משתמש, סיסמה, ו תְחוּם

4.1. מסנן אימות מותאם אישית

בשלנו CustomAuthenticationFilter, אנחנו לחלץ את השם שם משתמש, סיסמה ושדות תחום מהבקשה. ערכים אלה משמשים ליצירת מופע של ההתאמה האישית שלנוAuthenticationToken אשר מועבר ל אימות ספק לאימות:

מחלקה ציבורית CustomAuthenticationFilter מרחיב את UsernamePasswordAuthenticationFilter {ציבורי סטטי ציבורי מחרוזת SPRING_SECURITY_FORM_DOMAIN_KEY = "תחום"; @ ביטול ניסיון אימות ציבורי אימות (בקשת HttpServletRequest, תגובת HttpServletResponse) זורק AuthenticationException {// ... CustomAuthenticationToken authRequest = getAuthRequest (בקשה); setDetails (בקשה, authRequest); להחזיר this.getAuthenticationManager (). authenticate (authRequest); } פרטי CustomAuthenticationToken getAuthRequest (HttpServletRequest בקשה) {מחרוזת שם משתמש = להשיג שם משתמש (בקשה); סיסמת מחרוזת = obtainPassword (בקשה); תחום מחרוזת = obtainDomain (בקשה); // ... להחזיר אסימון CustomAuthenticationToken חדש (שם משתמש, סיסמה, תחום); }

4.2. המותאם אישית פרטי המשתמש שֵׁרוּת

שֶׁלָנוּ CustomUserDetailsService חוזה מגדיר שיטה אחת הנקראת loadUserByUsernameAndDomain.

ה CustomUserDetailsServiceImpl בכיתה שאנו יוצרים פשוט מיישם את החוזה ומייצג אותנו CustomUserRepository להשיג את מִשׁתַמֵשׁ:

 UserDetails ציבורי loadUserByUsernameAndDomain (שם משתמש מחרוזת, דומיין מחרוזת) זורק UsernameNotFoundException {if (StringUtils.isAnyBlank (שם משתמש, דומיין)) {זרוק שם משתמש חדשNotFoundException ("יש לספק שם משתמש ודומיין"); } משתמש משתמש = userRepository.findUser (שם משתמש, תחום); if (user == null) {throw new UsernameNotFoundException (String.format ("שם משתמש לא נמצא עבור דומיין, שם משתמש =% s, דומיין =% s", שם משתמש, דומיין)); } להחזיר משתמש; }

4.3. המותאם אישית UserDetailsAuthenticationProvider

שֶׁלָנוּ CustomUserDetailsAuthenticationProvider מרחיב תקציר UserDetailsAuthenticationProvider ונציגים שלנו CustomUserDetailService כדי לאחזר את מִשׁתַמֵשׁ. המאפיין החשוב ביותר בשיעור זה הוא יישום ה- retrieveUser שיטה.

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

@Override מוגן UserDetails retrieveUser (מחרוזת שם משתמש, UsernamePasswordAuthenticationToken אימות) זורק AuthenticationException {CustomAuthenticationToken auth = (CustomAuthenticationToken) authentication; UserDetails loadedUser; נסה {loadedUser = this.userDetailsService .loadUserByUsernameAndDomain (auth.getPrincipal () .toString (), auth.getDomain ()); } לתפוס (UsernameNotFoundException notFound) {if (authentication.getCredentials ()! = null) {String presentPassword = authentication.getCredentials () .toString (); passwordEncoder.matches (presentPassword, userNotFoundEncodedPassword); } לזרוק notFound; } לתפוס (Exception repositoryProblem) {לזרוק InternalAuthenticationServiceException חדש (repositoryProblem.getMessage (), repositoryProblem); } // ... return loadedUser; }

4.4. סיכום

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

5. מסקנה

במאמר זה יישמנו כניסה לטופס ב- Spring Security שעשתה שימוש בשדה כניסה נוסף. עשינו זאת בשתי דרכים שונות:

  • בגישה הפשוטה שלנו, מזערנו את כמות הקוד הדרושה לנו. הצלחנו שימוש חוזר DaoAuthenticationProvider ו- UsernamePasswordAuthentication על ידי התאמת שם המשתמש עם לוגיקת ניתוח מותאמת אישית
  • בגישה המותאמת אישית שלנו, סיפקנו תמיכה בשטח מותאמת אישית על ידי הרחבת AbstractUserDetailsAuthenticationProvider ומספקת משלנו CustomUserDetailsService עם CustomAuthenticationToken

כמו תמיד, ניתן למצוא את כל קוד המקור ב- GitHub.


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