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.
Generating Angular Project from CLI
Generating Spring Boot Project
Spring Boot Maven Dependency
Below is our final pom.xml if you simply want to add dependencies in your existing project. pom.xml
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
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/internal/Observable";
import {Injectable} from "@angular/core";
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
intercept(request: HttpRequest, next: HttpHandler): Observable> {
let token = window.localStorage.getItem('token');
if (token) {
request = request.clone({
setHeaders: {
Authorization: 'Bearer ' + token
}
});
}
return next.handle(request);
}
}
Spring Boot Server-Side Implementation
Spring Boot JWT Security
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
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
if (header != null && header.startsWith(TOKEN_PREFIX)) {
authToken = header.replace(TOKEN_PREFIX,"");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
logger.error("an error occured during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.warn("the token is expired and not valid anymore", e);
} catch(SignatureException e){
logger.error("Authentication Failed. Username or Password not valid.");
}
} else {
logger.warn("couldn't find bearer string, will ignore the header");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(req, res);
}
}
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
@RestController
@RequestMapping("/token")
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserService userService;
@RequestMapping(value = "/generate-token", method = RequestMethod.POST)
public ResponseEntity register(@RequestBody LoginUser loginUser) throws AuthenticationException {
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginUser.getUsername(),
loginUser.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
final User user = userService.findOne(loginUser.getUsername());
final String token = jwtTokenUtil.generateToken(user);
return ResponseEntity.ok(new AuthToken(token));
}
}
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
package com.devglan.controller;
import com.devglan.model.ApiResponse;
import com.devglan.model.User;
import com.devglan.model.UserDto;
import com.devglan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ApiResponse saveUser(@RequestBody UserDto user){
return new ApiResponse<>(HttpStatus.OK.value(), "User saved successfully.",userService.save(user));
}
@GetMapping
public ApiResponse> listUser(){
return new ApiResponse<>(HttpStatus.OK.value(), "User list fetched successfully.",userService.findAll());
}
@GetMapping("/{id}")
public ApiResponse getOne(@PathVariable int id){
return new ApiResponse<>(HttpStatus.OK.value(), "User fetched successfully.",userService.findById(id));
}
@PutMapping("/{id}")
public ApiResponse update(@RequestBody UserDto userDto) {
return new ApiResponse<>(HttpStatus.OK.value(), "User updated successfully.",userService.update(userDto));
}
@DeleteMapping("/{id}")
public ApiResponse delete(@PathVariable int id) {
userService.delete(id);
return new ApiResponse<>(HttpStatus.OK.value(), "User deleted successfully.", null);
}
}
Spring Boot Service Implementation
UserServiceImpl.java
The service class has some business logic and it is the bridge between our controllers and DAOs.
@Transactional
@Service(value = "userService")
public class UserServiceImpl implements UserDetailsService, UserService {
@Autowired
private UserDao userDao;
@Autowired
private BCryptPasswordEncoder bcryptEncoder;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("Invalid username or password.");
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthority());
}
private List getAuthority() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
public List findAll() {
List list = new ArrayList<>();
userDao.findAll().iterator().forEachRemaining(list::add);
return list;
}
@Override
public void delete(int id) {
userDao.deleteById(id);
}
@Override
public User findOne(String username) {
return userDao.findByUsername(username);
}
@Override
public User findById(int id) {
Optional optionalUser = userDao.findById(id);
return optionalUser.isPresent() ? optionalUser.get() : null;
}
@Override
public UserDto update(UserDto userDto) {
User user = findById(userDto.getId());
if(user != null) {
BeanUtils.copyProperties(userDto, user, "password", "username");
userDao.save(user);
}
return userDto;
}
@Override
public User save(UserDto user) {
User newUser = new User();
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());
return userDao.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
@Repository
public interface UserDao extends CrudRepository {
User findByUsername(String username);
}
Spring Boot Model Class
Below is our User model class the UserDto class. User.java
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
@Column
private String firstName;
@Column
private String lastName;
@Column
private String username;
@Column
@JsonIgnore
private String password;
@Column
private long salary;
@Column
private int age;
//setters and getters
UserDto.java
public class UserDto {
private int id;
private String firstName;
private String lastName;
private String username;
private String password;
private int age;
private long salary;
//setters and getters
LoginUser.java
public class LoginUser {
private String username;
private String password;
//setters and getters
AuthToken.java
public class AuthToken {
private String token;
private String username;
//setters and getters
ApiResponse.java
public class ApiResponse {
private int status;
private String message;
private Object result;
public ApiResponse(int status, String message, Object result) {
this.status = status;
this.message = message;
this.result = result;
}
//setters and getters
Exception Handling at Server Side
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
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
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.
@RestControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(RuntimeException.class)
public ApiResponse handleNotFoundException(RuntimeException ex) {
ApiResponse apiResponse = new ApiResponse(400, "Bad request", null);
return apiResponse;
}
}
CORS Configuration for Spring Boot Angular
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.
We have command line runner integrated in the spring boot main class to add user during application start up.
@SpringBootApplication
public class Application {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner init(UserDao userDao){
return args -> {
User user1 = new User();
user1.setFirstName("Devglan");
user1.setLastName("Devglan");
user1.setSalary(12345);
user1.setAge(23);
user1.setUsername("devglan");
user1.setPassword(passwordEncoder.encode("devglan"));
userDao.save(user1);
User user2 = new User();
user2.setFirstName("John");
user2.setLastName("Doe");
user2.setSalary(4567);
user2.setAge(34);
user2.setUsername("john");
user2.setPassword(passwordEncoder.encode("john"));
userDao.save(user2);
};
}
}
Conclusion
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.