כיצד להגדיר Java Spring Boot Boot JWT Authentication

בחודש האחרון הייתה לי הזדמנות ליישם אימות JWT לפרויקט צדדי. עבדתי בעבר עם JWT ב- Ruby on Rails, אך זו הייתה הפעם הראשונה שלי באביב.

בפוסט זה אנסה להסביר את מה שלמדתי ויישמתי בפרויקט שלי כדי לשתף את הניסיון שלי ולקוות לעזור לאנשים מסוימים.

נתחיל במבט מהיר על התיאוריה שמאחורי JWT ואיך היא עובדת. לאחר מכן נבחן כיצד ליישם אותו ביישום Spring Boot.

יסודות JWT

JWT, או JSON Web Tokens (RFC 7519), הוא תקן המשמש בעיקר לאבטחת ממשקי API של REST. למרות היותה טכנולוגיה חדשה יחסית, היא צוברת פופולריות מהירה.

בתהליך אימות JWT, הקצה (לקוח) שולח תחילה אישורים לאימות עצמו (שם משתמש וסיסמה במקרה שלנו, מכיוון שאנו עובדים על יישום אינטרנט).

השרת (אפליקציית Spring במקרה שלנו) בודק את האישורים הללו, ואם הם תקפים, הוא יוצר JWT ומחזיר אותו.

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

יישום

עכשיו בואו נראה איך נוכל ליישם את מנגנון הכניסה והשמירה של JWT ביישום Spring אמיתי.

תלות

תוכל לראות את רשימת התלות של Maven בה משתמש קוד הקוד שלנו. שים לב שתלות הליבה כמו Spring Boot ו- Hibernate אינן כלולות בצילום המסך הזה.

שמירת משתמשים

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

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

יש לנו גם מחלקת UserRepository פשוטה להצלת משתמשים. עלינו לבטל את שיטת findByUsername מכיוון שנשתמש בה באימות.

public interface UserRepository extends JpaRepository{ User findByUsername(String username); }

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

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

@Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); }

כדי לחסל את הסיסמה, נגדיר שעועית BCrypt ב- @ SpringBootApplication ונציין את המחלקה הראשית באופן הבא:

אנו נקרא לשיטות שעועית זו כשנצטרך לחסל סיסמה.

אנו זקוקים גם ל- UserController כדי להציל משתמשים. אנו יוצרים את הבקר, מציינים אותו באמצעות @RestController ומגדירים את המיפוי המתאים.

ביישום שלנו אנו שומרים את המשתמש בהתבסס על אובייקט DTO שמועבר מהקצה הקדמי. אתה יכול גם להעביר אובייקט משתמש ב- @ RequestBody .

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

@Transactional(rollbackFor = Exception.class) public String saveDto(UserDto userDto) { userDto.setPassword(bCryptPasswordEncoder.encode(userDto.getPassword())); return save(new User(userDto)).getId(); }

מסנן אימות

אנו זקוקים לאימות כדי לוודא שהמשתמש הוא באמת מי שהוא טוען שהוא. אנו נשתמש בזוג שם המשתמש / הסיסמה הקלאסי כדי להשיג זאת.

להלן השלבים ליישום אימות:

  1. צור את מסנן האימות שלנו המרחיב את UsernamePasswordAuthenticationFilter
  2. צור מחלקת תצורת אבטחה המרחיבה את WebSecurityConfigurerAdapter והחל את המסנן

הנה הקוד למסנן האימות שלנו - כפי שאתה אולי יודע, מסננים הם עמוד השדרה של אבטחת אביב.

בוא נעבור על קוד זה שלב אחר שלב.

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

אנו מתקשרים לשיטת setFilterProcessesUrl בבנאי שלנו. שיטה זו מגדירה את כתובת ה- URL לכניסה המוגדרת כברירת מחדל לפרמטר שסופק.

