commit
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.spring.common.advice;
|
||||
package com.spring.common.support;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -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(
|
||||
@@ -23,6 +27,17 @@ import com.spring.infra.security.error.SecurityExceptionHandler;
|
||||
)
|
||||
public class ResponseWrapper implements ResponseBodyAdvice<Object> {
|
||||
|
||||
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(
|
||||
@NonNull MethodParameter returnType,
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ScheduleJobResponse> 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);
|
||||
}
|
||||
|
||||
@@ -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<UserManagementResponse> getUsers(UserFindRequest request) {
|
||||
return userManagementService.getUsers(request);
|
||||
}
|
||||
|
||||
@PutMapping("/change-role-approve")
|
||||
@PreAuthorize("hasAnyRole('SUPER')")
|
||||
public void changeRoleApprove(@RequestBody @Valid List<ChangeUserRoleApproveRequest> requests) {
|
||||
userManagementService.changeRoleApprove(requests);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAnyRole('SUPER')")
|
||||
public void deleteUser(@PathVariable String id) {
|
||||
userManagementService.deleteUser(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@ public class ChangeUserRoleApproveRequest {
|
||||
private final AgentUserRole userRole;
|
||||
|
||||
@NotNull(message = "승인 여부는 필수값 입니다.")
|
||||
private final boolean isApproved;
|
||||
private final boolean approved;
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<? extends GrantedAuthority> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<? extends GrantedAuthority> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
import com.spring.domain.user.entity.AgentUser;
|
||||
import com.spring.domain.user.entity.AgentUserRole;
|
||||
|
||||
String getKey();
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
String getMemberName();
|
||||
@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<? extends GrantedAuthority> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
@@ -42,4 +48,11 @@ public class SecurityExceptionHandler {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
if (authentication.getPrincipal() instanceof UserPrincipal) {
|
||||
UserPrincipal user = (UserPrincipal) authentication.getPrincipal();
|
||||
if (!user.getAgentUser().isApproved()) {
|
||||
throw new SecurityAuthException(SecurityExceptionRule.USER_NOT_APPROVED);
|
||||
}
|
||||
}
|
||||
|
||||
return authentication;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,24 +60,21 @@ 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));
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
public SecurityAccessDeniedHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* 접근 거부 상황을 처리합니다.
|
||||
*
|
||||
* <p>사용자가 접근 권한이 없는 리소스에 접근을 시도할 때 호출됩니다.
|
||||
* 이 메소드는 SC_FORBIDDEN (403) 상태 코드를 응답으로 전송합니다.</p>
|
||||
*
|
||||
* @param request 현재 HTTP 요청
|
||||
* @param response 현재 HTTP 응답
|
||||
* @param accessDeniedException 발생한 접근 거부 예외
|
||||
* @throws IOException 입출력 예외 발생 시
|
||||
* @throws ServletException 서블릿 예외 발생 시
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<String, String> userInfo() {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
Map<String, String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<AgentUserRole> roles;
|
||||
|
||||
public static List<Menus> fromRole(AgentUserRole role) {
|
||||
return Arrays.stream(values())
|
||||
.filter(menu -> menu.roles.contains(role))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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<AgentUserRole> 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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, String> 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<Map<String, String>> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,3 +5,20 @@ export const formatDateTime = (dateTimeString) => {
|
||||
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]);
|
||||
}
|
||||
@@ -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 => `
|
||||
<tr>
|
||||
<tr data-key="${user.id}">
|
||||
<td class="align-middle">${user.userId}</td>
|
||||
<td class="align-middle">${user.userName}</td>
|
||||
<td class="align-middle">${user.email}</td>
|
||||
<td class="align-middle">
|
||||
<select class="form-select form-select-sm" data-user-id="${user.id}" data-user-role="${user.userRole}">
|
||||
<select id="userRole-${user.id}" class="form-select form-select-sm">
|
||||
${ROLES.length > 0 ? ROLES.map(role => `
|
||||
<option value="${role}" ${user.userRole === role ? 'selected' : ''}>${role}</option>
|
||||
`).join('') : '<option value=""></option>'}
|
||||
</select>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<input type="checkbox" ${user.approved ? 'checked' : ''} data-user-id="${user.id}" data-user-approved="${user.approved}" class="form-check-input">
|
||||
<input id="approved-${user.id}" type="checkbox" ${user.approved ? 'checked' : ''} class="form-check-input">
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<button id="deleteUserBtn" onclick="deleteUser('${user.id}')" class="btn btn-sm btn-outline-danger" data-bs-toggle="tooltip" data-bs-placement="left" title="사용자 삭제">
|
||||
<button class="btn btn-sm btn-outline-danger delete-btn" data-id="${user.id}" title="사용자 삭제">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).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();
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
userId: /*[[${userInfo?.userId ?: ''}]]*/ '',
|
||||
userName: /*[[${userInfo?.userName ?: ''}]]*/ ''
|
||||
};
|
||||
const MENUS = /*[[${menus}]]*/ '';
|
||||
/*]]>*/
|
||||
</script>
|
||||
<script th:src="@{/js/lib/axios/axios.min.js}"></script>
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org" th:fragment="sidebar" lang="ko" xml:lang="ko">
|
||||
<aside id="sidebar" class="sidebar">
|
||||
<ul class="sidebar-nav" id="sidebar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/dashboard"><i class="bi bi-grid"></i><span>Dashboard</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/schedule"><i class="bi bi-menu-button-wide"></i><span>Schedule</span></a>
|
||||
</li>
|
||||
<li class="nav-item"></li>
|
||||
<a class="nav-link" href="/user/management"><i class="bi bi-person"></i><span>User Management</span></a>
|
||||
<li class="nav-item" th:each="menu : ${menus}">
|
||||
<a class="nav-link" th:href="${menu.menuUri}">
|
||||
<i th:class="${menu.menuIcon}"></i>
|
||||
<span th:text="${menu.menuName}"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
<h1 class="display-4 fw-bold text-danger mb-4" th:text="${#strings.substring(message, 0, 3)}">500</h1>
|
||||
<h2 class="h4 text-secondary mb-4" th:text="${#strings.substring(message, 4)}">내부 서버 오류</h2>
|
||||
<p class="text-muted mb-4">죄송합니다. 문제가 발생했습니다. 기술팀이 이 문제를 해결하기 위해 노력하고 있습니다.</p>
|
||||
<p class="text-muted mb-4" th:text="${errorMessage}">죄송합니다. 문제가 발생했습니다. 기술팀이 이 문제를 해결하기 위해 노력하고 있습니다.</p>
|
||||
<a href="/" class="btn btn-primary btn-lg d-inline-flex align-items-center">
|
||||
<i class="bi bi-house-door-fill me-2"></i>홈으로 돌아가기
|
||||
</a>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title d-flex justify-content-between align-items-center">
|
||||
<span class="fs-6 text-dark"><i class="bi bi-list-ul"></i> 사용자 목록</span>
|
||||
<button id="saveChangesBtn" class="btn btn-sm btn-outline-primary" data-bs-toggle="tooltip" data-bs-placement="left" title="사용자 수정">
|
||||
<button id="updateUserBtn" class="btn btn-sm btn-outline-primary" data-bs-toggle="tooltip" data-bs-placement="left" title="사용자 수정">
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
</button>
|
||||
</h5>
|
||||
|
||||
Reference in New Issue
Block a user