Az elmúlt hónapban alkalmam volt egy JWT hitelesítést megvalósítani egy mellékprojekthez. Korábban a JWT-vel dolgoztam a Ruby on Rails-en, de ez volt az első alkalom tavasszal.
Ebben a bejegyzésben megpróbálom elmagyarázni, mit tanultam és alkalmaztam a projektem során, hogy megosszam tapasztalataimat és remélhetőleg néhány embernek segíthessek.
Kezdjük azzal, hogy gyorsan áttekintjük a JWT mögött meghúzódó elméletet és működését. Ezután megvizsgáljuk, hogyan lehet megvalósítani egy Spring Boot alkalmazásban.
JWT alapjai
A JWT vagy a JSON Web Token (RFC 7519) egy szabvány, amelyet leginkább a REST API-k biztonságosságára használnak. Annak ellenére, hogy viszonylag új technológia, gyorsan népszerűvé válik.
A JWT hitelesítési folyamatban a kezelőfelület (kliens) először elküld néhány hitelesítő adatot, hogy hitelesítse magát (esetünkben felhasználónév és jelszó, mivel egy webalkalmazáson dolgozunk).
Ezután a szerver (esetünkben a Spring alkalmazás) ellenőrzi ezeket a hitelesítő adatokat, és ha érvényesek, akkor létrehoz egy JWT-t és visszaadja azt.
Ezt a lépést követően az ügyfélnek meg kell adnia ezt a tokent a kérelem Engedélyezés fejlécében a „Bearer TOKEN” űrlapon. A háttér ellenőrzi a token érvényességét, és engedélyezi vagy elutasítja a kéréseket. A token tárolhat felhasználói szerepköröket és engedélyezheti a kéréseket az adott jogosultságok alapján.

Végrehajtás
Most nézzük meg, hogyan tudjuk megvalósítani a JWT bejelentkezési és mentési mechanizmusát egy igazi tavaszi alkalmazásban.
Függőségek
Az alábbiakban megtekintheti a Maven-függőségek listáját, amelyeket példakódunk használ. Ne feledje, hogy az alapvető függőségek, mint a Spring Boot és a Hibernate, nem szerepelnek ebben a képernyőképben.

Felhasználók mentése
Először olyan vezérlőket hozunk létre, amelyek biztonságosan mentik a felhasználókat és hitelesítik őket a felhasználónév és a jelszó alapján.
Van egy User nevű modell entitásunk. Ez egy egyszerű entitásosztály, amely a USER táblához kapcsolódik. Alkalmazásától függően bármilyen tulajdonságot használhat, amelyre szüksége van.

Van egy egyszerű UserRepository osztályunk is a felhasználók mentésére. Felül kell írnunk a findByUsername metódust, mivel a hitelesítés során használni fogjuk.
public interface UserRepository extends JpaRepository{ User findByUsername(String username); }
Soha ne tároljunk egyszerű szövegű jelszavakat az adatbázisban, mert sok felhasználó általában ugyanazt a jelszót használja több webhelyhez.
Sok különböző hash algoritmus létezik, de a leggyakrabban használt BCrypt, és ez a biztonságos hash ajánlott módszere. A témával kapcsolatos további információkért tekintse meg ezt a cikket.
@Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); }
A jelszó kivonatolásához megadunk egy BCrypt babot a @SpringBootApplication alkalmazásban, és a következőképpen jegyzeteljük a főosztályt :
Akkor hívjuk meg a metódusokat ezen a babon, ha jelszót kell kivonatolnunk.
A felhasználók mentéséhez szükségünk van egy UserController-re is. Létrehozzuk a vezérlőt, annotáljuk a @RestController programmal, és meghatározzuk a megfelelő leképezést.
Alkalmazásunkban a felhasználót a kezelőfelületről továbbított DTO objektum alapján mentjük el. Felhasználói objektumot is átadhat a @RequestBody fájlban .
Miután elhaladtuk a DTO objektumot, a korábban létrehozott BCrypt bab segítségével titkosítjuk a jelszó mezőt . Ezt megteheti a vezérlőben is, de jobb gyakorlat ezt a logikát a szervizosztályba sorolni.
@Transactional(rollbackFor = Exception.class) public String saveDto(UserDto userDto) { userDto.setPassword(bCryptPasswordEncoder.encode(userDto.getPassword())); return save(new User(userDto)).getId(); }
Hitelesítési szűrő
Hitelesítésre van szükségünk, hogy megbizonyosodhassunk arról, hogy a felhasználó valóban az, akinek állítja magát. Ennek eléréséhez a klasszikus felhasználónév / jelszó párost fogjuk használni.
A hitelesítés megvalósításának lépései:
- Hozzon létre hitelesítési szűrőnket, amely kiterjeszti a UsernamePasswordAuthenticationFilter alkalmazást
- Hozzon létre egy biztonsági konfigurációs osztályt, amely kiterjeszti a WebSecurityConfigurerAdapter alkalmazást, és alkalmazza a szűrőt
Itt található a hitelesítési szűrőnk kódja - amint azt Ön is tudja, a szűrők a Spring Security gerincét alkotják.
Nézzük át lépésről lépésre ezt a kódot.
Ez az osztály kiterjeszti a UsernamePasswordAuthenticationFilter alkalmazást, amely a Spring Security alapértelmezett osztálya a jelszó hitelesítéséhez. Kiterjesztjük az egyedi hitelesítési logikánk meghatározására.
Hívást kezdeményezünk a setFilterProcessesUrl metódusra a konstruktorunkban. Ez a módszer az alapértelmezett bejelentkezési URL-t állítja be a megadott paraméterre.
Ha eltávolítja ezt a sort, a Spring Security alapértelmezés szerint létrehozza a „/ login” végpontot. Meghatározza számunkra a bejelentkezési végpontot, ezért nem definiálunk kifejezetten egy bejelentkezési végpontot a vezérlőnkben.
E sor után a bejelentkezési végpontunk az / api / services / controller / user / login lesz . Ezzel a funkcióval konzisztens maradhat a végpontjaival.
Mi felülírják a attemptAuthentication és successfulAuthentication módszereit UsernameAuthenticationFilter osztályban.
A tryAuthentication függvény akkor fut, amikor a felhasználó megpróbál bejelentkezni az alkalmazásunkba. Elolvassa a hitelesítő adatokat, létrehoz egy felhasználói POJO-t belőlük, majd ellenőrzi a hitelesítést.
Átadjuk a felhasználónevet, jelszót és egy üres listát. Az üres lista a hatóságokat (szerepköröket) képviseli, és hagyjuk a jelenlegi állapotot, mivel alkalmazásunkban még nincsenek szerepeink.
Ha a hitelesítés sikeres, akkor a sikeresAuthentication módszer fut. Ennek a módszernek a paramétereit a Spring Security adja át a kulisszák mögött.
The attemptAuthentication method returns an Authentication object that contains the authorities we passed while attempting.
We want to return a token to user after authentication is successful, so we create the token using username, secret, and expiration date. We need to define the SECRET and EXPIRATION_DATE now.
We create a class to be a container for our constants. You can set the secret to whatever you want, but the best practice is making the secret key as long as your hash. We use the HS256 algorithm in this example, so our secret key is 256 bits/32 chars.
The expiration time is set to 15 minutes, because it is the best practice against secret key brute-forcing attacks. The time is in milliseconds.
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. :)