This is the final part of Authentication using JWT & Role-Based Access Control in Spring Boot & MYSQL.
In the controllers folder, create UserController.java file to define routes for login and registration APIs.
package com.dev.springtutorial.controllers; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.dev.springtutorial.auth.jwt.JwtUtils; import com.dev.springtutorial.auth.payload.Request.LoginRequest; import com.dev.springtutorial.auth.payload.Request.RegisterRequest; import com.dev.springtutorial.auth.payload.Response.JwtResponse; import com.dev.springtutorial.auth.payload.Response.MessageResponse; import com.dev.springtutorial.auth.services.MyUserDetails; import com.dev.springtutorial.models.ERole; import com.dev.springtutorial.models.Role; import com.dev.springtutorial.models.User; import com.dev.springtutorial.repos.RoleRepository; import com.dev.springtutorial.repos.UserRepository;
// allow APIs access from client apps usig diffeent domains @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/auth") public class UserController { @Autowired AuthenticationManager authenticationManager; @Autowired UserRepository userRepository; @Autowired RoleRepository roleRepository; @Autowired PasswordEncoder encoder; @Autowired JwtUtils jwtUtils; @PostMapping("/login") public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { // authenticate the user Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); // set authentication to current security context SecurityContextHolder.getContext().setAuthentication(authentication); // create token from the authentication String jwt = jwtUtils.generateJwtToken(authentication); // get user details from the authentication MyUserDetails userDetails = (MyUserDetails) authentication.getPrincipal(); // create role list Collection<? extends GrantedAuthority> authorities=userDetails.getAuthorities(); List<String> roles= new ArrayList<>(); for (GrantedAuthority grantedAuthority : authorities) { roles.add(grantedAuthority.getAuthority()); } // return jwt response return ResponseEntity.ok(new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), roles)); } @PostMapping("/register") public ResponseEntity<?> registerUser(@Valid @RequestBody RegisterRequest signUpRequest) { // check if the user already exists if (userRepository.existsByUsername(signUpRequest.getUsername())) { return ResponseEntity .badRequest() .body(new MessageResponse("Error: Username already exists!")); } if (userRepository.existsByEmail(signUpRequest.getEmail())) { return ResponseEntity .badRequest() .body(new MessageResponse("Error: Email is already in use!")); } // Create new user's account User user = new User(signUpRequest.getUsername(), signUpRequest.getEmail(), encoder.encode(signUpRequest.getPassword())); Set<String> strRoles = signUpRequest.getRole(); Set<Role> roles = new HashSet<>(); if (strRoles == null) { Role userRole = roleRepository.findByName(ERole.ROLE_USER) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(userRole); } else { strRoles.forEach(role -> { switch (role) { case "admin": Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(adminRole); break; default: Role userRole = roleRepository.findByName(ERole.ROLE_USER) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(userRole); } }); } user.setRoles(roles); userRepository.save(user); return ResponseEntity.ok(new MessageResponse("User registered successfully!")); } }
In the springtutorial folder, add security configuration file to configure Spring security in the system. In the configuration file, we define token filter to verify the token, authentication manager to process authentication request, filter security chains to secure request urls, and password encoder to specify bcrypt password encode. The prePostEnabled parameter of @EnableGlobalMethodSecurity method must be set to true to use @PreAuthorized annotation to define role-based access on APIs.
package com.dev.springtutorial; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.dev.springtutorial.auth.jwt.AuthTokenFilter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( prePostEnabled = true) // to use @PreAuthorize annotation
public class WebSecurityConfig { // specify which token filter to be used @Bean public AuthTokenFilter authenticationJwtTokenFilter() { return new AuthTokenFilter(); } @Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } //The SecurityFilterChain bean defines which URL paths should be secured and which should not @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { //Disables the AbstractHttpConfigurer by removing it. After doing so a fresh version of the configuration can be applied. http.cors().and().csrf().disable(); //Spring Security will never create an HttpSession and it will never use it to obtain the SecurityContext http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // URL paths should be secured and which not http .authorizeHttpRequests((requests) -> requests .antMatchers("/", "/home").permitAll() .antMatchers("/api/auth/**").permitAll() .antMatchers("/image/**").permitAll() .antMatchers("/uploads/**").permitAll() .anyRequest().authenticated() ) .addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } // use bcrypt password encoder @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
Execute the queries below to add sample data to roles table in MYSQL database:
INSERT INTO roles(name) VALUES ('ROLE_USER');
INSERT INTO roles(name) VALUES ('ROLE_ADMIN');
To apply role-based access control, we add @PreAuthorize annotation to the APIs handler mehtods. The products/all route is accessible to a logged user that has ROLE_USER or ROLE_ADMIN.
@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
public @ResponseBody Iterable<Product> getAllProducts() {
.....................
}
We allow product inserting, updating, and deleting to admin user only.
@PostMapping("/add") // POST Requests
@PreAuthorize("hasRole('ROLE_ADMIN')")
public @ResponseBody Product addPro ( ){
...............
}
@PutMapping("/update/{id}") // PUT Requests
@PreAuthorize("hasRole('ROLE_ADMIN')")
public @ResponseBody Product updatePro (){
.......
}
@DeleteMapping("/delete/{id}") // Map ONLY POST Requests
@PreAuthorize("hasRole('ROLE_ADMIN')")
public @ResponseBody void deletePro (@PathVariable int id) {
...................
}
While MYSQL is running, run the project. To register a new account, access http://localhost8080/api/auth/register. Then login via http://localhost8080/api/auth/login.
In the next tutorial, you will learn to create React front-end app to access the Spring Boot APIs.
Comments
Post a Comment