diff --git a/batch-quartz/src/main/java/com/spring/common/error/BizBaseException.java b/batch-quartz/src/main/java/com/spring/common/error/BizBaseException.java index d9a6242..5910407 100644 --- a/batch-quartz/src/main/java/com/spring/common/error/BizBaseException.java +++ b/batch-quartz/src/main/java/com/spring/common/error/BizBaseException.java @@ -5,16 +5,16 @@ import lombok.Getter; @Getter public class BizBaseException extends RuntimeException { - private final ExceptionRule exceptionRule; + private final ErrorRule errorRule; public BizBaseException() { super(ExceptionRule.SYSTE_ERROR.getMessage()); - this.exceptionRule = ExceptionRule.SYSTE_ERROR; + this.errorRule = ExceptionRule.SYSTE_ERROR; } - public BizBaseException(ExceptionRule exceptionRule) { + public BizBaseException(ErrorRule exceptionRule) { super(exceptionRule.getMessage()); - this.exceptionRule = exceptionRule; + this.errorRule = exceptionRule; } } diff --git a/batch-quartz/src/main/java/com/spring/common/error/BizErrorResponse.java b/batch-quartz/src/main/java/com/spring/common/error/BizErrorResponse.java index c817362..819b77d 100644 --- a/batch-quartz/src/main/java/com/spring/common/error/BizErrorResponse.java +++ b/batch-quartz/src/main/java/com/spring/common/error/BizErrorResponse.java @@ -10,12 +10,12 @@ import lombok.Getter; @Getter public class BizErrorResponse extends ErrorResponse { - public BizErrorResponse(ExceptionRule exceptionRule, List errors) { + public BizErrorResponse(ErrorRule exceptionRule, List errors) { super(exceptionRule); this.errors = errors; } - public static BizErrorResponse valueOf(ExceptionRule exceptionRule) { + public static BizErrorResponse valueOf(ErrorRule exceptionRule) { return new BizErrorResponse(exceptionRule, null); } diff --git a/batch-quartz/src/main/java/com/spring/common/error/ExceptionRule.java b/batch-quartz/src/main/java/com/spring/common/error/ExceptionRule.java index 91c18ac..979657b 100644 --- a/batch-quartz/src/main/java/com/spring/common/error/ExceptionRule.java +++ b/batch-quartz/src/main/java/com/spring/common/error/ExceptionRule.java @@ -16,9 +16,7 @@ public enum ExceptionRule implements ErrorRule { NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 자원을 찾을 수 없습니다."), INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버에 오류가 발생했습니다."), CONFLICT(HttpStatus.CONFLICT, "데이터 충돌이 발생했습니다."), - UNPROCESSABLE_ENTITY(HttpStatus.UNPROCESSABLE_ENTITY, "요청을 처리할 수 없습니다."), - USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다."), - SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "스케쥴 정보를 찾을 수 없습니다."); + UNPROCESSABLE_ENTITY(HttpStatus.UNPROCESSABLE_ENTITY, "요청을 처리할 수 없습니다."); private final HttpStatus status; private String message; diff --git a/batch-quartz/src/main/java/com/spring/common/error/GlobalExceptionHandler.java b/batch-quartz/src/main/java/com/spring/common/error/GlobalExceptionHandler.java index 44ff9dc..0f646fd 100644 --- a/batch-quartz/src/main/java/com/spring/common/error/GlobalExceptionHandler.java +++ b/batch-quartz/src/main/java/com/spring/common/error/GlobalExceptionHandler.java @@ -9,7 +9,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(BizBaseException.class) public BizErrorResponse handleCustomException(BizBaseException e) { - return BizErrorResponse.valueOf(e.getExceptionRule()); + return BizErrorResponse.valueOf(e.getErrorRule()); } @ExceptionHandler(MethodArgumentNotValidException.class) diff --git a/batch-quartz/src/main/java/com/spring/common/validation/EnumValid.java b/batch-quartz/src/main/java/com/spring/common/validation/EnumValid.java new file mode 100644 index 0000000..ade1e59 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/common/validation/EnumValid.java @@ -0,0 +1,19 @@ +package com.spring.common.validation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +@Constraint(validatedBy = {EnumValidator.class}) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnumValid { + String message() default "Invalid Enum Value."; + Class[] groups() default {}; + Class[] payload() default {}; + Class> target(); +} diff --git a/batch-quartz/src/main/java/com/spring/common/validation/EnumValidator.java b/batch-quartz/src/main/java/com/spring/common/validation/EnumValidator.java new file mode 100644 index 0000000..eb137ee --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/common/validation/EnumValidator.java @@ -0,0 +1,28 @@ +package com.spring.common.validation; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class EnumValidator implements ConstraintValidator> { + private EnumValid annotation; + + @Override + public void initialize(EnumValid constraintAnnotation) { + this.annotation = constraintAnnotation; + } + + @Override + public boolean isValid(Enum value, ConstraintValidatorContext context) { + if (value == null) return false; + Object[] enumValues = this.annotation.target().getEnumConstants(); + if (enumValues != null) { + for (Object enumValue : enumValues) { + if (value.toString().equals(enumValue.toString())) { + return true; + } + } + } + return false; + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/error/ScheduleNotFoundException.java b/batch-quartz/src/main/java/com/spring/domain/schedule/error/ScheduleNotFoundException.java index 633f86f..461058e 100644 --- a/batch-quartz/src/main/java/com/spring/domain/schedule/error/ScheduleNotFoundException.java +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/error/ScheduleNotFoundException.java @@ -1,16 +1,11 @@ package com.spring.domain.schedule.error; import com.spring.common.error.BizBaseException; -import com.spring.common.error.ExceptionRule; public class ScheduleNotFoundException extends BizBaseException { public ScheduleNotFoundException() { - super(ExceptionRule.SCHEDULE_NOT_FOUND); - } - - public ScheduleNotFoundException(ExceptionRule exceptionRule) { - super(exceptionRule); + super(ScheduleRule.SCHEDULE_NOT_FOUND); } } diff --git a/batch-quartz/src/main/java/com/spring/domain/schedule/error/ScheduleRule.java b/batch-quartz/src/main/java/com/spring/domain/schedule/error/ScheduleRule.java new file mode 100644 index 0000000..8a6b75e --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/schedule/error/ScheduleRule.java @@ -0,0 +1,29 @@ +package com.spring.domain.schedule.error; + +import org.springframework.http.HttpStatus; + +import com.spring.common.error.ErrorRule; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ScheduleRule implements ErrorRule { + + SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "스케쥴 정보를 찾을 수 없습니다."); + + private final HttpStatus status; + private String message; + + ScheduleRule(HttpStatus status, String message) { + this.status = status; + this.message = message; + } + + public ScheduleRule message(final String message) { + this.message = message; + return this; + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/user/api/SignApi.java b/batch-quartz/src/main/java/com/spring/domain/user/api/SignApi.java index 64a37fc..dc0cfaf 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/api/SignApi.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/api/SignApi.java @@ -2,6 +2,8 @@ package com.spring.domain.user.api; import javax.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -19,6 +21,11 @@ public class SignApi { private final SignUpService signUpService; + @GetMapping("/conflict/{userId}") + public boolean isConflictUserId(@PathVariable String userId) { + return signUpService.isConflictUserId(userId); + } + @PostMapping("/sign-up") public void signUp(@RequestBody @Valid SignUpRequest request) { signUpService.signUp(request); diff --git a/batch-quartz/src/main/java/com/spring/domain/user/dto/SignInRequest.java b/batch-quartz/src/main/java/com/spring/domain/user/dto/SignInRequest.java deleted file mode 100644 index 5d1a106..0000000 --- a/batch-quartz/src/main/java/com/spring/domain/user/dto/SignInRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.spring.domain.user.dto; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class SignInRequest { - - private final String username; - private final String password; - -} diff --git a/batch-quartz/src/main/java/com/spring/domain/user/dto/SignUpRequest.java b/batch-quartz/src/main/java/com/spring/domain/user/dto/SignUpRequest.java index d9e14cb..4293d5c 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/dto/SignUpRequest.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/dto/SignUpRequest.java @@ -2,7 +2,9 @@ package com.spring.domain.user.dto; import javax.validation.constraints.NotBlank; -import com.spring.domain.user.entity.AppUser; +import com.spring.common.validation.EnumValid; +import com.spring.domain.user.entity.AgentUser; +import com.spring.domain.user.entity.AgentUserRole; import lombok.AccessLevel; import lombok.Getter; @@ -12,21 +14,28 @@ import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class SignUpRequest { - @NotBlank(message = "로그인ID는 필수값 입니다.") - private String loginId; + @NotBlank(message = "사용자ID는 필수값 입니다.") + private String userId; + @NotBlank(message = "비밀번호는 필수값 입니다.") - private String password; + private String userPassword; + + @NotBlank(message = "사용자명은 필수값 입니다.") private String userName; + @EnumValid(target = AgentUserRole.class, message = "올바른 값을 입력해주세요.") + private AgentUserRole userRole; + public void encodePassword(String password) { - this.password = password; + this.userPassword = password; } - public AppUser toEntity() { - return AppUser.builder() - .loginId(loginId) - .password(password) + public AgentUser toEntity() { + return AgentUser.builder() + .userId(userId) + .userPassword(userPassword) .userName(userName) + .userRole(userRole) .build(); } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java new file mode 100644 index 0000000..cab4506 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java @@ -0,0 +1,61 @@ +package com.spring.domain.user.entity; + +import java.util.UUID; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.GenericGenerator; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "AGENT_USER") +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class AgentUser { + + @Id + @GeneratedValue(generator = "uuid2") + @GenericGenerator(name="uuid2", strategy = "uuid2") + @Column(name = "ID", nullable = false, columnDefinition = "BINARY(16)") + private UUID id; + + // @OneToOne(mappedBy = "agentUser", cascade = CascadeType.ALL, orphanRemoval = true) + // private AgentUserToken agentUserToken; + + @Column(name = "USER_ID", nullable = false, length = 50) + private String userId; + + @Column(name = "USER_PASSWORD", nullable = false, length = 128) + private String userPassword; + + @Column(name = "USER_NAME", nullable = false, length = 50) + private String userName; + + @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) { + this.userId = userId; + this.userPassword = userPassword; + this.userName = userName; + this.userRole = userRole; + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUserRole.java b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUserRole.java new file mode 100644 index 0000000..cc628b8 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUserRole.java @@ -0,0 +1,31 @@ +package com.spring.domain.user.entity; + +import java.util.Arrays; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum AgentUserRole { + + ROLE_USER("ROLE_USER", "사용자"), + ROLE_ADMIN("ROLE_ADMIN", "관리자"), + ROLE_ANONYMOUS("ROLE_ANONYMOUS", "익명사용자"); + + @JsonValue + private final String role; + private final String roleName; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static AgentUserRole fromRole(String role) { + return Arrays.stream(values()) + .filter(value -> value.getRole().equalsIgnoreCase(role)) + .findFirst() + .orElse(null); + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/user/entity/MemberRefreshToken.java b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUserToken.java similarity index 63% rename from batch-quartz/src/main/java/com/spring/domain/user/entity/MemberRefreshToken.java rename to batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUserToken.java index 9526107..244deba 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/entity/MemberRefreshToken.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUserToken.java @@ -5,6 +5,7 @@ import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; +import javax.persistence.ForeignKey; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapsId; @@ -12,23 +13,24 @@ import javax.persistence.OneToOne; import javax.persistence.Table; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@Table(name = "MEMBER_REFRESH_TOKEN") +@Table(name = "AGENT_USER_TOKEN") @Getter @NoArgsConstructor(access = AccessLevel.PRIVATE) -public class MemberRefreshToken { +public class AgentUserToken { @Id - @Column(name = "MEMBER_ID", nullable = false) - private UUID memberId; - + @Column(name = "ID", nullable = false) + private UUID id; + @OneToOne(fetch = FetchType.LAZY) @MapsId - @JoinColumn(name = "member_id") - private Member member; + @JoinColumn(name = "ID", referencedColumnName = "ID", columnDefinition = "BINARY(16)", foreignKey = @ForeignKey(name = "FK_AGENT_USER_TOKEN")) + private AgentUser agentUser; @Column(name = "REFRESH_TOKEN", nullable = false) private String refreshToken; @@ -36,6 +38,13 @@ public class MemberRefreshToken { @Column(name = "REISSUE_COUNT", nullable = false) private int reissueCount; + @Builder + public AgentUserToken(AgentUser agentUser, String refreshToken, int reissueCount) { + this.agentUser = agentUser; + this.refreshToken = refreshToken; + this.reissueCount = reissueCount; + } + public void updateRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/entity/Member.java b/batch-quartz/src/main/java/com/spring/domain/user/entity/Member.java deleted file mode 100644 index cdd6faf..0000000 --- a/batch-quartz/src/main/java/com/spring/domain/user/entity/Member.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.spring.domain.user.entity; - -import java.util.UUID; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "MEMBER") -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class Member { - - @Id - @Column(name = "MEMBER_ID", nullable = false) - private UUID memberId; - - @Column(name = "LOGIN_ID", nullable = false, length = 50) - private String loginId; - - @Column(name = "PASSWORD", nullable = false, length = 128) - private String password; - - @Column(name = "MEMBER_NAME", nullable = false, length = 50) - private String userName; - -} diff --git a/batch-quartz/src/main/java/com/spring/domain/user/error/UserIdConflictException.java b/batch-quartz/src/main/java/com/spring/domain/user/error/UserIdConflictException.java new file mode 100644 index 0000000..5964847 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/user/error/UserIdConflictException.java @@ -0,0 +1,11 @@ +package com.spring.domain.user.error; + +import com.spring.common.error.BizBaseException; + +public class UserIdConflictException extends BizBaseException { + + public UserIdConflictException() { + super(UserRule.USER_ID_CONFLICT); + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/user/error/UserNotFoundException.java b/batch-quartz/src/main/java/com/spring/domain/user/error/UserNotFoundException.java index 2b0b594..b7c3dd6 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/error/UserNotFoundException.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/error/UserNotFoundException.java @@ -1,16 +1,11 @@ package com.spring.domain.user.error; import com.spring.common.error.BizBaseException; -import com.spring.common.error.ExceptionRule; public class UserNotFoundException extends BizBaseException { public UserNotFoundException() { - super(ExceptionRule.USER_NOT_FOUND); - } - - public UserNotFoundException(ExceptionRule exceptionRule) { - super(exceptionRule); + super(UserRule.USER_NOT_FOUND); } } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/error/UserRule.java b/batch-quartz/src/main/java/com/spring/domain/user/error/UserRule.java new file mode 100644 index 0000000..54251f4 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/user/error/UserRule.java @@ -0,0 +1,30 @@ +package com.spring.domain.user.error; + +import org.springframework.http.HttpStatus; + +import com.spring.common.error.ErrorRule; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum UserRule implements ErrorRule { + + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다."), + USER_ID_CONFLICT(HttpStatus.CONFLICT, "중복된 아이디 입니다."); + + private final HttpStatus status; + private String message; + + UserRule(HttpStatus status, String message) { + this.status = status; + this.message = message; + } + + public UserRule message(final String message) { + this.message = message; + return this; + } + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserRepository.java b/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserRepository.java new file mode 100644 index 0000000..83d417f --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserRepository.java @@ -0,0 +1,16 @@ +package com.spring.domain.user.repository; + +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.spring.domain.user.entity.AgentUser; + +public interface AgentUserRepository extends JpaRepository { + + Optional findByUserId(String userId); + + boolean existsByUserId(String userId); + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserTokenRepository.java b/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserTokenRepository.java new file mode 100644 index 0000000..8cf53f3 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserTokenRepository.java @@ -0,0 +1,14 @@ +package com.spring.domain.user.repository; + +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.spring.domain.user.entity.AgentUserToken; + +public interface AgentUserTokenRepository extends JpaRepository { + + Optional findByIdAndReissueCountLessThan(UUID id, long count); + +} diff --git a/batch-quartz/src/main/java/com/spring/domain/user/service/SignUpService.java b/batch-quartz/src/main/java/com/spring/domain/user/service/SignUpService.java index f623227..6474a1d 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/service/SignUpService.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/service/SignUpService.java @@ -5,7 +5,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.spring.domain.user.dto.SignUpRequest; -import com.spring.domain.user.repository.AppUserRepository; +import com.spring.domain.user.error.UserIdConflictException; +import com.spring.domain.user.repository.AgentUserRepository; import lombok.RequiredArgsConstructor; @@ -13,13 +14,20 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class SignUpService { - private final AppUserRepository appUserRepository; + private final AgentUserRepository agentUserRepository; private final PasswordEncoder passwordEncoder; + @Transactional(readOnly = true) + public boolean isConflictUserId(String userId) { + boolean result = agentUserRepository.existsByUserId(userId); + if (result) throw new UserIdConflictException(); + return result; + } + @Transactional public void signUp(SignUpRequest request) { - request.encodePassword(passwordEncoder.encode(request.getPassword())); - appUserRepository.save(request.toEntity()); + request.encodePassword(passwordEncoder.encode(request.getUserPassword())); + agentUserRepository.save(request.toEntity()); } } diff --git a/batch-quartz/src/main/java/com/spring/infra/quartz/QuartzConfig.java b/batch-quartz/src/main/java/com/spring/infra/quartz/QuartzConfig.java index 74a7fef..fd7549c 100644 --- a/batch-quartz/src/main/java/com/spring/infra/quartz/QuartzConfig.java +++ b/batch-quartz/src/main/java/com/spring/infra/quartz/QuartzConfig.java @@ -77,7 +77,7 @@ public class QuartzConfig { factory.setDataSource(dataSource); factory.setTransactionManager(transactionManager); factory.setJobFactory(jobFactory); - factory.setAutoStartup(true); + factory.setAutoStartup(false); factory.setWaitForJobsToCompleteOnShutdown(true); return factory; } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/config/PermittedURI.java b/batch-quartz/src/main/java/com/spring/infra/security/config/PermittedURI.java new file mode 100644 index 0000000..59e5223 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/infra/security/config/PermittedURI.java @@ -0,0 +1,19 @@ +package com.spring.infra.security.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum PermittedURI { + + ROOT_URI("/"), + H2_CONSOLE_URI("/h2-console/**"), + FAVICON_URI("/favicon.ico"), + USER_CONFLICT_URI("/api/user/conflict/{userId}"), + USER_SIGN_UP("/api/user/sign-up"), + USER_SIGN_IN("/sign-in"), + USER_SIGN_OUT("/sign-out"); + + private final String uri; +} diff --git a/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java b/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java index 8289804..5fc2de6 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java @@ -1,6 +1,6 @@ package com.spring.infra.security.config; -import java.util.List; +import java.util.Arrays; import org.springframework.boot.autoconfigure.security.servlet.PathRequest; import org.springframework.context.annotation.Bean; @@ -47,13 +47,6 @@ import com.spring.infra.security.provider.UserAuthenticationProvider; @EnableWebSecurity @EnableMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfig { - - private static final String[] PERMITTED_URI = { - "/", - "/h2-console/**", - "/favicon.ico", - "/api/user/sign-up" - }; /** * Spring Security의 필터 체인을 구성합니다. @@ -79,13 +72,13 @@ public class SecurityConfig { .httpBasic(HttpBasicConfigurer::disable) .formLogin(FormLoginConfigurer::disable) .authorizeHttpRequests(auth -> auth - .antMatchers(PERMITTED_URI).permitAll() + .antMatchers(Arrays.stream(PermittedURI.values()).map(PermittedURI::getUri).toArray(String[]::new)).permitAll() .anyRequest().authenticated() ) .logout(logout -> logout - .logoutUrl("/sign-out") + .logoutUrl(PermittedURI.USER_SIGN_OUT.getUri()) .addLogoutHandler(new SignOutHandler(tokenService)) - .logoutSuccessUrl("/") + .logoutSuccessUrl(PermittedURI.ROOT_URI.getUri()) .invalidateHttpSession(true)) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) @@ -95,7 +88,7 @@ public class SecurityConfig { UsernamePasswordAuthenticationFilter.class ) .addFilterAfter( - new JwtAuthenticationFilter(tokenService, List.of(PERMITTED_URI)), + new JwtAuthenticationFilter(tokenService), AuthenticationProcessingFilter.class ) .addFilterAfter( diff --git a/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityURI.java b/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityURI.java new file mode 100644 index 0000000..4bfb719 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityURI.java @@ -0,0 +1,14 @@ +package com.spring.infra.security.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SecurityURI { + + REDIRECT_URI("/dashboard"); + + private final String uri; + +} diff --git a/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java b/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java index 50a96ba..5b05732 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java @@ -1,5 +1,6 @@ package com.spring.infra.security.domain; +import java.util.Arrays; import java.util.Collection; import java.util.stream.Collectors; @@ -7,7 +8,8 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import com.spring.domain.user.entity.AppUser; +import com.spring.domain.user.entity.AgentUser; +import com.spring.domain.user.entity.AgentUserRole; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -23,16 +25,16 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public final class UserPrincipal implements UserDetails { - private final transient AppUser appUser; + private final transient AgentUser agentUser; /** - * AppUser 객체로부터 UserPrincipal 객체를 생성합니다. + * AgentUser 객체로부터 UserPrincipal 객체를 생성합니다. * - * @param appUser 변환할 AppUser 객체 + * @param agentUser 변환할 AgentUser 객체 * @return 생성된 UserPrincipal 객체 */ - public static UserPrincipal valueOf(AppUser appUser) { - return new UserPrincipal(appUser); + public static UserPrincipal valueOf(AgentUser agentUser) { + return new UserPrincipal(agentUser); } /** @@ -42,10 +44,11 @@ public final class UserPrincipal implements UserDetails { */ @Override public Collection getAuthorities() { - return appUser.getAppUserRoleMap().stream() - .map(role -> role.getAppUserRole().getRoleType()) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); + return Arrays.stream(AgentUserRole.values()) + .filter(role -> Arrays.asList(agentUser.getUserRole()).contains(role)) + .map(AgentUserRole::getRole) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); } /** @@ -96,7 +99,7 @@ public final class UserPrincipal implements UserDetails { */ @Override public String getPassword() { - return appUser.getPassword(); + return agentUser.getUserPassword(); } /** @@ -106,7 +109,7 @@ public final class UserPrincipal implements UserDetails { */ @Override public String getUsername() { - return appUser.getLoginId(); + return agentUser.getUserName(); } } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityExceptionRule.java b/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityExceptionRule.java index 5656499..0300e39 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityExceptionRule.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/error/SecurityExceptionRule.java @@ -14,6 +14,7 @@ public enum SecurityExceptionRule implements ErrorRule { UNSUPPORTED_MEDIA_ERROR(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "지원되지 않는 유형 입니다."), USER_BAD_REQUEST(HttpStatus.BAD_REQUEST, "사용자 정보가 올바르지 않습니다."), USER_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "사용자 인증에 실패 하였습니다."), + USER_NOT_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 틀립니다."), USER_FORBIDDEN(HttpStatus.FORBIDDEN, "사용자 권한이 없습니다."), JWT_TOKEN_ERROR(HttpStatus.UNAUTHORIZED, "토큰이 잘못되었습니다."), JWT_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "토큰 정보가 없습니다."), diff --git a/batch-quartz/src/main/java/com/spring/infra/security/filter/AuthenticationProcessingFilter.java b/batch-quartz/src/main/java/com/spring/infra/security/filter/AuthenticationProcessingFilter.java index 2692a33..5f69a65 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/filter/AuthenticationProcessingFilter.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/filter/AuthenticationProcessingFilter.java @@ -17,13 +17,14 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.StringUtils; import com.fasterxml.jackson.databind.ObjectMapper; +import com.spring.infra.security.config.PermittedURI; 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 = "/sign-in"; + private static final String DEFAULT_LOGIN_REQUEST_URL = 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); diff --git a/batch-quartz/src/main/java/com/spring/infra/security/filter/JwtAuthenticationFilter.java b/batch-quartz/src/main/java/com/spring/infra/security/filter/JwtAuthenticationFilter.java index 66bb99e..e7c1564 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/filter/JwtAuthenticationFilter.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/filter/JwtAuthenticationFilter.java @@ -1,7 +1,7 @@ package com.spring.infra.security.filter; import java.io.IOException; -import java.util.List; +import java.util.Arrays; import java.util.Optional; import javax.servlet.FilterChain; @@ -16,6 +16,7 @@ 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; import com.spring.infra.security.jwt.JwtTokenRule; import com.spring.infra.security.jwt.JwtTokenService; @@ -34,7 +35,6 @@ public final class JwtAuthenticationFilter extends OncePerRequestFilter { private final AntPathMatcher pathMatcher = new AntPathMatcher(); private final JwtTokenService jwtTokenService; - private final List permitAllUrls; private static final String EXCEPTION_ATTRIBUTE = "exception"; /** @@ -54,7 +54,9 @@ public final class JwtAuthenticationFilter extends OncePerRequestFilter { ) throws ServletException, IOException { String requestURI = request.getRequestURI(); - if (permitAllUrls.stream().anyMatch(url -> pathMatcher.match(url, requestURI)) && !"/".equals(requestURI)) { + if (Arrays.stream(PermittedURI.values()).map(PermittedURI::getUri).anyMatch(uri -> pathMatcher.match(uri, requestURI)) && + !PermittedURI.ROOT_URI.getUri().equals(requestURI)) + { filterChain.doFilter(request, response); return; } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java index 0a75c2c..dadc1a8 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java @@ -14,6 +14,8 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; +import com.spring.infra.security.config.PermittedURI; + import lombok.RequiredArgsConstructor; /** @@ -40,7 +42,7 @@ public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoin } else if (isApiRequest(request)) { handleApiRequest(request, response, authException); } else { - response.sendRedirect("/"); + response.sendRedirect(PermittedURI.ROOT_URI.getUri()); } } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java index 767ef5b..790966f 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java @@ -17,6 +17,7 @@ import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; +import com.spring.infra.security.config.SecurityURI; import com.spring.infra.security.dto.SignResponse; import com.spring.infra.security.jwt.JwtTokenRule; import com.spring.infra.security.jwt.JwtTokenService; @@ -42,7 +43,7 @@ public class SigninSuccessHandler implements AuthenticationSuccessHandler { jwtTokenService.generateRefreshToken(response, authentication); SavedRequest savedRequest = requestCache.getRequest(request, response); - String targetUrl = (savedRequest != null) ? savedRequest.getRedirectUrl() : "/dashboard"; + String targetUrl = (savedRequest != null) ? savedRequest.getRedirectUrl() : SecurityURI.REDIRECT_URI.getUri(); response.setHeader(HttpHeaders.AUTHORIZATION, JwtTokenRule.BEARER_PREFIX.getValue() + accessToken); response.setContentType(MediaType.APPLICATION_JSON_VALUE); diff --git a/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java b/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java index 4b44224..843276d 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java @@ -4,6 +4,7 @@ import java.security.Key; import java.util.List; import java.util.stream.Collectors; +import javax.persistence.EntityNotFoundException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -17,6 +18,11 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.spring.domain.user.entity.AgentUser; +import com.spring.domain.user.entity.AgentUserToken; +import com.spring.domain.user.repository.AgentUserRepository; +import com.spring.domain.user.repository.AgentUserTokenRepository; +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.UserPrincipalService; @@ -38,6 +44,8 @@ public class JwtTokenService { private final JwtTokenUtil jwtTokenUtil; private final JwtTokenGenerator jwtTokenGenerator; private final UserPrincipalService userPrincipalService; + private final AgentUserRepository agentUserRepository; + private final AgentUserTokenRepository agentUserTokenRepository; private final Key accessSecretKey; private final Key refreshSecretKey; private final long refreshExpiration; @@ -46,11 +54,15 @@ public class JwtTokenService { JwtTokenUtil jwtTokenUtil, JwtTokenGenerator jwtTokenGenerator, UserPrincipalService userPrincipalService, + AgentUserRepository agentUserRepository, + AgentUserTokenRepository agentUserTokenRepository, JwtProperties jwtProperties ) { this.jwtTokenUtil = jwtTokenUtil; this.jwtTokenGenerator = jwtTokenGenerator; this.userPrincipalService = userPrincipalService; + this.agentUserRepository = agentUserRepository; + this.agentUserTokenRepository = agentUserTokenRepository; this.accessSecretKey = jwtTokenUtil.getSigningKey(jwtProperties.getAccessToken().getSecret()); this.refreshSecretKey = jwtTokenUtil.getSigningKey(jwtProperties.getRefreshToken().getSecret()); this.refreshExpiration = jwtProperties.getRefreshToken().getExpiration(); @@ -76,8 +88,20 @@ public class JwtTokenService { * @param authentication 인증 정보 * @return 생성된 리프레시 토큰 */ + @Transactional public String generateRefreshToken(HttpServletResponse response, Authentication authentication) { String refreshToken = jwtTokenGenerator.generateRefreshToken(authentication); + + UserPrincipal user = (UserPrincipal) authentication.getPrincipal(); + AgentUser agentUser = agentUserRepository.findById(user.getAgentUser().getId()).orElseThrow(() -> new EntityNotFoundException("AgentUser not found")); + agentUserTokenRepository.findById(agentUser.getId()) + .ifPresentOrElse( + it -> it.updateRefreshToken(refreshToken), + () -> agentUserTokenRepository.save( + AgentUserToken.builder().agentUser(agentUser).refreshToken(refreshToken).build() + ) + ); + ResponseCookie cookie = setTokenToCookie(JwtTokenRule.REFRESH_PREFIX.getValue(), refreshToken, refreshExpiration); response.addHeader(JwtTokenRule.JWT_ISSUE_HEADER.getValue(), cookie.toString()); return refreshToken; diff --git a/batch-quartz/src/main/java/com/spring/infra/security/provider/UserAuthenticationProvider.java b/batch-quartz/src/main/java/com/spring/infra/security/provider/UserAuthenticationProvider.java index aa3c5f2..7081f5a 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/provider/UserAuthenticationProvider.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/provider/UserAuthenticationProvider.java @@ -1,7 +1,6 @@ package com.spring.infra.security.provider; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -10,6 +9,8 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import com.spring.infra.security.error.SecurityAuthException; +import com.spring.infra.security.error.SecurityExceptionRule; import com.spring.infra.security.service.UserPrincipalService; import lombok.RequiredArgsConstructor; @@ -24,11 +25,11 @@ public class UserAuthenticationProvider implements AuthenticationProvider { @Transactional(readOnly = true) @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - String loginId = authentication.getName(); + String username = authentication.getName(); String password = String.valueOf(authentication.getCredentials()); - UserDetails user = userPrincipalService.loadUserByUsername(loginId); + UserDetails user = userPrincipalService.loadUserByUsername(username); if (isNotMatches(password, user.getPassword())) { - throw new BadCredentialsException(loginId); + throw new SecurityAuthException(SecurityExceptionRule.USER_NOT_PASSWORD.getMessage()); } return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/service/UserPrincipalService.java b/batch-quartz/src/main/java/com/spring/infra/security/service/UserPrincipalService.java index 55d4a3e..16fcdf4 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/service/UserPrincipalService.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/service/UserPrincipalService.java @@ -6,8 +6,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.spring.domain.user.entity.AppUser; -import com.spring.domain.user.repository.AppUserRepository; +import com.spring.domain.user.entity.AgentUser; +import com.spring.domain.user.repository.AgentUserRepository; import com.spring.infra.security.domain.UserPrincipal; import com.spring.infra.security.error.SecurityExceptionRule; @@ -17,12 +17,12 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class UserPrincipalService implements UserDetailsService { - private final AppUserRepository appUserRepository; + private final AgentUserRepository agentUserRepository; @Transactional(readOnly = true) @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - AppUser user = appUserRepository.findByLoginId(username) + AgentUser user = agentUserRepository.findByUserId(username) .orElseThrow(() -> new UsernameNotFoundException(SecurityExceptionRule.USER_UNAUTHORIZED.getMessage())); return UserPrincipal.valueOf(user); } diff --git a/batch-quartz/src/main/java/com/spring/web/controller/SignController.java b/batch-quartz/src/main/java/com/spring/web/controller/SignController.java index e084b4c..4d8e85b 100644 --- a/batch-quartz/src/main/java/com/spring/web/controller/SignController.java +++ b/batch-quartz/src/main/java/com/spring/web/controller/SignController.java @@ -6,6 +6,8 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import com.spring.domain.user.entity.AgentUserRole; + @Controller @RequestMapping("/") public class SignController { @@ -20,6 +22,7 @@ public class SignController { public String signIn(Model model) { model.addAttribute("baseUrl", baseUrl); model.addAttribute("timeout", timeout); + model.addAttribute("roles", AgentUserRole.values()); return "pages/sign/sign-in"; } diff --git a/batch-quartz/src/main/resources/static/js/apis/sign-api.js b/batch-quartz/src/main/resources/static/js/apis/sign-api.js index feb31de..88ed342 100644 --- a/batch-quartz/src/main/resources/static/js/apis/sign-api.js +++ b/batch-quartz/src/main/resources/static/js/apis/sign-api.js @@ -14,9 +14,14 @@ const signService = { removeTokens(); }, - signUp: async (loginId, password, userName) => { - const response = await apiClient.post('/api/user/sign-up', { loginId, password, userName }); + signUp: async (params) => { + const response = await apiClient.post('/api/user/sign-up', params); return response.data; + }, + + isConflictUserId: async (userId) => { + const response = await apiClient.get(`/api/user/conflict/${userId}`); + return response.data.data; } }; diff --git a/batch-quartz/src/main/resources/static/js/pages/sign/sign-in.js b/batch-quartz/src/main/resources/static/js/pages/sign/sign-in.js index 4161ae7..ccdf58c 100644 --- a/batch-quartz/src/main/resources/static/js/pages/sign/sign-in.js +++ b/batch-quartz/src/main/resources/static/js/pages/sign/sign-in.js @@ -1,14 +1,12 @@ import signService from '../../apis/sign-api.js'; document.addEventListener('DOMContentLoaded', () => { - + const signupModal = new bootstrap.Modal(document.getElementById('signupModal')); const signInButton = document.getElementById('signIn'); const signupButton = document.getElementById('signUp'); - const signupModal = new bootstrap.Modal(document.getElementById('signupModal')); - const signupSubmit = document.getElementById('signupSubmit'); + const signupForm = document.getElementById('signupForm'); - signInButton.addEventListener('click', (e) => { - e.preventDefault(); + signInButton.addEventListener('click', () => { const username = document.getElementById('username').value; const password = document.getElementById('password').value; signService.signIn(username, password).then(response => { @@ -18,20 +16,25 @@ document.addEventListener('DOMContentLoaded', () => { }); }); - signupButton.addEventListener('click', (e) => { - e.preventDefault(); + signupButton.addEventListener('click', () => { signupModal.show(); }); - signupSubmit.addEventListener('click', (e) => { + signupForm.addEventListener('submit', async (e) => { e.preventDefault(); - const loginId = document.getElementById('loginId').value; - const password = document.getElementById('loginPassword').value; - const userName = document.getElementById('userName').value; - signService.signUp(loginId, password, userName).then(() => { - alert(`회원가입이 완료 되었습니다.`); - signupModal.hide(); - }); + const userId = document.getElementById('userId').value; + const isConflict = await signService.isConflictUserId(userId); + if (!isConflict) { + const params = Object.fromEntries(new FormData(signupForm)); + signService.signUp(params).then(() => { + alert(`회원가입이 완료 되었습니다.`); + signupModal.hide(); + }); + } + }); + + document.getElementById('signupModal').addEventListener('hidden.bs.modal', () => { + signupForm.reset(); }); }); \ No newline at end of file diff --git a/batch-quartz/src/main/resources/templates/pages/sign/sign-in.html b/batch-quartz/src/main/resources/templates/pages/sign/sign-in.html index 68c19c3..5c7a0f7 100644 --- a/batch-quartz/src/main/resources/templates/pages/sign/sign-in.html +++ b/batch-quartz/src/main/resources/templates/pages/sign/sign-in.html @@ -54,36 +54,44 @@ - + +