diff --git a/batch-quartz/src/main/java/com/spring/common/error/GlobalErrorController.java b/batch-quartz/src/main/java/com/spring/common/error/GlobalErrorController.java index 4634660..3210944 100644 --- a/batch-quartz/src/main/java/com/spring/common/error/GlobalErrorController.java +++ b/batch-quartz/src/main/java/com/spring/common/error/GlobalErrorController.java @@ -7,6 +7,7 @@ import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; @@ -17,9 +18,11 @@ public class GlobalErrorController implements ErrorController { @GetMapping("/error") public String handleError(HttpServletRequest request, Model model) { Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + String errorMessage = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_MESSAGE)); String statusMsg = status.toString(); HttpStatus httpStatus = HttpStatus.valueOf(Integer.valueOf(statusMsg)); model.addAttribute("message", statusMsg + " " + httpStatus.getReasonPhrase()); + if (StringUtils.hasText(errorMessage)) model.addAttribute("errorMessage", errorMessage); return "pages/error/error"; } diff --git a/batch-quartz/src/main/java/com/spring/common/advice/ApiResponse.java b/batch-quartz/src/main/java/com/spring/common/support/ApiResponse.java similarity index 93% rename from batch-quartz/src/main/java/com/spring/common/advice/ApiResponse.java rename to batch-quartz/src/main/java/com/spring/common/support/ApiResponse.java index e21d934..9042d68 100644 --- a/batch-quartz/src/main/java/com/spring/common/advice/ApiResponse.java +++ b/batch-quartz/src/main/java/com/spring/common/support/ApiResponse.java @@ -1,4 +1,4 @@ -package com.spring.common.advice; +package com.spring.common.support; import java.time.LocalDateTime; diff --git a/batch-quartz/src/main/java/com/spring/common/advice/ResponseWrapper.java b/batch-quartz/src/main/java/com/spring/common/support/ResponseWrapper.java similarity index 80% rename from batch-quartz/src/main/java/com/spring/common/advice/ResponseWrapper.java rename to batch-quartz/src/main/java/com/spring/common/support/ResponseWrapper.java index 1793b86..47c8abe 100644 --- a/batch-quartz/src/main/java/com/spring/common/advice/ResponseWrapper.java +++ b/batch-quartz/src/main/java/com/spring/common/support/ResponseWrapper.java @@ -1,4 +1,4 @@ -package com.spring.common.advice; +package com.spring.common.support; import org.springframework.core.MethodParameter; import org.springframework.http.HttpStatus; @@ -8,6 +8,9 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @@ -15,6 +18,7 @@ import com.spring.common.converter.CommHttpMessageConverter; import com.spring.common.error.ErrorResponse; import com.spring.common.error.ErrorRule; import com.spring.common.error.GlobalExceptionHandler; +import com.spring.common.validation.CollectionValidator; import com.spring.infra.security.error.SecurityExceptionHandler; @RestControllerAdvice( @@ -22,6 +26,17 @@ import com.spring.infra.security.error.SecurityExceptionHandler; basePackageClasses = { GlobalExceptionHandler.class, SecurityExceptionHandler.class } ) public class ResponseWrapper implements ResponseBodyAdvice { + + private final LocalValidatorFactoryBean validator; + + public ResponseWrapper(LocalValidatorFactoryBean validator) { + this.validator = validator; + } + + @InitBinder + public void initBinder(WebDataBinder binder) { + binder.addValidators(new CollectionValidator(validator)); + } @Override public boolean supports( diff --git a/batch-quartz/src/main/java/com/spring/common/validation/CollectionValidator.java b/batch-quartz/src/main/java/com/spring/common/validation/CollectionValidator.java new file mode 100644 index 0000000..853384d --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/common/validation/CollectionValidator.java @@ -0,0 +1,32 @@ +package com.spring.common.validation; + +import java.util.Collection; + +import org.springframework.lang.NonNull; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +public class CollectionValidator implements Validator { + private final Validator validator; + + public CollectionValidator(LocalValidatorFactoryBean validatorFactory) { + this.validator = validatorFactory; + } + + @Override + public boolean supports(@NonNull Class clazz) { + return true; + } + + @Override + public void validate(@NonNull Object target, @NonNull Errors errors) { + if (target instanceof Collection) { + Collection collection = (Collection) target; + for (Object object : collection) { + ValidationUtils.invokeValidator(validator, object, errors); + } + } + } +} diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/api/ScheduleJobApi.java b/batch-quartz/src/main/java/com/spring/domain/schedule/api/ScheduleJobApi.java index 0b49dde..5d251c9 100644 --- a/batch-quartz/src/main/java/com/spring/domain/schedule/api/ScheduleJobApi.java +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/api/ScheduleJobApi.java @@ -4,6 +4,7 @@ import java.util.List; import javax.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -30,31 +31,37 @@ public class ScheduleJobApi { private final ScheduleControlService scheduleControlService; @GetMapping + @PreAuthorize("hasAnyRole('SUPER', 'ADMIN')") public List getAllJobs(@RequestParam(required = false) String groupName, @RequestParam(required = false) String jobName) { return findScheduleJobService.getAllJobs(groupName, jobName); } @GetMapping("/{groupName}/{jobName}") + @PreAuthorize("hasAnyRole('SUPER', 'ADMIN')") public ScheduleJobResponse getJobDetail(@PathVariable String groupName, @PathVariable String jobName) { return findScheduleJobService.getJobDetail(groupName, jobName); } @GetMapping("/pause/{groupName}/{jobName}") + @PreAuthorize("hasAnyRole('SUPER', 'ADMIN')") public void pauseJob(@PathVariable String groupName, @PathVariable String jobName) { scheduleControlService.pauseJob(groupName, jobName); } @GetMapping("/resume/{groupName}/{jobName}") + @PreAuthorize("hasAnyRole('SUPER', 'ADMIN')") public void resumeJob(@PathVariable String groupName, @PathVariable String jobName) { scheduleControlService.resumeJob(groupName, jobName); } @GetMapping("/trigger/{groupName}/{jobName}") + @PreAuthorize("hasAnyRole('SUPER', 'ADMIN')") public void triggerJob(@PathVariable String groupName, @PathVariable String jobName) { scheduleControlService.triggerJob(groupName, jobName); } @PostMapping("/reschedule") + @PreAuthorize("hasAnyRole('SUPER', 'ADMIN')") public boolean rescheduleJob(@Valid @RequestBody ReScheduleJobRequest request) { return reScheduleJobService.rescheduleJob(request); } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/api/UserManagementApi.java b/batch-quartz/src/main/java/com/spring/domain/user/api/UserManagementApi.java index bcf1deb..0a1b84e 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/api/UserManagementApi.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/api/UserManagementApi.java @@ -4,7 +4,10 @@ import java.util.List; import javax.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -25,13 +28,21 @@ public class UserManagementApi { private final UserManagementService userManagementService; @GetMapping + @PreAuthorize("hasAnyRole('SUPER')") public List getUsers(UserFindRequest request) { return userManagementService.getUsers(request); } @PutMapping("/change-role-approve") + @PreAuthorize("hasAnyRole('SUPER')") public void changeRoleApprove(@RequestBody @Valid List requests) { userManagementService.changeRoleApprove(requests); } + @DeleteMapping("/{id}") + @PreAuthorize("hasAnyRole('SUPER')") + public void deleteUser(@PathVariable String id) { + userManagementService.deleteUser(id); + } + } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/dto/ChangeUserRoleApproveRequest.java b/batch-quartz/src/main/java/com/spring/domain/user/dto/ChangeUserRoleApproveRequest.java index a21677b..5a2b1dd 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/dto/ChangeUserRoleApproveRequest.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/dto/ChangeUserRoleApproveRequest.java @@ -20,6 +20,6 @@ public class ChangeUserRoleApproveRequest { private final AgentUserRole userRole; @NotNull(message = "승인 여부는 필수값 입니다.") - private final boolean isApproved; + private final boolean approved; } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/dto/SignUpRequest.java b/batch-quartz/src/main/java/com/spring/domain/user/dto/SignUpRequest.java index ea0b04d..7de5b8d 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/dto/SignUpRequest.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/dto/SignUpRequest.java @@ -38,10 +38,10 @@ public class SignUpRequest { public AgentUser toEntity() { return AgentUser.builder() .userId(userId) - .userPassword(userPassword) - .userName(userName) + .password(userPassword) + .name(userName) .email(email) - .isApproved(false) + .approved(false) .userRole(userRole) .build(); } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/dto/UserFindRequest.java b/batch-quartz/src/main/java/com/spring/domain/user/dto/UserFindRequest.java index ab79208..63be682 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/dto/UserFindRequest.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/dto/UserFindRequest.java @@ -26,7 +26,7 @@ public class UserFindRequest { } public static boolean matchesUserName(AgentUser user, String userName) { - return userName == null || userName.isEmpty() || user.getMemberName().contains(userName); + return userName == null || userName.isEmpty() || user.getName().contains(userName); } public static boolean matchesEmail(AgentUser user, String email) { diff --git a/batch-quartz/src/main/java/com/spring/domain/user/dto/UserManagementResponse.java b/batch-quartz/src/main/java/com/spring/domain/user/dto/UserManagementResponse.java index 257edbf..37ae4e3 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/dto/UserManagementResponse.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/dto/UserManagementResponse.java @@ -21,7 +21,7 @@ public class UserManagementResponse { return new UserManagementResponse( user.getId().toString(), user.getUserId(), - user.getMemberName(), + user.getName(), user.getEmail(), user.isApproved(), user.getUserRole() diff --git a/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java index c5958eb..0bb7e8a 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java @@ -1,9 +1,6 @@ package com.spring.domain.user.entity; -import java.util.Arrays; -import java.util.Collection; import java.util.UUID; -import java.util.stream.Collectors; import javax.persistence.Column; import javax.persistence.Entity; @@ -14,10 +11,6 @@ import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.annotations.GenericGenerator; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import com.spring.infra.security.domain.UserPrincipal; import lombok.AccessLevel; import lombok.Builder; @@ -28,7 +21,7 @@ import lombok.NoArgsConstructor; @Table(name = "AGENT_USER") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AgentUser implements UserPrincipal { +public class AgentUser { @Id @GeneratedValue(generator = "uuid2") @@ -39,91 +32,42 @@ public class AgentUser implements UserPrincipal { @Column(name = "USER_ID", nullable = false, length = 50) private String userId; - @Column(name = "USER_PASSWORD", nullable = false, length = 128) - private String userPassword; + @Column(name = "PASSWORD", nullable = false, length = 128) + private String password; - @Column(name = "USER_NAME", nullable = false, length = 50) - private String userName; + @Column(name = "NAME", nullable = false, length = 50) + private String name; @Column(name = "EMAIL", nullable = false, length = 100) private String email; - @Column(name = "IS_APPROVED", nullable = false) - private boolean isApproved; + @Column(name = "APPROVED", nullable = false) + private boolean approved; @Enumerated(EnumType.STRING) @Column(name = "USER_ROLE", nullable = false, length = 50) private AgentUserRole userRole; @Builder - public AgentUser(String userId, String userPassword, String userName, AgentUserRole userRole, String email, boolean isApproved) { + public AgentUser(String userId, String password, String name, AgentUserRole userRole, String email, boolean approved) { this.userId = userId; - this.userPassword = userPassword; - this.userName = userName; + this.password = password; + this.name = name; this.userRole = userRole; this.email = email; - this.isApproved = isApproved; + this.approved = approved; } public void changePassword(String newPassword) { - this.userPassword = newPassword; + this.password = newPassword; } public void changeUserRole(AgentUserRole userRole) { this.userRole = userRole; } - public void changeApproved(boolean isApproved) { - this.isApproved = isApproved; - } - - @Override - public Collection getAuthorities() { - return Arrays.stream(AgentUserRole.values()) - .filter(role -> Arrays.asList(this.userRole).contains(role)) - .map(AgentUserRole::getRole) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); - } - - @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 String getPassword() { - return this.userPassword; - } - - @Override - public String getUsername() { - return this.userId; - } - - @Override - public String getKey() { - return this.id.toString(); - } - - @Override - public String getMemberName() { - return this.userName; + public void changeApproved(boolean approved) { + this.approved = approved; } } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/service/UserManagementService.java b/batch-quartz/src/main/java/com/spring/domain/user/service/UserManagementService.java index 192ff1c..7161ff4 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/service/UserManagementService.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/service/UserManagementService.java @@ -40,4 +40,11 @@ public class UserManagementService { } } + @Transactional + public void deleteUser(String id) { + AgentUser user = agentUserRepository.findById(UUID.fromString(id)) + .orElseThrow(UserNotFoundException::new); + agentUserRepository.delete(user); + } + } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/service/UserPrincipalService.java b/batch-quartz/src/main/java/com/spring/domain/user/service/UserPrincipalService.java index 9852802..441020e 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/service/UserPrincipalService.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/service/UserPrincipalService.java @@ -25,15 +25,18 @@ public class UserPrincipalService implements UserDetailsService, UserAuthenticat @Transactional(readOnly = true) @Override public UserPrincipal loadUserByUsername(String username) throws UsernameNotFoundException { - return agentUserRepository.findByUserId(username) - .orElseThrow(() -> new SecurityAuthException(SecurityExceptionRule.USER_UNAUTHORIZED)); + return UserPrincipal.valueOf( + agentUserRepository.findByUserId(username) + .orElseThrow(() -> new SecurityAuthException(SecurityExceptionRule.USER_UNAUTHORIZED)) + ); } @Transactional(readOnly = true) @Override public UserPrincipal getUserDetails(String key) { - return agentUserRepository.findById(UUID.fromString(key)) - .orElseThrow(UserNotFoundException::new); + return UserPrincipal.valueOf( + agentUserRepository.findById(UUID.fromString(key)).orElseThrow(UserNotFoundException::new) + ); } } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java b/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java index 5fc2de6..6489d72 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java @@ -12,6 +12,7 @@ import org.springframework.security.config.annotation.method.configuration.Enabl import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; @@ -71,6 +72,7 @@ public class SecurityConfig { .csrf(CsrfConfigurer::disable) .httpBasic(HttpBasicConfigurer::disable) .formLogin(FormLoginConfigurer::disable) + .anonymous(AnonymousConfigurer::disable) .authorizeHttpRequests(auth -> auth .antMatchers(Arrays.stream(PermittedURI.values()).map(PermittedURI::getUri).toArray(String[]::new)).permitAll() .anyRequest().authenticated() diff --git a/batch-quartz/src/main/java/com/spring/infra/security/domain/JwtUserPrincipal.java b/batch-quartz/src/main/java/com/spring/infra/security/domain/JwtUserPrincipal.java deleted file mode 100644 index 6bceb6e..0000000 --- a/batch-quartz/src/main/java/com/spring/infra/security/domain/JwtUserPrincipal.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.spring.infra.security.domain; - -import java.util.Collection; -import java.util.Collections; - -import org.springframework.security.core.GrantedAuthority; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class JwtUserPrincipal implements UserPrincipal { - - private final String userId; - private final String userName; - - @Override - public String getKey() { - return null; - } - - @Override - public String getMemberName() { - return null; - } - - @Override - public Collection getAuthorities() { - return Collections.emptyList(); - } - - @Override - public String getPassword() { - return null; - } - - @Override - public String getUsername() { - return userName; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - -} diff --git a/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java b/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java index a2d3950..e1ee578 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java @@ -1,11 +1,78 @@ package com.spring.infra.security.domain; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -public interface UserPrincipal extends UserDetails { - - String getKey(); +import com.spring.domain.user.entity.AgentUser; +import com.spring.domain.user.entity.AgentUserRole; - String getMemberName(); +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class UserPrincipal implements UserDetails { + + private final transient AgentUser agentUser; + + public static UserPrincipal valueOf(AgentUser agentUser) { + return new UserPrincipal(agentUser); + } + + public static UserPrincipal of(String userId, String userName) { + return new UserPrincipal(AgentUser.builder().userId(userId).name(userName).build()); + } + + @Override + public Collection getAuthorities() { + return Arrays.stream(AgentUserRole.values()) + .filter(role -> Arrays.asList(agentUser.getUserRole()).contains(role)) + .map(AgentUserRole::getRole) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + + @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 String getPassword() { + return agentUser.getPassword(); + } + + @Override + public String getUsername() { + return agentUser.getUserId(); + } + + public String getKey() { + return agentUser.getId().toString(); + } + + public String getMemberName() { + return agentUser.getName(); + } } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityAuthException.java b/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityAuthException.java index 70f8c6e..afd47f5 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityAuthException.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityAuthException.java @@ -9,6 +9,11 @@ public class SecurityAuthException extends AuthenticationException { private final SecurityExceptionRule exceptionRule; + public SecurityAuthException() { + super(SecurityExceptionRule.SYSTEM_ERROR.getMessage()); + this.exceptionRule = SecurityExceptionRule.SYSTEM_ERROR; + } + public SecurityAuthException(String msg) { super(msg); this.exceptionRule = SecurityExceptionRule.SYSTEM_ERROR; diff --git a/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityExceptionHandler.java b/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityExceptionHandler.java index da594c8..47a2d57 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityExceptionHandler.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityExceptionHandler.java @@ -1,27 +1,33 @@ package com.spring.infra.security.error; +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.security.SignatureException; +import lombok.RequiredArgsConstructor; @RestControllerAdvice +@RequiredArgsConstructor public class SecurityExceptionHandler { + private final AccessDeniedHandler accessDeniedHandler; + @ExceptionHandler(SecurityAuthException.class) public SecurityErrorResponse handleAuthenticationException(SecurityAuthException e) { return SecurityErrorResponse.valueOf(e.getExceptionRule()); } - @ExceptionHandler(AccessDeniedException.class) - public SecurityErrorResponse handleAccessDeniedException() { - return SecurityErrorResponse.valueOf(SecurityExceptionRule.USER_FORBIDDEN); - } - @ExceptionHandler(AuthenticationException.class) public SecurityErrorResponse handleAuthenticationException() { return SecurityErrorResponse.valueOf(SecurityExceptionRule.USER_UNAUTHORIZED); @@ -31,15 +37,22 @@ public class SecurityExceptionHandler { public SecurityErrorResponse handleSignatureException() { return SecurityErrorResponse.valueOf(SecurityExceptionRule.SIGNATURE_ERROR); } - + @ExceptionHandler(MalformedJwtException.class) public SecurityErrorResponse handleMalformedJwtException() { return SecurityErrorResponse.valueOf(SecurityExceptionRule.MALFORMED_JWT_ERROR); } - + @ExceptionHandler(ExpiredJwtException.class) public SecurityErrorResponse handleExpiredJwtException() { return SecurityErrorResponse.valueOf(SecurityExceptionRule.EXPIRED_JWT_ERROR); } + @ExceptionHandler(AccessDeniedException.class) + public void handleAccessDeniedException( + AccessDeniedException ex, HttpServletRequest request, HttpServletResponse response + ) throws IOException, ServletException { + accessDeniedHandler.handle(request, response, ex); + } + } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/filter/AuthenticationProcessingFilter.java b/batch-quartz/src/main/java/com/spring/infra/security/filter/AuthenticationProcessingFilter.java index 5cba89a..1141b22 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/filter/AuthenticationProcessingFilter.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/filter/AuthenticationProcessingFilter.java @@ -17,18 +17,17 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.StringUtils; import com.fasterxml.jackson.databind.ObjectMapper; -import com.spring.domain.user.entity.AgentUser; import com.spring.infra.security.config.PermittedURI; +import com.spring.infra.security.domain.UserPrincipal; import com.spring.infra.security.dto.SignInRequest; import com.spring.infra.security.error.SecurityAuthException; import com.spring.infra.security.error.SecurityExceptionRule; public class AuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { - private static final String DEFAULT_LOGIN_REQUEST_URL = PermittedURI.USER_SIGN_IN.getUri(); + private static final String DEFAULT_LOGIN_REQUEST_URI = PermittedURI.USER_SIGN_IN.getUri(); private static final String HTTP_METHOD = "POST"; - private static final AntPathRequestMatcher DEFAULT_LOGIN_PATH_REQUEST_MATCHER = - new AntPathRequestMatcher(DEFAULT_LOGIN_REQUEST_URL, HTTP_METHOD); + private static final AntPathRequestMatcher DEFAULT_LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(DEFAULT_LOGIN_REQUEST_URI, HTTP_METHOD); private final ObjectMapper objectMapper; @@ -38,8 +37,11 @@ public class AuthenticationProcessingFilter extends AbstractAuthenticationProces } @Override - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) - throws AuthenticationException, IOException, ServletException { + public Authentication attemptAuthentication( + HttpServletRequest request, + HttpServletResponse response + ) throws AuthenticationException, IOException, ServletException { + if (!isValidRequestType(request)) { throw new SecurityAuthException(SecurityExceptionRule.UNSUPPORTED_MEDIA_ERROR); } @@ -49,9 +51,14 @@ public class AuthenticationProcessingFilter extends AbstractAuthenticationProces } var token = new UsernamePasswordAuthenticationToken(signInRequest.getUsername(), signInRequest.getPassword()); var authentication = this.getAuthenticationManager().authenticate(token); - if (!((AgentUser) authentication.getPrincipal()).isApproved()) { - throw new SecurityAuthException(SecurityExceptionRule.USER_NOT_APPROVED); + + if (authentication.getPrincipal() instanceof UserPrincipal) { + UserPrincipal user = (UserPrincipal) authentication.getPrincipal(); + if (!user.getAgentUser().isApproved()) { + throw new SecurityAuthException(SecurityExceptionRule.USER_NOT_APPROVED); + } } + return authentication; } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/filter/JwtAuthenticationFilter.java b/batch-quartz/src/main/java/com/spring/infra/security/filter/JwtAuthenticationFilter.java index 689524d..f845b82 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/filter/JwtAuthenticationFilter.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/filter/JwtAuthenticationFilter.java @@ -13,7 +13,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.lang.NonNull; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.AntPathMatcher; import org.springframework.web.filter.OncePerRequestFilter; import com.spring.infra.security.config.PermittedURI; @@ -33,7 +32,6 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public final class JwtAuthenticationFilter extends OncePerRequestFilter { - private final AntPathMatcher pathMatcher = new AntPathMatcher(); private final JwtTokenService jwtTokenService; private static final String EXCEPTION_ATTRIBUTE = "exception"; @@ -53,9 +51,7 @@ public final class JwtAuthenticationFilter extends OncePerRequestFilter { @NonNull FilterChain filterChain ) throws ServletException, IOException { - String requestURI = request.getRequestURI(); - if (Arrays.stream(PermittedURI.values()).map(PermittedURI::getUri).anyMatch(uri -> pathMatcher.match(uri, requestURI)) && - !PermittedURI.ROOT_URI.getUri().equals(requestURI)) { + if (isPermittedURI(request.getRequestURI())) { filterChain.doFilter(request, response); return; } @@ -64,23 +60,20 @@ public final class JwtAuthenticationFilter extends OncePerRequestFilter { String accessToken = parseBearerToken(request, HttpHeaders.AUTHORIZATION); if (jwtTokenService.validateAccessToken(accessToken)) { setAuthenticationToContext(accessToken); - return; + } else { + String refreshToken = jwtTokenService.resolveTokenFromCookie(request, JwtTokenRule.REFRESH_PREFIX); + jwtTokenService.validateToken(refreshToken); + String reissuedAccessToken = jwtTokenService.getRefreshToken(refreshToken); + Authentication authentication = jwtTokenService.getAuthentication(reissuedAccessToken); + jwtTokenService.saveRefreshToken(authentication.getName(), jwtTokenService.generateRefreshToken(response, authentication)); + setAuthenticationToContext(jwtTokenService.generateAccessToken(response, authentication)); } - - String refreshToken = jwtTokenService.resolveTokenFromCookie(request, JwtTokenRule.REFRESH_PREFIX); - jwtTokenService.validateToken(refreshToken); - - String reissuedAccessToken = jwtTokenService.getRefreshToken(refreshToken); - Authentication authentication = jwtTokenService.getAuthentication(reissuedAccessToken); - jwtTokenService.saveRefreshToken(authentication.getName(), jwtTokenService.generateRefreshToken(response, authentication)); - - setAuthenticationToContext(jwtTokenService.generateAccessToken(response, authentication)); } catch (Exception e) { jwtTokenService.deleteCookie(response); request.setAttribute(EXCEPTION_ATTRIBUTE, e); - } finally { - filterChain.doFilter(request, response); } + + filterChain.doFilter(request, response); } @@ -101,4 +94,11 @@ public final class JwtAuthenticationFilter extends OncePerRequestFilter { .orElse(jwtTokenService.resolveTokenFromCookie(request, JwtTokenRule.ACCESS_PREFIX)); } + private boolean isPermittedURI(String requestURI) { + return Arrays.stream(PermittedURI.values()) + .map(PermittedURI::getUri) + .anyMatch(uri -> uri.equals(requestURI)) && + !PermittedURI.ROOT_URI.getUri().equals(requestURI); + } + } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAccessDeniedHandler.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAccessDeniedHandler.java index a4afcc3..846cb08 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAccessDeniedHandler.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAccessDeniedHandler.java @@ -2,16 +2,24 @@ package com.spring.infra.security.handler; import java.io.IOException; +import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; +import com.spring.infra.security.error.SecurityAuthException; +import com.spring.infra.security.error.SecurityExceptionRule; + +import lombok.RequiredArgsConstructor; + /** * JWT 인증에서 접근 거부 상황을 처리하는 핸들러 클래스입니다. * @@ -22,30 +30,29 @@ import org.springframework.web.servlet.HandlerExceptionResolver; * @version 1.0 */ @Component +@RequiredArgsConstructor public class SecurityAccessDeniedHandler implements AccessDeniedHandler { - private final HandlerExceptionResolver resolver; - - public SecurityAccessDeniedHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) { - this.resolver = resolver; - } - - /** - * 접근 거부 상황을 처리합니다. - * - *

사용자가 접근 권한이 없는 리소스에 접근을 시도할 때 호출됩니다. - * 이 메소드는 SC_FORBIDDEN (403) 상태 코드를 응답으로 전송합니다.

- * - * @param request 현재 HTTP 요청 - * @param response 현재 HTTP 응답 - * @param accessDeniedException 발생한 접근 거부 예외 - * @throws IOException 입출력 예외 발생 시 - * @throws ServletException 서블릿 예외 발생 시 - */ + private final ApplicationContext applicationContext; + @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { - resolver.resolveException(request, response, null, accessDeniedException); + if (isApiRequest(request)) { + HandlerExceptionResolver resolver = applicationContext.getBean("handlerExceptionResolver", HandlerExceptionResolver.class); + resolver.resolveException(request, response, null, new SecurityAuthException(SecurityExceptionRule.USER_FORBIDDEN)); + } else { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpServletResponse.SC_FORBIDDEN); + request.setAttribute(RequestDispatcher.ERROR_MESSAGE, SecurityExceptionRule.USER_FORBIDDEN.getMessage()); + RequestDispatcher dispatcher = request.getRequestDispatcher("/error"); + dispatcher.forward(request, response); + } + } + + private boolean isApiRequest(HttpServletRequest request) { + String accept = request.getHeader(HttpHeaders.ACCEPT); + return accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE); } } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java index dadc1a8..ebc7c74 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java @@ -35,8 +35,7 @@ public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoin private final HttpRequestEndpointChecker endpointChecker; @Override - public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { if (!endpointChecker.isEndpointExist(request)) { response.sendError(HttpServletResponse.SC_NOT_FOUND); } else if (isApiRequest(request)) { diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninFailureHandler.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninFailureHandler.java index 740df27..2496780 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninFailureHandler.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninFailureHandler.java @@ -12,15 +12,15 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; +import lombok.RequiredArgsConstructor; + @Component +@RequiredArgsConstructor public class SigninFailureHandler implements AuthenticationFailureHandler { + @Qualifier("handlerExceptionResolver") private final HandlerExceptionResolver resolver; - public SigninFailureHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) { - this.resolver = resolver; - } - @Override public void onAuthenticationFailure( HttpServletRequest request, diff --git a/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java b/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java index 82724f0..25debee 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java @@ -18,7 +18,7 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.spring.infra.security.domain.JwtUserPrincipal; +import com.spring.infra.security.domain.UserPrincipal; import com.spring.infra.security.error.SecurityAuthException; import com.spring.infra.security.error.SecurityExceptionRule; import com.spring.infra.security.service.RefreshTokenService; @@ -173,7 +173,7 @@ public class JwtTokenService { .map(String::valueOf) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); - return new UsernamePasswordAuthenticationToken(new JwtUserPrincipal(userId, userName), "", auths); + return new UsernamePasswordAuthenticationToken(UserPrincipal.of(userId, userName), "", auths); } /** diff --git a/batch-quartz/src/main/java/com/spring/web/advice/GlobalControllerAdvice.java b/batch-quartz/src/main/java/com/spring/web/advice/GlobalControllerAdvice.java deleted file mode 100644 index 2cf47da..0000000 --- a/batch-quartz/src/main/java/com/spring/web/advice/GlobalControllerAdvice.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.spring.web.advice; - -import java.util.HashMap; -import java.util.Map; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ModelAttribute; - -import com.spring.infra.security.domain.JwtUserPrincipal; -import com.spring.infra.security.domain.UserPrincipal; - -@ControllerAdvice(basePackages = "com.spring.web.controller") -public class GlobalControllerAdvice { - - @ModelAttribute("userInfo") - public Map userInfo() { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - Map userInfo = new HashMap<>(); - if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof UserPrincipal) { - JwtUserPrincipal user = (JwtUserPrincipal) auth.getPrincipal(); - userInfo.put("userId", user.getUserId()); - userInfo.put("userName", user.getUsername()); - } else { - userInfo.put("userId", ""); - userInfo.put("userName", ""); - } - return userInfo; - } - -} diff --git a/batch-quartz/src/main/java/com/spring/web/constant/Menus.java b/batch-quartz/src/main/java/com/spring/web/constant/Menus.java new file mode 100644 index 0000000..46b4658 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/web/constant/Menus.java @@ -0,0 +1,46 @@ +package com.spring.web.constant; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import com.spring.domain.user.entity.AgentUserRole; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Menus { + + DASHBOARD( + "/dashboard", + "Dashboard", + "bi bi-grid", + List.of(AgentUserRole.ROLE_SUPER, AgentUserRole.ROLE_ADMIN, AgentUserRole.ROLE_USER) + ), + SCHEDULE( + "/schedule", + "Schedule", + "bi bi-menu-button-wide", + List.of(AgentUserRole.ROLE_SUPER, AgentUserRole.ROLE_ADMIN) + ), + USER_MANAGEMENT( + "/user/management", + "User Management", + "bi bi-person", + List.of(AgentUserRole.ROLE_SUPER) + ); + + private final String menuUri; + private final String menuName; + private final String menuIcon; + private final List roles; + + public static List fromRole(AgentUserRole role) { + return Arrays.stream(values()) + .filter(menu -> menu.roles.contains(role)) + .collect(Collectors.toList()); + } + +} diff --git a/batch-quartz/src/main/java/com/spring/web/controller/ScheduleController.java b/batch-quartz/src/main/java/com/spring/web/controller/ScheduleController.java index e503359..0bd6dea 100644 --- a/batch-quartz/src/main/java/com/spring/web/controller/ScheduleController.java +++ b/batch-quartz/src/main/java/com/spring/web/controller/ScheduleController.java @@ -1,5 +1,6 @@ package com.spring.web.controller; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -9,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestMapping; public class ScheduleController { @GetMapping + @PreAuthorize("hasAnyRole('SUPER', 'ADMIN')") public String schedule() { return "pages/schedule/schedule"; } diff --git a/batch-quartz/src/main/java/com/spring/web/controller/UserController.java b/batch-quartz/src/main/java/com/spring/web/controller/UserController.java index 597d450..50d15be 100644 --- a/batch-quartz/src/main/java/com/spring/web/controller/UserController.java +++ b/batch-quartz/src/main/java/com/spring/web/controller/UserController.java @@ -1,8 +1,6 @@ package com.spring.web.controller; -import java.util.List; -import java.util.stream.Collectors; - +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -15,11 +13,9 @@ import com.spring.domain.user.entity.AgentUserRole; public class UserController { @GetMapping("/management") + @PreAuthorize("hasAnyRole('SUPER')") public String management(Model model) { - List roles = List.of(AgentUserRole.values()).stream() - .filter(role -> !role.getRole().equals(AgentUserRole.ROLE_SUPER.name())) - .collect(Collectors.toList()); - model.addAttribute("roles", roles); + model.addAttribute("roles", AgentUserRole.values()); return "pages/user/user-management"; } diff --git a/batch-quartz/src/main/java/com/spring/web/support/GlobalControllerAdvice.java b/batch-quartz/src/main/java/com/spring/web/support/GlobalControllerAdvice.java new file mode 100644 index 0000000..e8a85f4 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/web/support/GlobalControllerAdvice.java @@ -0,0 +1,47 @@ +package com.spring.web.support; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ModelAttribute; + +import com.spring.domain.user.entity.AgentUserRole; +import com.spring.infra.security.domain.UserPrincipal; +import com.spring.web.constant.Menus; + +@ControllerAdvice(basePackages = "com.spring.web.controller") +public class GlobalControllerAdvice { + + @ModelAttribute("userInfo") + public Map userInfo() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null && auth.isAuthenticated() && auth.getPrincipal() instanceof UserPrincipal) { + UserPrincipal user = (UserPrincipal) auth.getPrincipal(); + return Map.of("userId", user.getUsername(), "userName", user.getMemberName()); + } + return Map.of("userId", "", "userName", ""); + } + + @ModelAttribute("menus") + public List> getMenus() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null && auth.isAuthenticated()) { + AgentUserRole userRole = auth.getAuthorities().stream() + .map(role -> role.getAuthority()) + .map(AgentUserRole::fromRole) + .findFirst() + .orElse(AgentUserRole.ROLE_USER); + + return Menus.fromRole(userRole).stream() + .map(menu -> Map.of("menuUri", menu.getMenuUri(), "menuName", menu.getMenuName(), "menuIcon", menu.getMenuIcon())) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + +} diff --git a/batch-quartz/src/main/resources/static/js/apis/user-api.js b/batch-quartz/src/main/resources/static/js/apis/user-api.js index 98a4905..38b8273 100644 --- a/batch-quartz/src/main/resources/static/js/apis/user-api.js +++ b/batch-quartz/src/main/resources/static/js/apis/user-api.js @@ -10,8 +10,8 @@ const userService = { await apiClient.put('/api/user/change-role-approve', users); }, - deleteUser: async (userId) => { - await apiClient.delete(`/api/user/${userId}`); + deleteUser: async (id) => { + await apiClient.delete(`/api/user/${id}`); } }; diff --git a/batch-quartz/src/main/resources/static/js/common/common.js b/batch-quartz/src/main/resources/static/js/common/common.js index 46469bd..db00f74 100644 --- a/batch-quartz/src/main/resources/static/js/common/common.js +++ b/batch-quartz/src/main/resources/static/js/common/common.js @@ -4,4 +4,21 @@ export const formatDateTime = (dateTimeString) => { if (!dateTimeString) return '-'; const date = new Date(dateTimeString); return dayjs(date).format("YYYY-MM-DD ddd A HH:mm:ss"); +} + +export const getModifiedRows = (arr1, arr2, keyField) => { + const modifiedRows = []; + const map1 = new Map(arr1.map(item => [item[keyField], item])); + arr2.forEach(item2 => { + const item1 = map1.get(item2[keyField]); + if (item1 && !deepEqualSelectedFields(item1, item2)) { + modifiedRows.push(item2); + } + }); + return modifiedRows; +} + +export const deepEqualSelectedFields = (obj1, obj2) => { + const keysToCompare = Object.keys(obj2); + return keysToCompare.every(key => obj1[key] === obj2[key]); } \ No newline at end of file diff --git a/batch-quartz/src/main/resources/static/js/pages/user/user-management.js b/batch-quartz/src/main/resources/static/js/pages/user/user-management.js index c95917e..30cc616 100644 --- a/batch-quartz/src/main/resources/static/js/pages/user/user-management.js +++ b/batch-quartz/src/main/resources/static/js/pages/user/user-management.js @@ -1,3 +1,4 @@ +import { getModifiedRows } from '../../common/common.js'; import userService from '../../apis/user-api.js'; let users = []; @@ -12,7 +13,12 @@ const setupEventListeners = () => { e.preventDefault(); fetchDataAndRender(); }); - document.getElementById('saveChangesBtn').addEventListener('click', saveChanges); + document.getElementById('updateUserBtn').addEventListener('click', () => { + const confirmUpdate = confirm('회원정보를 수정하시겠습니까?'); + if (confirmUpdate) { + updateUser(); + } + }); }; const fetchDataAndRender = async () => { @@ -24,49 +30,58 @@ const fetchDataAndRender = async () => { const updateTable = (users) => { const tableBody = document.querySelector('tbody'); tableBody.innerHTML = users.map(user => ` - + ${user.userId} ${user.userName} ${user.email} - ${ROLES.length > 0 ? ROLES.map(role => ` `).join('') : ''} - + - `).join(''); + + document.querySelectorAll('.delete-btn').forEach(btn => btn.addEventListener('click', (e) => { + const confirmUpdate = confirm('회원정보를 삭제하시겠습니까?'); + if (confirmUpdate) { + const {id} = e.target.closest('button').dataset; + deleteUser(id); + } + })); }; -const saveChanges = async () => { - const updatedUsers = users.map(user => { - const selectElement = document.querySelector(`select[data-user-id="${user.id}"]`); - const checkboxElement = document.querySelector(`input[data-user-approved="${user.approved}"]`); +const updateUser = async () => { + const updatedUsers = Array.from(document.querySelectorAll('tbody tr')).map(row => { + const id = row.dataset.key; + const selectElement = row.querySelector(`#userRole-${id}`); + const checkboxElement = row.querySelector(`#approved-${id}`); const userRole = selectElement ? selectElement.value : null; const isApproved = checkboxElement ? checkboxElement.checked : false; return { - id: user.id, + id: id, userRole: userRole, - isApproved: isApproved + approved: isApproved }; }); - await userService.changeRoleApprove(updatedUsers); + await userService.changeRoleApprove(getModifiedRows(users, updatedUsers, "id")); alert('회원정보가 수정 되었습니다.'); fetchDataAndRender(); }; -const deleteUser = async (userId) => { - await userService.deleteUser(userId); +const deleteUser = async (id) => { + await userService.deleteUser(id); alert('사용자가 삭제되었습니다.'); fetchDataAndRender(); }; \ No newline at end of file diff --git a/batch-quartz/src/main/resources/templates/fragments/config.html b/batch-quartz/src/main/resources/templates/fragments/config.html index 40a0c7d..af9ad77 100644 --- a/batch-quartz/src/main/resources/templates/fragments/config.html +++ b/batch-quartz/src/main/resources/templates/fragments/config.html @@ -9,6 +9,7 @@ userId: /*[[${userInfo?.userId ?: ''}]]*/ '', userName: /*[[${userInfo?.userName ?: ''}]]*/ '' }; + const MENUS = /*[[${menus}]]*/ ''; /*]]>*/ diff --git a/batch-quartz/src/main/resources/templates/fragments/left.html b/batch-quartz/src/main/resources/templates/fragments/left.html index 1050cf1..ba698ea 100644 --- a/batch-quartz/src/main/resources/templates/fragments/left.html +++ b/batch-quartz/src/main/resources/templates/fragments/left.html @@ -2,14 +2,11 @@ diff --git a/batch-quartz/src/main/resources/templates/pages/error/error.html b/batch-quartz/src/main/resources/templates/pages/error/error.html index 39b633b..21fcb94 100644 --- a/batch-quartz/src/main/resources/templates/pages/error/error.html +++ b/batch-quartz/src/main/resources/templates/pages/error/error.html @@ -15,7 +15,7 @@

500

내부 서버 오류

-

죄송합니다. 문제가 발생했습니다. 기술팀이 이 문제를 해결하기 위해 노력하고 있습니다.

+

죄송합니다. 문제가 발생했습니다. 기술팀이 이 문제를 해결하기 위해 노력하고 있습니다.

홈으로 돌아가기 diff --git a/batch-quartz/src/main/resources/templates/pages/user/user-management.html b/batch-quartz/src/main/resources/templates/pages/user/user-management.html index bee43a4..f9539f6 100644 --- a/batch-quartz/src/main/resources/templates/pages/user/user-management.html +++ b/batch-quartz/src/main/resources/templates/pages/user/user-management.html @@ -79,7 +79,7 @@
사용자 목록 -