Как да настроите Java Spring Boot JWT оторизация и удостоверяване

През последния месец имах възможност да внедря JWT auth за страничен проект. Преди съм работил с JWT в Ruby on Rails, но това беше първият ми път през пролетта.

В този пост ще се опитам да обясня какво съм научил и приложил в моя проект, за да споделя моя опит и да се надявам да помогна на някои хора.

Ще започнем, като разгледаме набързо теорията зад JWT и как тя работи. След това ще разгледаме как да го приложим в приложението Spring Boot.

Основи на JWT

JWT или JSON Web Tokens (RFC 7519) е стандарт, който се използва най-вече за осигуряване на REST API. Въпреки че е относително нова технология, тя набира бърза популярност.

В процеса на JWT auth предният край (клиент) първо изпраща някои идентификационни данни, за да се удостовери (потребителско име и парола в нашия случай, тъй като работим върху уеб приложение).

След това сървърът (приложението Spring в нашия случай) проверява тези идентификационни данни и ако те са валидни, генерира JWT и го връща.

След тази стъпка клиентът трябва да предостави този маркер в заглавката за оторизация на заявката във формуляра „TOQEN на носителя“. Задният край ще провери валидността на този маркер и ще разреши или отхвърли заявки. Токенът може също да съхранява потребителски роли и да разрешава заявките въз основа на дадените правомощия.

Изпълнение

Сега нека видим как можем да приложим механизма за влизане и запазване на JWT в реално приложение Spring.

Зависимости

Можете да видите списъка с зависимости на Maven, който нашият примерен код използва по-долу. Имайте предвид, че основните зависимости като Spring Boot и Hibernate не са включени в тази екранна снимка.

Запазване на потребители

Ще започнем със създаването на контролери за безопасно спасяване на потребителите и тяхното удостоверяване въз основа на потребителско име и парола.

Имаме модел на обект, наречен Потребител. Това е прост клас на обект, който се преобразува в таблицата USER . Можете да използвате всички свойства, от които се нуждаете, в зависимост от вашето приложение.

Имаме и прост клас UserRepository, за да спасим потребителите. Трябва да заменим метода findByUsername , тъй като ще го използваме при удостоверяване.

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

Никога не трябва да съхраняваме пароли за открит текст в базата данни, защото много потребители са склонни да използват една и съща парола за множество сайтове.

Има много различни алгоритми за хеширане, но най-често използваният е BCrypt и е препоръчителен метод за сигурно хеширане. Можете да разгледате тази статия за повече информация по темата.

@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 и приложете филтъра

Ето кода за нашия филтър за удостоверяване - както може би знаете, филтрите са гръбнакът на Spring Security.

Нека да преминем този код стъпка по стъпка.

Този клас разширява UsernamePasswordAuthenticationFilter, който е класът по подразбиране за удостоверяване с парола в Spring Security. Ние го разширяваме, за да дефинираме нашата потребителска логика за удостоверяване.

Извършваме извикване на метода setFilterProcessesUrl в нашия конструктор. Този метод задава URL адреса за вход по подразбиране на предоставения параметър.

Ако премахнете този ред, Spring Security създава крайната точка „/ login“ по подразбиране. Той определя крайната точка за влизане за нас, поради което няма да дефинираме изрично крайна точка за вход в нашия контролер.

След този ред крайната ни точка за вход ще бъде / api / services / controller / user / login . Можете да използвате тази функция, за да останете в съответствие с крайните си точки.

We override the attemptAuthentication and successfulAuthentication methods of the UsernameAuthenticationFilter class.

The attemptAuthentication function runs when the user tries to log in to our application. It reads the credentials, creates a user POJO from them, and then checks the credentials to authenticate.

We pass the username, password, and an empty list. The empty list represents the authorities (roles), and we leave it as is since we do not have any roles in our application yet.

If the authentication is successful, the successfulAuthentication method runs. The parameters of this method are passed by Spring Security behind the scenes.

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. :)