Angular,Spring Boot,Spring Data and Rest Example(CRUD)
Angular 8 Client-Side Architecture
With the release of Angular 8, there are many new features that have been introduced such as Ivy Preview, Web Workers, Lazy Loading, Differential Loading, etc. The new version requires TypeScript 3.4+ and Node 12+. We will be using Angular 8 at the client side.
We will have 4 different routes. The login route will be the welcome page that accepts username and password for login. On submit, the login API will be called and in response of the API, client receives a JWT token which is cached in the browser local cache so that any further HTTP request can be injected with this token in the header. We will have an interceptor implemented to perform this injection.
After login, the list route will be loaded which will again make a REST call to fetch user list and this list will be rendered in the browser. This screen will have options to add new user, edit and delete an existing user.
Spring Boot Server-Side Architecture
At the server-side, we will be using Spring Boot 2 to expose our REST endpoints. We will have Spring Security integrated with Spring Data to perform DB operations.
At the server side, we have a security filter defined that is responsible for intercepting all the requests to extract JWT token from the HTTP header and set the security context. You can follow this article to create a full-fledged JWT token-based authentication system using Spring Security.
After this the request will reach the controllers. We have seperate controllers defined for authentication related stuff and for user CRUD related stuff. The authentication related APIs are not secured but all the user related APIs are secured. The controller commands to the service layer to perform the business logic which makes DAO calls if required.
Below are the lists of commands that we will execute to generate our components from CLI.
ng g component login
ng g component user/list-user
ng g component user/add-user
ng g component user/edit-user
Now, let us start implementing these components step by step.
Login Component
As you can see, the login component has a reactive login form that takes username and password input from the user. On the click of Login button, onSubmit() function is called. This function then calls the service component so that the username/password can be validated from the API. Even this request will be intercepted by our HTTP interceptor but the token will not be added in the header. login.component.html
Once the list view is rendered, an API call will be made to fetch user details and the same can be viewed in a tabular form. We have multiple buttons on this page to add, edit and delete any user. list-user.component.html
There are many things happening here. All the action items implementation are here. On the click of Add button addUser() function will be invoked which will just navigate to add-user route.
On click of Edit button, editUser() function will be called. This method saves the selected user id in the local storage. Once, the edit component is rendered, this user id will be picked to fetch user details from the API and the add form is rendered in the editable format.
On click of the delete button, the selected rows will be removed from the table and an API call be made to remove this user from the DB too. list-user.component.ts
Add user component has a simple form to take input from the user and makes a HTTP call to save the user in the DB. Once, the user is added, user will be again routed to list-user route. add-user.component.ts
Whenever, edit button is clicked, the selected user id is stored in the local storage and inside ngOnInit(), an API call is made to fetch the user by user id and the form is auto-populated. edit-user.component.html
Below is the service class that makes HTTP calls to perform the CRUD operations. It uses HttpClient from @angular/common/http. As we discussed above, we have 2 different controllers in the server side and hence two different URL group to make HTTP request. api.service.ts
The interceptor implements HttpInterceptor which intercepts all the HTTP request and token in the header for API authentication. At the time of login, there won't be any valid token present in the local cache hence, there is a condition check for the presence of token. interceptor.ts
Following class extends OncePerRequestFilter that ensures a single execution per request dispatch. This class checks for the authorization header and authenticates the JWT token and sets the authentication in the context. Doing so will protect our APIs from those requests which do not have any authorization token. The configuration about which resource to be protected and which not can be configured in WebSecurityConfig.java JwtAuthenticationFilter.java
Now let us define our usual spring boot security configurations.We have userDetailsService injected to fetch user credentials from database.
Here the annotation @EnableGlobalMethodSecurity enables method level security and you can annotate your method with annotations such as @Secured to provide role based authentication at method level. WebSecurityConfig.java
As discussed above, we have 2 different controllers - one is for authentication and another is to peform CRUD operation on User entity.
Following is the controller that is exposed to create token on user behalf and if you noticed in WebSecurityConfig.java we have configured this url to have no authentication so that user can generate JWT token with valid credentials. AuthenticationController.java
Below is our controller class that has all the API implementation for the CRUD operation. Please let me know if you have any doubt with the code in the comment section. UserController.java
The service class has some business logic and it is the bridge between our controllers and DAOs.
@Transactional@Service(value ="userService")public classUserServiceImplimplementsUserDetailsService,UserService { @Autowiredprivate UserDao userDao; @Autowiredprivate BCryptPasswordEncoder bcryptEncoder;public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user =userDao.findByUsername(username);if(user ==null){thrownewUsernameNotFoundException("Invalid username or password."); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthority());
}private List getAuthority() {returnArrays.asList(newSimpleGrantedAuthority("ROLE_ADMIN")); }public List findAll() { List list =new ArrayList<>();userDao.findAll().iterator().forEachRemaining(list::add);return list; } @Overridepublicvoiddelete(int id) {userDao.deleteById(id); } @Overridepublic User findOne(String username) {returnuserDao.findByUsername(username); } @Overridepublic User findById(int id) { Optional optionalUser =userDao.findById(id);returnoptionalUser.isPresent() ?optionalUser.get() :null; } @Overridepublic UserDto update(UserDto userDto) { User user =findById(userDto.getId());if(user !=null) {BeanUtils.copyProperties(userDto, user,"password","username");userDao.save(user); }return userDto; } @Overridepublic User save(UserDto user) { User newUser =newUser();newUser.setUsername(user.getUsername());newUser.setFirstName(user.getFirstName());newUser.setLastName(user.getLastName());newUser.setPassword(bcryptEncoder.encode(user.getPassword()));newUser.setAge(user.getAge());newUser.setSalary(user.getSalary());returnuserDao.save(newUser); }}
Spring Data Implementation
Let us configure our DB connection parameters first. As we will be using MySQL in this app, below is our configuration properties. application.properties
Below is the repo class. It extends CrudRepository that has all the implementations for CRUD operations. For a detailed integration of Spring Data JPA, you can visit my another article here. UserDao.java
There are 2 different configurations for exception handling in the server side. To handle REST exception, we use @ControllerAdvice and @ExceptionHandler in Spring MVC but these handler works if the request is handled by the DispatcherServlet. However, security-related exceptions occur before that as it is thrown by Filters. Hence, it is required to insert a custom filter (RestAccessDeniedHandler and RestAuthenticationEntryPoint) earlier in the chain to catch the exception and return accordingly.
Full explanation of handling exception in spring security can be found here in my previous article. JwtAuthenticationEntryPoint.java
The exception handling part is out of scope for this article. In the production system, you will have your custom exception defined and you basically write your advice to handle those exceptions. Below implementation is just for a demo to get you started. Please let me know if you need a full-fledged solution to this.
Modern browsers does not allow cross browser HTTP request and you will see error stating that Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
To enable CORS request at our server, we can add below filter. It will act as a global filter.
In this article, we developed a full stack app with Angular 8 at the client-side, Spring Boot and Spring Data in the server-side and secured the REST endpoints with Spring security.