In this article you can read about applying
Spring Security to the backend module of a Spring Boot and Angular app. Check out how to configure security, generate JWT tokens and protect API endpoints.
What we are going to build
In the Securing your Spring Boot and Angular app with JWT #1 – Introduction post you can find the description of the secured multi-module application which we are going to create.
In the Securing your Spring Boot and Angular app with JWT #3 – Frontend post you can find the details of safeguarding the frontend module.
The finished project is available in the GitHub repository – little-pinecone/jwt-spring-boot-angular-scaffolding.
In this post we are focusing on the backend module.
|To keep the code snippets reasonably short in this post, I don’t include the
You can see the final directory tree for the backend module on the image below:
Get the base project
It’s the starting point for securing the
Cookie dispenser application.
Secure the backend
Right now all calls to the API are allowed, as you can see in this screenshot taken from Postman:
When we are done, the path
http://localhost:8080/cookies will be available only for authenticated and authorized users.
Enable Spring Security
Add the dependency
Add the Spring Security dependency to the backend pom file:
After rebuilding the project, all calls to the API should fail:
We’re getting the
401 Unauthorized error so the default security configuration works as intended.
Fix tests after enabling Spring Security
CookieController has a test that won’t be executed properly after adding the security. We are getting the following error:
To fix it, add the spring-security-test dependency to the backend pom file:
Furthermore, we need to add the
@WithMockUser(roles = "USER") annotation to the test:
After those changes the test execution should run without any further errors.
The work done in this section is contained in the commit 1d6b7c76360d2ad98ec02c1af0c63719dd29c87c.
Handle user registration
Create the model for User data
To create the
User class, annotated as a JPA entity, we need the spring-boot-starter-data-jpa dependency added to our project:
User class and copy the following code to the file:
User entity has fields for an
id and the embedded
UserCredentials class. We need getters for the properties and two constructors. Methods:
toString() are omitted for the sake of brevity, don’t forget to generate them in your project.
Next we are going to add the class for username and password – create the
UserCredentials class and copy the following code into it:
toString() are omitted in the code snippet above.
UserRepository interface that extends
CrudRepository to manage the persistence layer:
Provide business logic for handling users
The logic for creating a new user is enclosed in the
UserService – we need to encrypt passwords, give every user a default role in the app and save users in a database. Copy the following code to your service:
To store encrypted passwords we need to create an
encoder that is injected into the
UserService. Create a new configuration file –
PasswordEncoderConfig and declare the encoder bean in it, like in the following code snippet:
Last but not least, create a REST controller to handle the registration requests. Copy the code from the
This endpoint enables new users to register by using the
UserService we provided.
We need to override the default security configuration to specify which requests require authorization and how to handle forbidden API calls. In our case, we also need to provide the possibility to apply
CORS configuration depending on the environment. Create the
SecurityConfig file and copy the following code:
All API requests, apart from the one for user registration (), are secured.
If we decide to serve something on paths other than(e.g. a landing page on ) the authentication won’t be required.
In case of not authorized calls to the API, the server will return a response with 401 status and “Unauthorized” error.
application-development.properties file and copy the following property there:
Now we can test the request creating a new user:
The work done in this section is contained in the commit 40e2d0ec5745cb0605a5c59525b8628cf2df0dc7.
|The commit contains also all tests for
Include the dependency and define token properties
Start with adding the dependency for JWT:
We will define token properties and initialize them with the following default values in the
Remember to annotate it with @ConfigurationProperties to bind the external configuration to this class so you can set your own
expiration time and
secret in the production environment:
Enable searching for a user during the authentication process
UserRepository interface declare the method for getting a user by a username:
And use it in the
We are going to use our implementation of
findByUsername() function during authentication. In order to achieve that we need to create the
UserDetailService interface implementation, override its
loadUserByUsername() function and inject our
UserService to access the method we’ve created above.
Copy the following code to enable fetching a
UserDetails object with complete credentials and granted authorities. In case of requesting for a not existing user a
UsernameNotFoundException is thrown:
AuthenticationFilter class that extends
UsernamePasswordAuthenticationFilter (Spring Boot will automatically place our filter in the most suitable position in the Security Filter Chain) and initialize all essential properties in its constructor:
Now we can override the
attemptAuthentication() function that is called whenever the API gets a request to the login path. Let’s extract the credentials sent in a request and use it to generate the
UsernamePasswordAuthenticationToken instance that we pass to the
We will also override the
successfulAuthentication() function to add the
Authorization header with the
JWT token data to the response. The header is prefixed with “Bearer ” from the
TokenProperties configuration and contains the complete token built with the
createToken() function is also the place where you can specify the signing algorithm. Copy the following code to the
AuthorizationFilter class that extends
OncePerRequestFilter and inject the
TokenProperties instance to it:
We need to override the
doFilterInternal() function to extract the
Authorization header from a request. Let’s stop for a moment and investigate the logic here. If the
Authorization header is found we verify whether a user that sent the request is authorized to access the content:
First we parse the token after removing the “Bearer ” prefix from it:
If the claims subject (principal) is present we can set the user context for the authorized user:
And finally we have to pass the control to the next filter from the
Update the security configuration
Change the class annotation to @EnableWebSecurity, add the following properties and constructor to the code:
configure() method we are going to add:
logout()functionality – line 11;
AuthenticationFilter– line 13;
AuthorizationFilter– line 14;
- a matcher to the
loginpath that is available for all users – line 16;
- a matcher to restrain all requests (other than registration) that are related to the
"/api/users/**"endpoint to be accessible only to a user with the ADMIN role – line 18.
The method should look like the one on the code snippet below:
Allow usage of your custom
Check out the login functionality
Rebuild the application, register a test user and test the login functionality:
Copy the token value from the
Authorization header, and use it to display the secured pastry – change the
Authorization Type to the
Bearer Token and paste the token value:
Fix the tests
When we try to run tests, e.g. for the
UserController class, we will get the following error in the console:
We need to add the dependencies we already injected to the
SecurityConfig and the config class itself.
@Import() annotation and inject a Mock for the
The work done in this section is contained in the commits 6b4c29c6c11ca0cf435203475cde4f6d1809c6bf. The necessary fixes are added in the commits: 1b80e70533f4823086c7aefb556884452aa311e9 and 0eba82766d98ca5c242d8a07467139a0dab3038b.