אם תסיר את השורה הזו, Spring Security יוצר את נקודת הקצה "/ login" כברירת מחדל. זה מגדיר עבורנו את נקודת הסיום של הכניסה, ולכן לא נגדיר נקודת סיום התחברות בבקר שלנו במפורש.

לאחר שורה זו נקודת הסיום שלנו תהיה / api / services / controller / user / login . אתה יכול להשתמש בפונקציה זו כדי לשמור על עקביות עם נקודות הקצה שלך.

אנחנו עוקפים את attemptAuthentication ו successfulAuthentication השיטות של UsernameAuthenticationFilter בכיתה.

AttemptAuthentication הפונקציה פועלת כאשר מנסה המשתמשים להתחבר אלי היישום שלנו. זה קורא את האישורים, יוצר מהם משתמש POJO ואז בודק את האישורים כדי לאמת.

אנו מעבירים את שם המשתמש, הסיסמה ורשימה ריקה. הרשימה הריקה מייצגת את הרשויות (התפקידים) ואנחנו משאירים אותה כפי שהיא מכיוון שעדיין אין לנו תפקידים ביישום שלנו.

אם האימות מצליח, שיטת ההצלחה האמיתית פועלת. הפרמטרים של שיטה זו מועברים על ידי Spring Security מאחורי הקלעים.

AttemptAuthentication המתודה מחזירה אימות אובייקט המכיל את הרשויות שעברנו תוך ניסיון.

אנו רוצים להחזיר אסימון למשתמש לאחר שהאימות יצליח, לכן אנו יוצרים את האסימון באמצעות שם משתמש, סוד ותאריך תפוגה. עלינו להגדיר את SECRET ו- EXPIRATION_DATE כעת.

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

זמן התפוגה מוגדר ל -15 דקות מכיוון שזהו השיטה הטובה ביותר כנגד התקפות אילוץ מפתח סודיות. הזמן הוא באלפיות השנייה.

We have prepared our Authentication filter, but it is not active yet. We also need an Authorization filter, and then we will apply them both through a configuration class.

This filter will check the existence and validity of the access token on the Authorization header. We will specify which endpoints will be subject to this filter in our configuration class.

Authorization Filter

The doFilterInternal method intercepts the requests then checks the Authorization header. If the header is not present or doesn’t start with “BEARER”, it proceeds to the filter chain.

If the header is present, the getAuthentication method is invoked. getAuthentication verifies the JWT, and if the token is valid, it returns an access token which Spring will use internally.

This new token is then saved to SecurityContext. You can also pass in Authorities to this token if you need for role-based authorization.

Our filters are ready, and now we need to put them into action with the help of a configuration class.

Configuration

We annotate this class with @EnableWebSecurity and extend WebSecurityConfigureAdapter to implement our custom security logic.

We autowire the BCrypt bean that we defined earlier. We also autowire the UserDetailsService to find the user’s account.

The most important method is the one which accepts an HttpSecurity object. Here we specify the secure endpoints and filters that we want to apply. We configure CORS, and then we permit all post requests to our sign up URL that we defined in the constants class.

You can add other ant matchers to filter based on URL patterns and roles, and you can check this StackOverflow question for examples regarding that. The other method configures the AuthenticationManager to use our encoder object as its password encoder while checking the credentials.

Testing

Let’s send a few requests to test if it works properly.

Here we send a GET request to access a protected resource. Our server responds with a 403 code. This is the expected behavior because we haven’t provided a token in the header. Now let’s create a user:

To create a user, we send a post request with our User DTO data. We will use this user to login and get an access token.

Great! We got the token. After this point, we will use this token to access protected resources.

We provide the token in the Authorization header and we are now allowed access to our protected endpoint.

Conclusion

In this tutorial I have walked you through the steps I took when implementing JWT authorization and password authentication in Spring. We also learned how to save a user securely.

Thank you for reading – I hope it was helpful to you. If you are interested in reading more content like this, feel free to subscribe to my blog at //erinc.io. :)