Our application already has a controller, thanks to which we can manage data using HTTP requests. However, we must set security so that only authorized persons can manage these data.
Spring Security provides security services for Spring-based applications:
Protection against attacks like session fixation, clickjacking, cross-site request forgery, etc
Basic Access Authentication
Digest Access Authentication
Web Form Authentication
You can add Spring Security in your application by adding spring-boot-starter-security dependency to your pom.xml file. By the way, we’ll also add dependency to JWT. We will need it later:
To configure Spring Security in our project, we have to add new configuration class that extends the WebSecurityConfigurerAdapter class in net.devdiaries.wallet.configuration package:
The @Configuration and @EnableWebSecurity annotations provide the default security configuration. Now, we can define which endpoints should be secured. We have to override the configure(HttpSecurity HTTP method.
Adding user/password feature
We can use an in-memory user provided by Spring Security, but the real backend application should save the users in the database. We are going to create an entity class for the user (User.java) and repository class. It will be very similar to creating Currency.java and CurrencyRepository in the first post: 1 - User.java in the net.devdiaries.wallet.domain package:
2 - UserRepository.java in the net.devdiaries.wallet.domain package:
Now, we have to create the UserDetailsService implementation. This class (provided by Spring Security) is used for finding and authenticating users. Let’s create a new package: net.devdiaries.wallet.services with UserDetailsServiceImpl class and inject UserRepository:
We had to override loadUserByUsername(..) method. This method checks if the user was found or not. Finally, the method returns a Spring Security Userobject with the username, password, and role (we use new SimpleGrantedAuthority(“user”) for this tutorial) of the authenticated user.
Configure UserDetailsServiceImpl in the SecurityConfiguration
Let’s return to our SecurityConfiguration class (created above). Now we can complete its configuration:
We have added a configureGlobal(...) method to enable fetching and authorizing users from the database. We’ve also defined PasswordEncoder bean (BCryptPasswordEncoder) which uses the BCrypt hashing algorithm - our password has to be encrypted.
Now We can save new users in the CommandLineRunner. Inject UserRepository to the WalletApplication and add users:
We have just saved the “admin” user to our database. $2a$04$KNLUwOWHVQZVpXyMBNc7JOzbLiBjb9Tk9bP7KNcPI12ICuvzXQQKG is BCrypt-encrypted “admin” password (You can use online encryptors to encrypt another password).
Now, if you do GET request to the localhost:8080/currencies you will get a 401 Unauthorized HTTP error. You should authenticate to be able to get currencies (You can do this using the Authorization function in Postman or using curl -u admin).
Securing application using JWT token
Our authentication method is not usable when we are going to use our own frontend app. We are going to use the JSON Web Token (JWT) authentication. JWT defines how to exchange data between web services in a secure way through a JSON object. The information sent can be verified thanks to a digital signature, which is an element of the token. The JWT token is signed using the signature - the HMAC algorithm or with the RSA public/private key. Each JWT token contains three parts separated by dots:
Header: defines the type of the token and the hashing algorithm,
Payload: contains information about the user
Signature: used to verify that JWT has not been changed.
You can see the main idea of the JWT in the following image:
Creating Authentication Functionality
Let’s create a service (in the net.devdiaries.wallet.services package) that will create and validate the JWT token. The service will be called AuthenticationService:
In the beginning, we defined several constants. EXPIRATIONTIME defines the expiration time of the token (24 hours), SIGNINGKEY is used to sign the JWT (by verifying the JWT token we have a guarantee that it comes from our application), BEARER_PREFIX is the prefix of Authorization token - we use Bearer schema. The addJWTToken() creates the JWT and adds it to the Authorization header. We also had to add Access-Control-Expose-Headers header due to JS limitations on the frontend side. The getAuthentication() method gets the JWT token from the Authorization header.
Next, create a POJO class with user credentials in domain package:
We do not have to mark this class with @Entity because this class will only keep credentials for authentication. We do not want to create a new table in the database.
Finally, we need to create two filters that will handle requests:
1 - Filter for login and authentication - LoginFilter.java which handles all POST request to the `/login’ endpoint:
We extended AbstractAuthenticationProcessingFilter class which requres authenticationManager property in the Spring context (We’ve set it in the SecurityConfiguration class). If the authentication is successful, the successfulAuthentication method will be executed and then the addJWTToken() from AuthenticationService class will add the JWT to the Authorization header.
2 - Filter for handling in all other endpoints:
This filter extends GenericFilterBean and gets a token from the request header.
Finally, we have to update SecurityConfiguration class by overriding the configure() method. The following source code shows the SecurityConfiguration final code:
We defined that requests for all requests (except /login endpoint) requires authentication. We also added CORS filter. This is needed for frontend which is sending request from the other origin.
Run the application and call the /login endpoint with the POST HTTP method (using a postman or curl). In body add username and password which are in the database:
As you can see, we got JWT in response (Authorization header - at the bottom of the picture). Copy this token and add it to the Authorization header in the request for localhost:8080/currencies. In response, you will receive all the currencies.