This post is a sequel of part 1 were talked about some design decisions, especially about building without a mainstream DB.
This post is primarily on how I spent half of the time developing this app on spring-security 😫🤕. Nonetheless, this gave me a new picture of how spring applications work…If it hadn’t been for this aspect of the application, I might not have learnt about filters and servlets.
Entry Point…
Immediately after installing spring-security, all your endpoints will be inaccessible, and to access them, you must log in using a user generated by spring-security (spring security has some boilerplate configurations with a form login). Therefore, to personalize the process you create a class that extends WebSecurityConfigurerAdapter- is the entry point/file of spring security.
The implementation is shown below.
We use the @Configuration to make this class a configuration file that would be used by spring. We have to extend the already existing configuration to add our personalized code that fits our application.
Using @Autowired we import from the spring container
MyAccountDetailsService - A customized service to access users
JWTFilter - A filter checking every request to resolve and validate JWT tokens
Using @Bean we are creating a bean - an object that is instantiated, assembled, and otherwise managed by a Spring IoC container
authenticationManagerBean() returns a manager that can be used to login and …
encoder() returns the default PasswordEncoder for the application in this case BCryptPasswordEncoder()
Implementing the WebSecurityConfigurerAdapter we have to override the configure() function. In this case, we are using method overloading to configure two different aspects of the security system
protected void configure(AuthenticationManagerBuilder auth); In this function, we are setting the MyAccountDetailsService as the default service for user access and also set the default password encoder
protected void configure(HttpSecurity http); Here we are configuring the http.
First, we set cors then we disable csrf since we are using JSON requests not forms
Allowed access to /api/v1/create_account and /api/v1/login without authentication.
We then define the session manager to be STATELESS
we then finally add the jwtFilter
Below is the implementation of the JWTFilter
In summary, we are resolving the token from the header and if it is not null, we validate the token. Let us just take the jwtUtils as a black box. Although, you can see full implementation here. The validateToken function throws an exception which we have to take care of as we progress.
Once the token is valid then we try to verify the user using myAccountDetailsService then set the security context with the new usernamePasswordAuthenticationToken. we then move on to the next filter using filterChain.doFilter(req, res).
Below is the implementation of myAccountDetailsService.
To implement the UserDetaisService, we have to override the loadUserByUsername where we tell spring how to find our user.
Login
The AccountService has a simple login function see below
The authenticationManager is autowired into the service, and a token is returned after a successful login.
Error Management
At this stage, everything was looking okay. However, spring-security was returning a generic error for every exception even after adding an exceptionHandler to the HttpSecurity configuration.
At the filter level, @ControllerAdvice cant manage the errors.
Filters happens before controllers are even resolved so exceptions thrown from filters can’t be caught by a Controller Advice.
— site
To solve this issue, I built an exception handler filter to manage all errors in the filters.
This handler is to be added to the webSecurity configuration. The final implementation looks like
Line 26 autowires the ExceptionHandlerFilter while line 26 adds the filter before the CorsFIlter
Conclusion
It has been a long one, I pasted the snippets of the notable parts of the application in the post. Moreover, you can find the full implementation is here.