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.http.HttpStatus;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
@@ -17,9 +18,11 @@ public class GlobalErrorController implements ErrorController {
|
|||||||
@GetMapping("/error")
|
@GetMapping("/error")
|
||||||
public String handleError(HttpServletRequest request, Model model) {
|
public String handleError(HttpServletRequest request, Model model) {
|
||||||
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
||||||
|
String errorMessage = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_MESSAGE));
|
||||||
String statusMsg = status.toString();
|
String statusMsg = status.toString();
|
||||||
HttpStatus httpStatus = HttpStatus.valueOf(Integer.valueOf(statusMsg));
|
HttpStatus httpStatus = HttpStatus.valueOf(Integer.valueOf(statusMsg));
|
||||||
model.addAttribute("message", statusMsg + " " + httpStatus.getReasonPhrase());
|
model.addAttribute("message", statusMsg + " " + httpStatus.getReasonPhrase());
|
||||||
|
if (StringUtils.hasText(errorMessage)) model.addAttribute("errorMessage", errorMessage);
|
||||||
return "pages/error/error";
|
return "pages/error/error";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.spring.common.advice;
|
package com.spring.common.support;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
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.core.MethodParameter;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -8,6 +8,9 @@ import org.springframework.http.server.ServerHttpRequest;
|
|||||||
import org.springframework.http.server.ServerHttpResponse;
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.lang.Nullable;
|
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.bind.annotation.RestControllerAdvice;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
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.ErrorResponse;
|
||||||
import com.spring.common.error.ErrorRule;
|
import com.spring.common.error.ErrorRule;
|
||||||
import com.spring.common.error.GlobalExceptionHandler;
|
import com.spring.common.error.GlobalExceptionHandler;
|
||||||
|
import com.spring.common.validation.CollectionValidator;
|
||||||
import com.spring.infra.security.error.SecurityExceptionHandler;
|
import com.spring.infra.security.error.SecurityExceptionHandler;
|
||||||
|
|
||||||
@RestControllerAdvice(
|
@RestControllerAdvice(
|
||||||
@@ -22,6 +26,17 @@ import com.spring.infra.security.error.SecurityExceptionHandler;
|
|||||||
basePackageClasses = { GlobalExceptionHandler.class, SecurityExceptionHandler.class }
|
basePackageClasses = { GlobalExceptionHandler.class, SecurityExceptionHandler.class }
|
||||||
)
|
)
|
||||||
public class ResponseWrapper implements ResponseBodyAdvice<Object> {
|
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
|
@Override
|
||||||
public boolean supports(
|
public boolean supports(
|
||||||
@@ -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 javax.validation.Valid;
|
||||||
|
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -30,31 +31,37 @@ public class ScheduleJobApi {
|
|||||||
private final ScheduleControlService scheduleControlService;
|
private final ScheduleControlService scheduleControlService;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER', 'ADMIN')")
|
||||||
public List<ScheduleJobResponse> getAllJobs(@RequestParam(required = false) String groupName, @RequestParam(required = false) String jobName) {
|
public List<ScheduleJobResponse> getAllJobs(@RequestParam(required = false) String groupName, @RequestParam(required = false) String jobName) {
|
||||||
return findScheduleJobService.getAllJobs(groupName, jobName);
|
return findScheduleJobService.getAllJobs(groupName, jobName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{groupName}/{jobName}")
|
@GetMapping("/{groupName}/{jobName}")
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER', 'ADMIN')")
|
||||||
public ScheduleJobResponse getJobDetail(@PathVariable String groupName, @PathVariable String jobName) {
|
public ScheduleJobResponse getJobDetail(@PathVariable String groupName, @PathVariable String jobName) {
|
||||||
return findScheduleJobService.getJobDetail(groupName, jobName);
|
return findScheduleJobService.getJobDetail(groupName, jobName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/pause/{groupName}/{jobName}")
|
@GetMapping("/pause/{groupName}/{jobName}")
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER', 'ADMIN')")
|
||||||
public void pauseJob(@PathVariable String groupName, @PathVariable String jobName) {
|
public void pauseJob(@PathVariable String groupName, @PathVariable String jobName) {
|
||||||
scheduleControlService.pauseJob(groupName, jobName);
|
scheduleControlService.pauseJob(groupName, jobName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/resume/{groupName}/{jobName}")
|
@GetMapping("/resume/{groupName}/{jobName}")
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER', 'ADMIN')")
|
||||||
public void resumeJob(@PathVariable String groupName, @PathVariable String jobName) {
|
public void resumeJob(@PathVariable String groupName, @PathVariable String jobName) {
|
||||||
scheduleControlService.resumeJob(groupName, jobName);
|
scheduleControlService.resumeJob(groupName, jobName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/trigger/{groupName}/{jobName}")
|
@GetMapping("/trigger/{groupName}/{jobName}")
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER', 'ADMIN')")
|
||||||
public void triggerJob(@PathVariable String groupName, @PathVariable String jobName) {
|
public void triggerJob(@PathVariable String groupName, @PathVariable String jobName) {
|
||||||
scheduleControlService.triggerJob(groupName, jobName);
|
scheduleControlService.triggerJob(groupName, jobName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/reschedule")
|
@PostMapping("/reschedule")
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER', 'ADMIN')")
|
||||||
public boolean rescheduleJob(@Valid @RequestBody ReScheduleJobRequest request) {
|
public boolean rescheduleJob(@Valid @RequestBody ReScheduleJobRequest request) {
|
||||||
return reScheduleJobService.rescheduleJob(request);
|
return reScheduleJobService.rescheduleJob(request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import java.util.List;
|
|||||||
|
|
||||||
import javax.validation.Valid;
|
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.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -25,13 +28,21 @@ public class UserManagementApi {
|
|||||||
private final UserManagementService userManagementService;
|
private final UserManagementService userManagementService;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER')")
|
||||||
public List<UserManagementResponse> getUsers(UserFindRequest request) {
|
public List<UserManagementResponse> getUsers(UserFindRequest request) {
|
||||||
return userManagementService.getUsers(request);
|
return userManagementService.getUsers(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/change-role-approve")
|
@PutMapping("/change-role-approve")
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER')")
|
||||||
public void changeRoleApprove(@RequestBody @Valid List<ChangeUserRoleApproveRequest> requests) {
|
public void changeRoleApprove(@RequestBody @Valid List<ChangeUserRoleApproveRequest> requests) {
|
||||||
userManagementService.changeRoleApprove(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;
|
private final AgentUserRole userRole;
|
||||||
|
|
||||||
@NotNull(message = "승인 여부는 필수값 입니다.")
|
@NotNull(message = "승인 여부는 필수값 입니다.")
|
||||||
private final boolean isApproved;
|
private final boolean approved;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,10 +38,10 @@ public class SignUpRequest {
|
|||||||
public AgentUser toEntity() {
|
public AgentUser toEntity() {
|
||||||
return AgentUser.builder()
|
return AgentUser.builder()
|
||||||
.userId(userId)
|
.userId(userId)
|
||||||
.userPassword(userPassword)
|
.password(userPassword)
|
||||||
.userName(userName)
|
.name(userName)
|
||||||
.email(email)
|
.email(email)
|
||||||
.isApproved(false)
|
.approved(false)
|
||||||
.userRole(userRole)
|
.userRole(userRole)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class UserFindRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean matchesUserName(AgentUser user, String userName) {
|
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) {
|
public static boolean matchesEmail(AgentUser user, String email) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class UserManagementResponse {
|
|||||||
return new UserManagementResponse(
|
return new UserManagementResponse(
|
||||||
user.getId().toString(),
|
user.getId().toString(),
|
||||||
user.getUserId(),
|
user.getUserId(),
|
||||||
user.getMemberName(),
|
user.getName(),
|
||||||
user.getEmail(),
|
user.getEmail(),
|
||||||
user.isApproved(),
|
user.isApproved(),
|
||||||
user.getUserRole()
|
user.getUserRole()
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package com.spring.domain.user.entity;
|
package com.spring.domain.user.entity;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
@@ -14,10 +11,6 @@ import javax.persistence.Id;
|
|||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
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.AccessLevel;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@@ -28,7 +21,7 @@ import lombok.NoArgsConstructor;
|
|||||||
@Table(name = "AGENT_USER")
|
@Table(name = "AGENT_USER")
|
||||||
@Getter
|
@Getter
|
||||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
public class AgentUser implements UserPrincipal {
|
public class AgentUser {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(generator = "uuid2")
|
@GeneratedValue(generator = "uuid2")
|
||||||
@@ -39,91 +32,42 @@ public class AgentUser implements UserPrincipal {
|
|||||||
@Column(name = "USER_ID", nullable = false, length = 50)
|
@Column(name = "USER_ID", nullable = false, length = 50)
|
||||||
private String userId;
|
private String userId;
|
||||||
|
|
||||||
@Column(name = "USER_PASSWORD", nullable = false, length = 128)
|
@Column(name = "PASSWORD", nullable = false, length = 128)
|
||||||
private String userPassword;
|
private String password;
|
||||||
|
|
||||||
@Column(name = "USER_NAME", nullable = false, length = 50)
|
@Column(name = "NAME", nullable = false, length = 50)
|
||||||
private String userName;
|
private String name;
|
||||||
|
|
||||||
@Column(name = "EMAIL", nullable = false, length = 100)
|
@Column(name = "EMAIL", nullable = false, length = 100)
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@Column(name = "IS_APPROVED", nullable = false)
|
@Column(name = "APPROVED", nullable = false)
|
||||||
private boolean isApproved;
|
private boolean approved;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@Column(name = "USER_ROLE", nullable = false, length = 50)
|
@Column(name = "USER_ROLE", nullable = false, length = 50)
|
||||||
private AgentUserRole userRole;
|
private AgentUserRole userRole;
|
||||||
|
|
||||||
@Builder
|
@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.userId = userId;
|
||||||
this.userPassword = userPassword;
|
this.password = password;
|
||||||
this.userName = userName;
|
this.name = name;
|
||||||
this.userRole = userRole;
|
this.userRole = userRole;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.isApproved = isApproved;
|
this.approved = approved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changePassword(String newPassword) {
|
public void changePassword(String newPassword) {
|
||||||
this.userPassword = newPassword;
|
this.password = newPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeUserRole(AgentUserRole userRole) {
|
public void changeUserRole(AgentUserRole userRole) {
|
||||||
this.userRole = userRole;
|
this.userRole = userRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeApproved(boolean isApproved) {
|
public void changeApproved(boolean approved) {
|
||||||
this.isApproved = isApproved;
|
this.approved = approved;
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public UserPrincipal loadUserByUsername(String username) throws UsernameNotFoundException {
|
public UserPrincipal loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
return agentUserRepository.findByUserId(username)
|
return UserPrincipal.valueOf(
|
||||||
.orElseThrow(() -> new SecurityAuthException(SecurityExceptionRule.USER_UNAUTHORIZED));
|
agentUserRepository.findByUserId(username)
|
||||||
|
.orElseThrow(() -> new SecurityAuthException(SecurityExceptionRule.USER_UNAUTHORIZED))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public UserPrincipal getUserDetails(String key) {
|
public UserPrincipal getUserDetails(String key) {
|
||||||
return agentUserRepository.findById(UUID.fromString(key))
|
return UserPrincipal.valueOf(
|
||||||
.orElseThrow(UserNotFoundException::new);
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
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.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.CsrfConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
|
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
|
||||||
@@ -71,6 +72,7 @@ public class SecurityConfig {
|
|||||||
.csrf(CsrfConfigurer::disable)
|
.csrf(CsrfConfigurer::disable)
|
||||||
.httpBasic(HttpBasicConfigurer::disable)
|
.httpBasic(HttpBasicConfigurer::disable)
|
||||||
.formLogin(FormLoginConfigurer::disable)
|
.formLogin(FormLoginConfigurer::disable)
|
||||||
|
.anonymous(AnonymousConfigurer::disable)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.antMatchers(Arrays.stream(PermittedURI.values()).map(PermittedURI::getUri).toArray(String[]::new)).permitAll()
|
.antMatchers(Arrays.stream(PermittedURI.values()).map(PermittedURI::getUri).toArray(String[]::new)).permitAll()
|
||||||
.anyRequest().authenticated()
|
.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;
|
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;
|
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();
|
|
||||||
|
|
||||||
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<? 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;
|
private final SecurityExceptionRule exceptionRule;
|
||||||
|
|
||||||
|
public SecurityAuthException() {
|
||||||
|
super(SecurityExceptionRule.SYSTEM_ERROR.getMessage());
|
||||||
|
this.exceptionRule = SecurityExceptionRule.SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
public SecurityAuthException(String msg) {
|
public SecurityAuthException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
this.exceptionRule = SecurityExceptionRule.SYSTEM_ERROR;
|
this.exceptionRule = SecurityExceptionRule.SYSTEM_ERROR;
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
package com.spring.infra.security.error;
|
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.access.AccessDeniedException;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
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.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
import io.jsonwebtoken.MalformedJwtException;
|
import io.jsonwebtoken.MalformedJwtException;
|
||||||
import io.jsonwebtoken.security.SignatureException;
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SecurityExceptionHandler {
|
public class SecurityExceptionHandler {
|
||||||
|
|
||||||
|
private final AccessDeniedHandler accessDeniedHandler;
|
||||||
|
|
||||||
@ExceptionHandler(SecurityAuthException.class)
|
@ExceptionHandler(SecurityAuthException.class)
|
||||||
public SecurityErrorResponse handleAuthenticationException(SecurityAuthException e) {
|
public SecurityErrorResponse handleAuthenticationException(SecurityAuthException e) {
|
||||||
return SecurityErrorResponse.valueOf(e.getExceptionRule());
|
return SecurityErrorResponse.valueOf(e.getExceptionRule());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(AccessDeniedException.class)
|
|
||||||
public SecurityErrorResponse handleAccessDeniedException() {
|
|
||||||
return SecurityErrorResponse.valueOf(SecurityExceptionRule.USER_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(AuthenticationException.class)
|
@ExceptionHandler(AuthenticationException.class)
|
||||||
public SecurityErrorResponse handleAuthenticationException() {
|
public SecurityErrorResponse handleAuthenticationException() {
|
||||||
return SecurityErrorResponse.valueOf(SecurityExceptionRule.USER_UNAUTHORIZED);
|
return SecurityErrorResponse.valueOf(SecurityExceptionRule.USER_UNAUTHORIZED);
|
||||||
@@ -31,15 +37,22 @@ public class SecurityExceptionHandler {
|
|||||||
public SecurityErrorResponse handleSignatureException() {
|
public SecurityErrorResponse handleSignatureException() {
|
||||||
return SecurityErrorResponse.valueOf(SecurityExceptionRule.SIGNATURE_ERROR);
|
return SecurityErrorResponse.valueOf(SecurityExceptionRule.SIGNATURE_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(MalformedJwtException.class)
|
@ExceptionHandler(MalformedJwtException.class)
|
||||||
public SecurityErrorResponse handleMalformedJwtException() {
|
public SecurityErrorResponse handleMalformedJwtException() {
|
||||||
return SecurityErrorResponse.valueOf(SecurityExceptionRule.MALFORMED_JWT_ERROR);
|
return SecurityErrorResponse.valueOf(SecurityExceptionRule.MALFORMED_JWT_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(ExpiredJwtException.class)
|
@ExceptionHandler(ExpiredJwtException.class)
|
||||||
public SecurityErrorResponse handleExpiredJwtException() {
|
public SecurityErrorResponse handleExpiredJwtException() {
|
||||||
return SecurityErrorResponse.valueOf(SecurityExceptionRule.EXPIRED_JWT_ERROR);
|
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 org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
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.config.PermittedURI;
|
||||||
|
import com.spring.infra.security.domain.UserPrincipal;
|
||||||
import com.spring.infra.security.dto.SignInRequest;
|
import com.spring.infra.security.dto.SignInRequest;
|
||||||
import com.spring.infra.security.error.SecurityAuthException;
|
import com.spring.infra.security.error.SecurityAuthException;
|
||||||
import com.spring.infra.security.error.SecurityExceptionRule;
|
import com.spring.infra.security.error.SecurityExceptionRule;
|
||||||
|
|
||||||
public class AuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
|
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 String HTTP_METHOD = "POST";
|
||||||
private static final AntPathRequestMatcher DEFAULT_LOGIN_PATH_REQUEST_MATCHER =
|
private static final AntPathRequestMatcher DEFAULT_LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(DEFAULT_LOGIN_REQUEST_URI, HTTP_METHOD);
|
||||||
new AntPathRequestMatcher(DEFAULT_LOGIN_REQUEST_URL, HTTP_METHOD);
|
|
||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
@@ -38,8 +37,11 @@ public class AuthenticationProcessingFilter extends AbstractAuthenticationProces
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
public Authentication attemptAuthentication(
|
||||||
throws AuthenticationException, IOException, ServletException {
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response
|
||||||
|
) throws AuthenticationException, IOException, ServletException {
|
||||||
|
|
||||||
if (!isValidRequestType(request)) {
|
if (!isValidRequestType(request)) {
|
||||||
throw new SecurityAuthException(SecurityExceptionRule.UNSUPPORTED_MEDIA_ERROR);
|
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 token = new UsernamePasswordAuthenticationToken(signInRequest.getUsername(), signInRequest.getPassword());
|
||||||
var authentication = this.getAuthenticationManager().authenticate(token);
|
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;
|
return authentication;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import org.springframework.http.HttpHeaders;
|
|||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import com.spring.infra.security.config.PermittedURI;
|
import com.spring.infra.security.config.PermittedURI;
|
||||||
@@ -33,7 +32,6 @@ import lombok.RequiredArgsConstructor;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public final class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public final class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
|
||||||
private final JwtTokenService jwtTokenService;
|
private final JwtTokenService jwtTokenService;
|
||||||
private static final String EXCEPTION_ATTRIBUTE = "exception";
|
private static final String EXCEPTION_ATTRIBUTE = "exception";
|
||||||
|
|
||||||
@@ -53,9 +51,7 @@ public final class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
@NonNull FilterChain filterChain
|
@NonNull FilterChain filterChain
|
||||||
) throws ServletException, IOException {
|
) throws ServletException, IOException {
|
||||||
|
|
||||||
String requestURI = request.getRequestURI();
|
if (isPermittedURI(request.getRequestURI())) {
|
||||||
if (Arrays.stream(PermittedURI.values()).map(PermittedURI::getUri).anyMatch(uri -> pathMatcher.match(uri, requestURI)) &&
|
|
||||||
!PermittedURI.ROOT_URI.getUri().equals(requestURI)) {
|
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,23 +60,20 @@ public final class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
String accessToken = parseBearerToken(request, HttpHeaders.AUTHORIZATION);
|
String accessToken = parseBearerToken(request, HttpHeaders.AUTHORIZATION);
|
||||||
if (jwtTokenService.validateAccessToken(accessToken)) {
|
if (jwtTokenService.validateAccessToken(accessToken)) {
|
||||||
setAuthenticationToContext(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) {
|
} catch (Exception e) {
|
||||||
jwtTokenService.deleteCookie(response);
|
jwtTokenService.deleteCookie(response);
|
||||||
request.setAttribute(EXCEPTION_ATTRIBUTE, e);
|
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));
|
.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 java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.RequestDispatcher;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
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.access.AccessDeniedException;
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||||
|
|
||||||
|
import com.spring.infra.security.error.SecurityAuthException;
|
||||||
|
import com.spring.infra.security.error.SecurityExceptionRule;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT 인증에서 접근 거부 상황을 처리하는 핸들러 클래스입니다.
|
* JWT 인증에서 접근 거부 상황을 처리하는 핸들러 클래스입니다.
|
||||||
*
|
*
|
||||||
@@ -22,30 +30,29 @@ import org.springframework.web.servlet.HandlerExceptionResolver;
|
|||||||
* @version 1.0
|
* @version 1.0
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SecurityAccessDeniedHandler implements AccessDeniedHandler {
|
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
|
@Override
|
||||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||||
AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
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;
|
private final HttpRequestEndpointChecker endpointChecker;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||||
AuthenticationException authException) throws IOException, ServletException {
|
|
||||||
if (!endpointChecker.isEndpointExist(request)) {
|
if (!endpointChecker.isEndpointExist(request)) {
|
||||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||||
} else if (isApiRequest(request)) {
|
} else if (isApiRequest(request)) {
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SigninFailureHandler implements AuthenticationFailureHandler {
|
public class SigninFailureHandler implements AuthenticationFailureHandler {
|
||||||
|
|
||||||
|
@Qualifier("handlerExceptionResolver")
|
||||||
private final HandlerExceptionResolver resolver;
|
private final HandlerExceptionResolver resolver;
|
||||||
|
|
||||||
public SigninFailureHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
|
|
||||||
this.resolver = resolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationFailure(
|
public void onAuthenticationFailure(
|
||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import org.springframework.security.core.userdetails.UserDetails;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.SecurityAuthException;
|
||||||
import com.spring.infra.security.error.SecurityExceptionRule;
|
import com.spring.infra.security.error.SecurityExceptionRule;
|
||||||
import com.spring.infra.security.service.RefreshTokenService;
|
import com.spring.infra.security.service.RefreshTokenService;
|
||||||
@@ -173,7 +173,7 @@ public class JwtTokenService {
|
|||||||
.map(String::valueOf)
|
.map(String::valueOf)
|
||||||
.map(SimpleGrantedAuthority::new)
|
.map(SimpleGrantedAuthority::new)
|
||||||
.collect(Collectors.toList());
|
.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;
|
package com.spring.web.controller;
|
||||||
|
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -9,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
public class ScheduleController {
|
public class ScheduleController {
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER', 'ADMIN')")
|
||||||
public String schedule() {
|
public String schedule() {
|
||||||
return "pages/schedule/schedule";
|
return "pages/schedule/schedule";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.spring.web.controller;
|
package com.spring.web.controller;
|
||||||
|
|
||||||
import java.util.List;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -15,11 +13,9 @@ import com.spring.domain.user.entity.AgentUserRole;
|
|||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
@GetMapping("/management")
|
@GetMapping("/management")
|
||||||
|
@PreAuthorize("hasAnyRole('SUPER')")
|
||||||
public String management(Model model) {
|
public String management(Model model) {
|
||||||
List<AgentUserRole> roles = List.of(AgentUserRole.values()).stream()
|
model.addAttribute("roles", AgentUserRole.values());
|
||||||
.filter(role -> !role.getRole().equals(AgentUserRole.ROLE_SUPER.name()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
model.addAttribute("roles", roles);
|
|
||||||
return "pages/user/user-management";
|
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);
|
await apiClient.put('/api/user/change-role-approve', users);
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteUser: async (userId) => {
|
deleteUser: async (id) => {
|
||||||
await apiClient.delete(`/api/user/${userId}`);
|
await apiClient.delete(`/api/user/${id}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,21 @@ export const formatDateTime = (dateTimeString) => {
|
|||||||
if (!dateTimeString) return '-';
|
if (!dateTimeString) return '-';
|
||||||
const date = new Date(dateTimeString);
|
const date = new Date(dateTimeString);
|
||||||
return dayjs(date).format("YYYY-MM-DD ddd A HH:mm:ss");
|
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';
|
import userService from '../../apis/user-api.js';
|
||||||
|
|
||||||
let users = [];
|
let users = [];
|
||||||
@@ -12,7 +13,12 @@ const setupEventListeners = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
fetchDataAndRender();
|
fetchDataAndRender();
|
||||||
});
|
});
|
||||||
document.getElementById('saveChangesBtn').addEventListener('click', saveChanges);
|
document.getElementById('updateUserBtn').addEventListener('click', () => {
|
||||||
|
const confirmUpdate = confirm('회원정보를 수정하시겠습니까?');
|
||||||
|
if (confirmUpdate) {
|
||||||
|
updateUser();
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchDataAndRender = async () => {
|
const fetchDataAndRender = async () => {
|
||||||
@@ -24,49 +30,58 @@ const fetchDataAndRender = async () => {
|
|||||||
const updateTable = (users) => {
|
const updateTable = (users) => {
|
||||||
const tableBody = document.querySelector('tbody');
|
const tableBody = document.querySelector('tbody');
|
||||||
tableBody.innerHTML = users.map(user => `
|
tableBody.innerHTML = users.map(user => `
|
||||||
<tr>
|
<tr data-key="${user.id}">
|
||||||
<td class="align-middle">${user.userId}</td>
|
<td class="align-middle">${user.userId}</td>
|
||||||
<td class="align-middle">${user.userName}</td>
|
<td class="align-middle">${user.userName}</td>
|
||||||
<td class="align-middle">${user.email}</td>
|
<td class="align-middle">${user.email}</td>
|
||||||
<td class="align-middle">
|
<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 => `
|
${ROLES.length > 0 ? ROLES.map(role => `
|
||||||
<option value="${role}" ${user.userRole === role ? 'selected' : ''}>${role}</option>
|
<option value="${role}" ${user.userRole === role ? 'selected' : ''}>${role}</option>
|
||||||
`).join('') : '<option value=""></option>'}
|
`).join('') : '<option value=""></option>'}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<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>
|
||||||
<td class="align-middle">
|
<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>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).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 updateUser = async () => {
|
||||||
const updatedUsers = users.map(user => {
|
const updatedUsers = Array.from(document.querySelectorAll('tbody tr')).map(row => {
|
||||||
const selectElement = document.querySelector(`select[data-user-id="${user.id}"]`);
|
const id = row.dataset.key;
|
||||||
const checkboxElement = document.querySelector(`input[data-user-approved="${user.approved}"]`);
|
const selectElement = row.querySelector(`#userRole-${id}`);
|
||||||
|
const checkboxElement = row.querySelector(`#approved-${id}`);
|
||||||
const userRole = selectElement ? selectElement.value : null;
|
const userRole = selectElement ? selectElement.value : null;
|
||||||
const isApproved = checkboxElement ? checkboxElement.checked : false;
|
const isApproved = checkboxElement ? checkboxElement.checked : false;
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: id,
|
||||||
userRole: userRole,
|
userRole: userRole,
|
||||||
isApproved: isApproved
|
approved: isApproved
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await userService.changeRoleApprove(updatedUsers);
|
await userService.changeRoleApprove(getModifiedRows(users, updatedUsers, "id"));
|
||||||
alert('회원정보가 수정 되었습니다.');
|
alert('회원정보가 수정 되었습니다.');
|
||||||
fetchDataAndRender();
|
fetchDataAndRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteUser = async (userId) => {
|
const deleteUser = async (id) => {
|
||||||
await userService.deleteUser(userId);
|
await userService.deleteUser(id);
|
||||||
alert('사용자가 삭제되었습니다.');
|
alert('사용자가 삭제되었습니다.');
|
||||||
fetchDataAndRender();
|
fetchDataAndRender();
|
||||||
};
|
};
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
userId: /*[[${userInfo?.userId ?: ''}]]*/ '',
|
userId: /*[[${userInfo?.userId ?: ''}]]*/ '',
|
||||||
userName: /*[[${userInfo?.userName ?: ''}]]*/ ''
|
userName: /*[[${userInfo?.userName ?: ''}]]*/ ''
|
||||||
};
|
};
|
||||||
|
const MENUS = /*[[${menus}]]*/ '';
|
||||||
/*]]>*/
|
/*]]>*/
|
||||||
</script>
|
</script>
|
||||||
<script th:src="@{/js/lib/axios/axios.min.js}"></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">
|
<html xmlns:th="http://www.thymeleaf.org" th:fragment="sidebar" lang="ko" xml:lang="ko">
|
||||||
<aside id="sidebar" class="sidebar">
|
<aside id="sidebar" class="sidebar">
|
||||||
<ul class="sidebar-nav" id="sidebar-nav">
|
<ul class="sidebar-nav" id="sidebar-nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item" th:each="menu : ${menus}">
|
||||||
<a class="nav-link" href="/dashboard"><i class="bi bi-grid"></i><span>Dashboard</span></a>
|
<a class="nav-link" th:href="${menu.menuUri}">
|
||||||
</li>
|
<i th:class="${menu.menuIcon}"></i>
|
||||||
<li class="nav-item">
|
<span th:text="${menu.menuName}"></span>
|
||||||
<a class="nav-link" href="/schedule"><i class="bi bi-menu-button-wide"></i><span>Schedule</span></a>
|
</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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<h1 class="display-4 fw-bold text-danger mb-4" th:text="${#strings.substring(message, 0, 3)}">500</h1>
|
<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>
|
<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">
|
<a href="/" class="btn btn-primary btn-lg d-inline-flex align-items-center">
|
||||||
<i class="bi bi-house-door-fill me-2"></i>홈으로 돌아가기
|
<i class="bi bi-house-door-fill me-2"></i>홈으로 돌아가기
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title d-flex justify-content-between align-items-center">
|
<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>
|
<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>
|
<i class="bi bi-pencil-fill"></i>
|
||||||
</button>
|
</button>
|
||||||
</h5>
|
</h5>
|
||||||
|
|||||||
Reference in New Issue
Block a user