From 37a07882ab17bd7618df5b37c25be8cbd6fd712f Mon Sep 17 00:00:00 2001 From: Hanif Date: Wed, 3 Jan 2024 09:50:53 +0700 Subject: [PATCH] Add Service & Controllers Package --- .../auth/controllers/AuthController.java | 122 ++++++++++++++++++ .../auth/controllers/TestController.java | 30 +++++ .../modules/auth/service/UserDetailsImpl.java | 103 +++++++++++++++ .../auth/service/UserDetailsServiceImpl.java | 26 ++++ 4 files changed, 281 insertions(+) create mode 100644 src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/AuthController.java create mode 100644 src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/TestController.java create mode 100644 src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/service/UserDetailsImpl.java create mode 100644 src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/service/UserDetailsServiceImpl.java diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/AuthController.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/AuthController.java new file mode 100644 index 0000000..802ddc8 --- /dev/null +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/AuthController.java @@ -0,0 +1,122 @@ +package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.controllers; + +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.ERole; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.Role; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.User; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.request.LoginRequest; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.request.SignupRequest; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.response.MessageResponse; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.response.UserInfoResponse; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt.JwtUtils; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository.RoleRepository; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository.UserRepository; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.UserDetailsImpl; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +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.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("/api/auth") +public class AuthController { + + @Autowired + AuthenticationManager authenticationManager; + + @Autowired + UserRepository userRepository; + + @Autowired + RoleRepository roleRepository; + + @Autowired + PasswordEncoder encoder; + + @Autowired + JwtUtils jwtUtils; + + @PostMapping("/signup") + public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signUpRequest) { + if (userRepository.existsByUsername(signUpRequest.getUsername())) { + return ResponseEntity.badRequest().body(new MessageResponse("Error: Username is already taken!")); + } + + if (userRepository.existsByEmail(signUpRequest.getEmail())) { + return ResponseEntity.badRequest().body(new MessageResponse("Error: Email is already in use!")); + } + + User user = new User(signUpRequest.getUsername(), + signUpRequest.getEmail(), + encoder.encode(signUpRequest.getPassword())); + + Set strRoles = signUpRequest.getRole(); + Set 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 -> { + if (role.equals("admin")) { + Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN) + .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); + roles.add(adminRole); + } else { + 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!")); + } + + + @PostMapping("/signin") + public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { + + Authentication authentication = authenticationManager + .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); + + ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails); + + List roles = userDetails.getAuthorities().stream() + .map(item -> item.getAuthority()) + .collect(Collectors.toList()); + + return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) + .body(new UserInfoResponse(userDetails.getId(), + userDetails.getUsername(), + userDetails.getEmail(), + roles)); + } + + @PostMapping("/signout") + public ResponseEntity logoutUser() { + ResponseCookie cookie = jwtUtils.getCleanJwtCookie(); + return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString()) + .body(new MessageResponse("You've been signed out!")); + } +} diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/TestController.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/TestController.java new file mode 100644 index 0000000..6b9b44e --- /dev/null +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/TestController.java @@ -0,0 +1,30 @@ +package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.controllers; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("/api/test") +public class TestController { + + @GetMapping("/all") + public String allAccess() { + return "Public Content."; + } + + @GetMapping("/user") + @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") + public String userAccess() { + return "User Content."; + } + + @GetMapping("/admin") + @PreAuthorize("hasRole('ADMIN')") + public String adminAccess() { + return "Admin Board."; + } +} diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/service/UserDetailsImpl.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/service/UserDetailsImpl.java new file mode 100644 index 0000000..43ba26f --- /dev/null +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/service/UserDetailsImpl.java @@ -0,0 +1,103 @@ +package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.User; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class UserDetailsImpl implements UserDetails { + + private static final long serialVersionUID = 1L; + + private Long id; + + private String username; + + private String email; + + @JsonIgnore + private String password; + + private Collection authorities; + + public UserDetailsImpl(Long id, String username, String email, String password, Collection authorities) { + this.id = id; + this.username = username; + this.email = email; + this.password = password; + this.authorities = authorities; + } + + public static UserDetailsImpl build(User user) { + List authorities = user.getRoles().stream() + .map(role -> new SimpleGrantedAuthority(role.getName().name())) + .collect(Collectors.toList()); + + return new UserDetailsImpl( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getPassword(), + authorities + ); + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + UserDetailsImpl user = (UserDetailsImpl) o; + return Objects.equals(id, user.id); + } +} diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/service/UserDetailsServiceImpl.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..e4b352e --- /dev/null +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/service/UserDetailsServiceImpl.java @@ -0,0 +1,26 @@ +package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service; + +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.User; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + @Autowired + UserRepository userRepository; + + @Override + @Transactional + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username)); + + return UserDetailsImpl.build(user); + } +}