diff --git a/batch-quartz/pom.xml b/batch-quartz/pom.xml
index ff1d3aa..176879a 100644
--- a/batch-quartz/pom.xml
+++ b/batch-quartz/pom.xml
@@ -61,6 +61,10 @@
mybatis-spring-boot-starter
2.2.0
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
io.jsonwebtoken
jjwt-api
diff --git a/batch-quartz/src/main/java/com/spring/domain/user/api/AuthController.java b/batch-quartz/src/main/java/com/spring/domain/user/api/GenerateTokenApi.java
similarity index 97%
rename from batch-quartz/src/main/java/com/spring/domain/user/api/AuthController.java
rename to batch-quartz/src/main/java/com/spring/domain/user/api/GenerateTokenApi.java
index 5bafd12..f7dbed8 100644
--- a/batch-quartz/src/main/java/com/spring/domain/user/api/AuthController.java
+++ b/batch-quartz/src/main/java/com/spring/domain/user/api/GenerateTokenApi.java
@@ -18,7 +18,7 @@ import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
-public class AuthController {
+public class GenerateTokenApi {
private final AuthService authService;
private final JwtTokenService jwtTokenService;
diff --git a/batch-quartz/src/main/java/com/spring/domain/user/view/UserView.java b/batch-quartz/src/main/java/com/spring/domain/user/view/UserView.java
new file mode 100644
index 0000000..a341bfe
--- /dev/null
+++ b/batch-quartz/src/main/java/com/spring/domain/user/view/UserView.java
@@ -0,0 +1,16 @@
+package com.spring.domain.user.view;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping("/user")
+public class UserView {
+
+ @GetMapping("/sign-in")
+ public String signin() {
+ return "/views/user/signIn";
+ }
+
+}
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/SecurityConfig.java b/batch-quartz/src/main/java/com/spring/infra/security/config/SecurityConfig.java
index 48867b2..6450b8f 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
@@ -2,8 +2,11 @@ package com.spring.infra.security.config;
import java.util.List;
+import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -18,10 +21,15 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.spring.infra.security.filter.AuthenticationProcessingFilter;
import com.spring.infra.security.filter.JwtAuthenticationFilter;
-import com.spring.infra.security.handler.JwtAccessDeniedHandler;
-import com.spring.infra.security.handler.JwtAuthenticationEntryPoint;
+import com.spring.infra.security.handler.SecurityAccessDeniedHandler;
+import com.spring.infra.security.handler.SecurityAuthenticationEntryPoint;
+import com.spring.infra.security.handler.SigninFailureHandler;
+import com.spring.infra.security.handler.SigninSuccessHandler;
import com.spring.infra.security.jwt.JwtTokenService;
+import com.spring.infra.security.provider.UserAuthenticationProvider;
/**
* 애플리케이션의 보안 설정을 담당하는 구성 클래스입니다.
@@ -36,7 +44,7 @@ import com.spring.infra.security.jwt.JwtTokenService;
@EnableMethodSecurity
public class SecurityConfig {
- private static final String[] PERMITTED_URI = {"/favicon.ico", "/api/auth/**", "/signIn", "/h2-console/**"};
+ private static final String[] PERMITTED_URI = {"/favicon.ico", "/api/auth/**", "/user/sign-in", "/h2-console/**"};
/**
* Spring Security의 필터 체인을 구성합니다.
@@ -51,9 +59,10 @@ public class SecurityConfig {
SecurityFilterChain securityFilterChain(
HttpSecurity http,
JwtTokenService tokenService,
- JwtAuthenticationEntryPoint authenticationEntryPoint,
- JwtAccessDeniedHandler accessDeniedHandler) throws Exception
- {
+ SecurityAuthenticationEntryPoint authenticationEntryPoint,
+ SecurityAccessDeniedHandler accessDeniedHandler,
+ AuthenticationProcessingFilter authenticationProcessingFilter
+ ) throws Exception {
http
.headers(headers -> headers.frameOptions(FrameOptionsConfig::sameOrigin))
.csrf(CsrfConfigurer::disable)
@@ -64,12 +73,19 @@ public class SecurityConfig {
.anyRequest().authenticated()
)
.logout(logout -> logout
- .logoutSuccessUrl("/signIn")
+ .logoutSuccessUrl("/user/sign-in")
.invalidateHttpSession(true))
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
- .addFilterBefore(new JwtAuthenticationFilter(tokenService, List.of(PERMITTED_URI)), UsernamePasswordAuthenticationFilter.class)
+ .addFilterBefore(
+ authenticationProcessingFilter,
+ UsernamePasswordAuthenticationFilter.class
+ )
+ .addFilterAfter(
+ new JwtAuthenticationFilter(tokenService, List.of(PERMITTED_URI)),
+ AuthenticationProcessingFilter.class
+ )
.exceptionHandling(ex -> ex
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
@@ -84,7 +100,8 @@ public class SecurityConfig {
*/
@Bean
WebSecurityCustomizer ignoringCustomizer() {
- return web -> web.ignoring().antMatchers("/h2-console/**");
+ return web -> web.ignoring()
+ .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
/**
@@ -97,4 +114,23 @@ public class SecurityConfig {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
+ @Bean
+ AuthenticationManager authenticationManager(UserAuthenticationProvider provider) {
+ return new ProviderManager(provider);
+ }
+
+ @Bean
+ AuthenticationProcessingFilter authenticationProcessingFilter(
+ ObjectMapper objectMapper,
+ AuthenticationManager authenticationManager,
+ SigninSuccessHandler signinSuccessHandler,
+ SigninFailureHandler signinFailureHandler
+ ) {
+ var filter = new AuthenticationProcessingFilter(objectMapper);
+ filter.setAuthenticationManager(authenticationManager);
+ filter.setAuthenticationSuccessHandler(signinSuccessHandler);
+ filter.setAuthenticationFailureHandler(signinFailureHandler);
+ return filter;
+ }
+
}
diff --git a/batch-quartz/src/main/java/com/spring/infra/security/dto/SignInRequest.java b/batch-quartz/src/main/java/com/spring/infra/security/dto/SignInRequest.java
new file mode 100644
index 0000000..9f3e572
--- /dev/null
+++ b/batch-quartz/src/main/java/com/spring/infra/security/dto/SignInRequest.java
@@ -0,0 +1,14 @@
+package com.spring.infra.security.dto;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class SignInRequest {
+
+ private String username;
+ private String password;
+
+}
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
new file mode 100644
index 0000000..afa3f4a
--- /dev/null
+++ b/batch-quartz/src/main/java/com/spring/infra/security/filter/AuthenticationProcessingFilter.java
@@ -0,0 +1,60 @@
+package com.spring.infra.security.filter;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.util.StringUtils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.spring.infra.security.dto.SignInRequest;
+
+public class AuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
+
+ private static final String DEFAULT_LOGIN_REQUEST_URL = "/sign-in";
+ private static final String HTTP_METHOD = "POST";
+ private static final AntPathRequestMatcher DEFAULT_LOGIN_PATH_REQUEST_MATCHER =
+ new AntPathRequestMatcher(DEFAULT_LOGIN_REQUEST_URL, HTTP_METHOD);
+
+ private final ObjectMapper objectMapper;
+
+ public AuthenticationProcessingFilter(ObjectMapper objectMapper) {
+ super(DEFAULT_LOGIN_PATH_REQUEST_MATCHER);
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
+ throws AuthenticationException, IOException, ServletException {
+ if (!isValidRequestType(request)) {
+ throw new IllegalStateException("request is not supported. check request method and content-type");
+ }
+ var signInRequest = objectMapper.readValue(request.getReader(), SignInRequest.class);
+ if (!isValidRequest(signInRequest)) {
+ throw new IllegalArgumentException("Ussername & Password are not empty!!");
+ }
+ var token = new UsernamePasswordAuthenticationToken(signInRequest.getUsername(), signInRequest.getPassword());
+ return this.getAuthenticationManager().authenticate(token);
+ }
+
+ private boolean isValidRequestType(HttpServletRequest request) {
+ return Objects.equals(request.getMethod(), HttpMethod.POST.name()) &&
+ Objects.equals(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);
+ }
+
+ private boolean isValidRequest(SignInRequest signInRequest) {
+ return StringUtils.hasText(signInRequest.getUsername()) ||
+ StringUtils.hasText(signInRequest.getPassword());
+ }
+
+}
diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/JwtAccessDeniedHandler.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAccessDeniedHandler.java
similarity index 95%
rename from batch-quartz/src/main/java/com/spring/infra/security/handler/JwtAccessDeniedHandler.java
rename to batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAccessDeniedHandler.java
index 199e21b..c57dae4 100644
--- a/batch-quartz/src/main/java/com/spring/infra/security/handler/JwtAccessDeniedHandler.java
+++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAccessDeniedHandler.java
@@ -20,7 +20,7 @@ import org.springframework.stereotype.Component;
* @version 1.0
*/
@Component
-public class JwtAccessDeniedHandler implements AccessDeniedHandler {
+public class SecurityAccessDeniedHandler implements AccessDeniedHandler {
/**
* 접근 거부 상황을 처리합니다.
diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/JwtAuthenticationEntryPoint.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java
similarity index 94%
rename from batch-quartz/src/main/java/com/spring/infra/security/handler/JwtAuthenticationEntryPoint.java
rename to batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java
index aaa70d0..5e4185e 100644
--- a/batch-quartz/src/main/java/com/spring/infra/security/handler/JwtAuthenticationEntryPoint.java
+++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SecurityAuthenticationEntryPoint.java
@@ -20,7 +20,7 @@ import org.springframework.stereotype.Component;
* @version 1.0
*/
@Component
-public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
+public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {
/**
* 인증되지 않은 접근을 처리합니다.
diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninFailureHandler.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninFailureHandler.java
new file mode 100644
index 0000000..f724b35
--- /dev/null
+++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninFailureHandler.java
@@ -0,0 +1,25 @@
+package com.spring.infra.security.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SigninFailureHandler implements AuthenticationFailureHandler {
+
+ @Override
+ public void onAuthenticationFailure(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ AuthenticationException exception
+ ) throws IOException, ServletException {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); //401 인증 실패
+ }
+
+}
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
new file mode 100644
index 0000000..c88cb67
--- /dev/null
+++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java
@@ -0,0 +1,48 @@
+package com.spring.infra.security.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.DefaultRedirectStrategy;
+import org.springframework.security.web.RedirectStrategy;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
+import org.springframework.security.web.savedrequest.RequestCache;
+import org.springframework.security.web.savedrequest.SavedRequest;
+import org.springframework.stereotype.Component;
+
+import com.spring.infra.security.jwt.JwtTokenService;
+
+import lombok.RequiredArgsConstructor;
+
+@Component
+@RequiredArgsConstructor
+public class SigninSuccessHandler implements AuthenticationSuccessHandler {
+
+ private final JwtTokenService jwtTokenService;
+ private RequestCache requestCache = new HttpSessionRequestCache();
+ private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+
+ @Override
+ public void onAuthenticationSuccess(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Authentication authentication
+ ) throws IOException, ServletException {
+ jwtTokenService.generateAccessToken(response, authentication);
+ jwtTokenService.generateRefreshToken(response, authentication);
+
+ SavedRequest savedRequest = requestCache.getRequest(request, response);
+ if (savedRequest != null) { // 접근 권한 없는 경로 접근해서 스프링 시큐리티가 인터셉트해서 로그인폼으로 이동 후 로그인 성공한 경우
+ redirectStrategy.sendRedirect(request, response, savedRequest.getRedirectUrl());
+ } else { // 로그인 버튼 눌러서 로그인한 경우 기존에 있던 페이지로 리다이렉트
+ String prevPage = String.valueOf(request.getSession().getAttribute("prevPage"));
+ redirectStrategy.sendRedirect(request, response, prevPage);
+ }
+ }
+
+}
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
new file mode 100644
index 0000000..20e6f42
--- /dev/null
+++ b/batch-quartz/src/main/java/com/spring/infra/security/provider/UserAuthenticationProvider.java
@@ -0,0 +1,43 @@
+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;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Component;
+
+import com.spring.infra.security.service.UserPrincipalService;
+
+import lombok.RequiredArgsConstructor;
+
+@Component
+@RequiredArgsConstructor
+public class UserAuthenticationProvider implements AuthenticationProvider {
+
+ private final PasswordEncoder passwordEncoder;
+ private final UserPrincipalService userPrincipalService;
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ String loginId = authentication.getName();
+ String password = String.valueOf(authentication.getCredentials());
+ UserDetails user = userPrincipalService.loadUserByUsername(loginId);
+ if (isNotMatches(password, user.getPassword())) {
+ throw new BadCredentialsException(loginId);
+ }
+ return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return authentication.equals(UsernamePasswordAuthenticationToken.class);
+ }
+
+ private boolean isNotMatches(String password, String encodePassword) {
+ return !passwordEncoder.matches(password, encodePassword);
+ }
+
+}
diff --git a/batch-quartz/src/main/resources/application.yml b/batch-quartz/src/main/resources/application.yml
index 702a3fe..53e57ac 100644
--- a/batch-quartz/src/main/resources/application.yml
+++ b/batch-quartz/src/main/resources/application.yml
@@ -73,6 +73,14 @@ spring:
threadCount: 10
threadPriority: 5
+ thymeleaf:
+ cache: false
+ check-template-location: false
+ enabled: true
+ prefix: classpath:/templates
+ suffix: .html
+ view-names: /views/*
+
h2:
console: # H2 DB를 웹에서 관리할 수 있는 기능
enabled: true # H2 Console 사용 여부
diff --git a/batch-quartz/src/main/resources/quartz.yml b/batch-quartz/src/main/resources/quartz.yml
deleted file mode 100644
index 7c88af7..0000000
--- a/batch-quartz/src/main/resources/quartz.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-spring:
- quartz:
- scheduler:
- instanceName: batch-quartz
- instance-id: SYS_PROP
- name: BatchQuartzScheduler
-
-org:
- quartz:
- jobStore:
- tablePrefix: QRTZ_
- isClustered: true
- misfireThreshold: 2000
- clusterCheckinInterval: 1000
- class: org.quartz.impl.jdbcjobstore.JobStoreTX
- driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
- acquireTriggersWithinLock: true
- scheduler:
- instance-id:
- instanceName:
- rmi:
- export: false
- proxy: false
- batchTriggerAcquisitionMaxCount: 20
- idleWaitTime: 1000
- skipUpdateCheck: true
- threadPool:
- class: org.quartz.simpl.SimpleThreadPool
- threadCount: 10
- threadPriority: 5
- threadsInheritContextClassLoaderOfInitializingThread: true
- threadNamePrefix: BatchQuartz
-# dataSource:
-# nxcus:
-# driver: org.h2.Driver #oracle.jdbc.driver.OracleDriver
-# URL: 'jdbc:h2:mem:test' #jdbc:oracle:thin:@polarbear:1521:dev
-# user: mindol1004
-# password: 1111
-# maxConnections: 5
-# validationQuery: select 0 from dual
\ No newline at end of file
diff --git a/batch-quartz/src/main/resources/batch-schema.sql b/batch-quartz/src/main/resources/sql-schema/batch-schema.sql
similarity index 100%
rename from batch-quartz/src/main/resources/batch-schema.sql
rename to batch-quartz/src/main/resources/sql-schema/batch-schema.sql
diff --git a/batch-quartz/src/main/resources/quartz-schema.sql b/batch-quartz/src/main/resources/sql-schema/quartz-schema.sql
similarity index 100%
rename from batch-quartz/src/main/resources/quartz-schema.sql
rename to batch-quartz/src/main/resources/sql-schema/quartz-schema.sql
diff --git a/batch-quartz/src/main/resources/static/css/style.css b/batch-quartz/src/main/resources/static/css/style.css
new file mode 100644
index 0000000..49eb822
--- /dev/null
+++ b/batch-quartz/src/main/resources/static/css/style.css
@@ -0,0 +1,87 @@
+body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ background: linear-gradient(to right, #f0f0f5, #e9ecef);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ margin: 0;
+}
+
+.container {
+ background-color: white;
+ padding: 30px;
+ border-radius: 15px;
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
+ width: 350px;
+ text-align: center;
+}
+
+.icon {
+ margin-bottom: 20px;
+}
+
+.icon img {
+ width: 50px; /* 아이콘 크기 조정 */
+}
+
+h1 {
+ color: #333;
+ margin-bottom: 20px;
+}
+
+.input-group {
+ margin-bottom: 15px;
+}
+
+.input-icon {
+ display: flex;
+ align-items: center;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ padding: 10px;
+}
+
+.input-icon img {
+ margin-right: 10px; /* 아이콘과 인풋 간격 조정 */
+ width: 20px; /* 아이콘 크기 조정 */
+ height: 20px; /* 아이콘 크기 조정 */
+}
+
+input {
+ width: 100%;
+ border: none;
+ outline: none;
+ font-size: 16px;
+}
+
+input::placeholder {
+ color: #aaa; /* 플레이스홀더 색상 */
+}
+
+.remember-me {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ margin-bottom: 15px;
+}
+
+.remember-me label {
+ margin-left: 5px;
+}
+
+button {
+ width: 100%;
+ padding: 12px;
+ background-color: #007aff;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 16px;
+ transition: background-color 0.3s;
+}
+
+button:hover {
+ background-color: #005bb5;
+}
\ No newline at end of file
diff --git a/batch-quartz/src/main/resources/static/favicon.ico b/batch-quartz/src/main/resources/static/favicon.ico
new file mode 100644
index 0000000..039dc7a
Binary files /dev/null and b/batch-quartz/src/main/resources/static/favicon.ico differ
diff --git a/batch-quartz/src/main/resources/static/images/user-id.png b/batch-quartz/src/main/resources/static/images/user-id.png
new file mode 100644
index 0000000..b7f95f4
Binary files /dev/null and b/batch-quartz/src/main/resources/static/images/user-id.png differ
diff --git a/batch-quartz/src/main/resources/static/images/user-lock.png b/batch-quartz/src/main/resources/static/images/user-lock.png
new file mode 100644
index 0000000..f6e8d6b
Binary files /dev/null and b/batch-quartz/src/main/resources/static/images/user-lock.png differ
diff --git a/batch-quartz/src/main/resources/static/images/user.png b/batch-quartz/src/main/resources/static/images/user.png
new file mode 100644
index 0000000..eab9166
Binary files /dev/null and b/batch-quartz/src/main/resources/static/images/user.png differ
diff --git a/batch-quartz/src/main/resources/static/js/common/axiosInstance.js b/batch-quartz/src/main/resources/static/js/common/axiosInstance.js
new file mode 100644
index 0000000..e2352b8
--- /dev/null
+++ b/batch-quartz/src/main/resources/static/js/common/axiosInstance.js
@@ -0,0 +1,43 @@
+// Axios 인스턴스 생성
+const axiosInstance = axios.create({
+ baseURL: 'http://localhost:8081', // 기본 URL 설정
+ timeout: 10000, // 요청 타임아웃 설정 (10초)
+ headers: {
+ 'Content-Type': 'application/json',
+ // 필요한 경우 추가 헤더 설정
+ }
+});
+
+// 요청 인터셉터 (필요한 경우)
+axiosInstance.interceptors.request.use(
+ config => {
+ // 요청 전에 수행할 작업 (예: 토큰 추가)
+ const token = localStorage.getItem('token'); // 예시: 로컬 스토리지에서 토큰 가져오기
+ if (token) {
+ config.headers['Authorization'] = `Bearer ${token}`;
+ }
+ return config;
+ },
+ error => {
+ return Promise.reject(error);
+ }
+);
+
+// 응답 인터셉터 (필요한 경우)
+axiosInstance.interceptors.response.use(
+ response => {
+ return response;
+ },
+ error => {
+ console.log(error.response);
+ // 오류 처리 (예: 401 Unauthorized 처리)
+ if (error.response && error.response.status === 401) {
+ // 로그아웃 처리 또는 리다이렉트
+ console.log("111111111111111");
+ console.log(error.response);
+ }
+ return Promise.reject(error);
+ }
+);
+
+export default axiosInstance;
\ No newline at end of file
diff --git a/batch-quartz/src/main/resources/static/js/lib/axios/axios.min.js b/batch-quartz/src/main/resources/static/js/lib/axios/axios.min.js
new file mode 100644
index 0000000..c868e94
--- /dev/null
+++ b/batch-quartz/src/main/resources/static/js/lib/axios/axios.min.js
@@ -0,0 +1,2 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).axios=t()}(this,(function(){"use strict";function e(e){var r,n;function o(r,n){try{var a=e[r](n),s=a.value,u=s instanceof t;Promise.resolve(u?s.v:s).then((function(t){if(u){var n="return"===r?"return":"next";if(!s.k||t.done)return o(n,t);t=e[n](t).value}i(a.done?"return":"normal",t)}),(function(e){o("throw",e)}))}catch(e){i("throw",e)}}function i(e,t){switch(e){case"return":r.resolve({value:t,done:!0});break;case"throw":r.reject(t);break;default:r.resolve({value:t,done:!1})}(r=r.next)?o(r.key,r.arg):n=null}this._invoke=function(e,t){return new Promise((function(i,a){var s={key:e,arg:t,resolve:i,reject:a,next:null};n?n=n.next=s:(r=n=s,o(e,t))}))},"function"!=typeof e.return&&(this.return=void 0)}function t(e,t){this.v=e,this.k=t}function r(e){var r={},n=!1;function o(r,o){return n=!0,o=new Promise((function(t){t(e[r](o))})),{done:!1,value:new t(o,1)}}return r["undefined"!=typeof Symbol&&Symbol.iterator||"@@iterator"]=function(){return this},r.next=function(e){return n?(n=!1,e):o("next",e)},"function"==typeof e.throw&&(r.throw=function(e){if(n)throw n=!1,e;return o("throw",e)}),"function"==typeof e.return&&(r.return=function(e){return n?(n=!1,e):o("return",e)}),r}function n(e){var t,r,n,i=2;for("undefined"!=typeof Symbol&&(r=Symbol.asyncIterator,n=Symbol.iterator);i--;){if(r&&null!=(t=e[r]))return t.call(e);if(n&&null!=(t=e[n]))return new o(t.call(e));r="@@asyncIterator",n="@@iterator"}throw new TypeError("Object is not async iterable")}function o(e){function t(e){if(Object(e)!==e)return Promise.reject(new TypeError(e+" is not an object."));var t=e.done;return Promise.resolve(e.value).then((function(e){return{value:e,done:t}}))}return o=function(e){this.s=e,this.n=e.next},o.prototype={s:null,n:null,next:function(){return t(this.n.apply(this.s,arguments))},return:function(e){var r=this.s.return;return void 0===r?Promise.resolve({value:e,done:!0}):t(r.apply(this.s,arguments))},throw:function(e){var r=this.s.return;return void 0===r?Promise.reject(e):t(r.apply(this.s,arguments))}},new o(e)}function i(e){return new t(e,0)}function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function s(e){for(var t=1;t=0;--i){var a=this.tryEntries[i],s=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var u=n.call(a,"catchLoc"),c=n.call(a,"finallyLoc");if(u&&c){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--t){var r=this.tryEntries[t];if(r.finallyLoc===e)return this.complete(r.completion,r.afterLoc),A(r),y}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var r=this.tryEntries[t];if(r.tryLoc===e){var n=r.completion;if("throw"===n.type){var o=n.arg;A(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,r,n){return this.delegate={iterator:L(t),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=e),y}},t}function c(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}function f(e){return f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},f(e)}function l(e,t,r,n,o,i,a){try{var s=e[i](a),u=s.value}catch(e){return void r(e)}s.done?t(u):Promise.resolve(u).then(n,o)}function h(e){return function(){var t=this,r=arguments;return new Promise((function(n,o){var i=e.apply(t,r);function a(e){l(i,n,o,a,s,"next",e)}function s(e){l(i,n,o,a,s,"throw",e)}a(void 0)}))}}function p(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function d(e,t){for(var r=0;re.length)&&(t=e.length);for(var r=0,n=new Array(t);r2&&void 0!==arguments[2]?arguments[2]:{},i=o.allOwnKeys,a=void 0!==i&&i;if(null!=e)if("object"!==f(e)&&(e=[e]),L(e))for(r=0,n=e.length;r0;)if(t===(r=n[o]).toLowerCase())return r;return null}var Y="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:global,Q=function(e){return!N(e)&&e!==Y};var Z,ee=(Z="undefined"!=typeof Uint8Array&&j(Uint8Array),function(e){return Z&&e instanceof Z}),te=A("HTMLFormElement"),re=function(e){var t=Object.prototype.hasOwnProperty;return function(e,r){return t.call(e,r)}}(),ne=A("RegExp"),oe=function(e,t){var r=Object.getOwnPropertyDescriptors(e),n={};X(r,(function(r,o){var i;!1!==(i=t(r,o,e))&&(n[o]=i||r)})),Object.defineProperties(e,n)},ie="abcdefghijklmnopqrstuvwxyz",ae="0123456789",se={DIGIT:ae,ALPHA:ie,ALPHA_DIGIT:ie+ie.toUpperCase()+ae};var ue,ce,fe,le,he=A("AsyncFunction"),pe=(ue="function"==typeof setImmediate,ce=F(Y.postMessage),ue?setImmediate:ce?(fe="axios@".concat(Math.random()),le=[],Y.addEventListener("message",(function(e){var t=e.source,r=e.data;t===Y&&r===fe&&le.length&&le.shift()()}),!1),function(e){le.push(e),Y.postMessage(fe,"*")}):function(e){return setTimeout(e)}),de="undefined"!=typeof queueMicrotask?queueMicrotask.bind(Y):"undefined"!=typeof process&&process.nextTick||pe,ve={isArray:L,isArrayBuffer:_,isBuffer:function(e){return null!==e&&!N(e)&&null!==e.constructor&&!N(e.constructor)&&F(e.constructor.isBuffer)&&e.constructor.isBuffer(e)},isFormData:function(e){var t;return e&&("function"==typeof FormData&&e instanceof FormData||F(e.append)&&("formdata"===(t=k(e))||"object"===t&&F(e.toString)&&"[object FormData]"===e.toString()))},isArrayBufferView:function(e){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&_(e.buffer)},isString:C,isNumber:U,isBoolean:function(e){return!0===e||!1===e},isObject:B,isPlainObject:D,isReadableStream:W,isRequest:G,isResponse:K,isHeaders:V,isUndefined:N,isDate:I,isFile:q,isBlob:M,isRegExp:ne,isFunction:F,isStream:function(e){return B(e)&&F(e.pipe)},isURLSearchParams:H,isTypedArray:ee,isFileList:z,forEach:X,merge:function e(){for(var t=Q(this)&&this||{},r=t.caseless,n={},o=function(t,o){var i=r&&$(n,o)||o;D(n[i])&&D(t)?n[i]=e(n[i],t):D(t)?n[i]=e({},t):L(t)?n[i]=t.slice():n[i]=t},i=0,a=arguments.length;i3&&void 0!==arguments[3]?arguments[3]:{},o=n.allOwnKeys;return X(t,(function(t,n){r&&F(t)?e[n]=x(t,r):e[n]=t}),{allOwnKeys:o}),e},trim:function(e){return e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},stripBOM:function(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e},inherits:function(e,t,r,n){e.prototype=Object.create(t.prototype,n),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),r&&Object.assign(e.prototype,r)},toFlatObject:function(e,t,r,n){var o,i,a,s={};if(t=t||{},null==e)return t;do{for(i=(o=Object.getOwnPropertyNames(e)).length;i-- >0;)a=o[i],n&&!n(a,e,t)||s[a]||(t[a]=e[a],s[a]=!0);e=!1!==r&&j(e)}while(e&&(!r||r(e,t))&&e!==Object.prototype);return t},kindOf:k,kindOfTest:A,endsWith:function(e,t,r){e=String(e),(void 0===r||r>e.length)&&(r=e.length),r-=t.length;var n=e.indexOf(t,r);return-1!==n&&n===r},toArray:function(e){if(!e)return null;if(L(e))return e;var t=e.length;if(!U(t))return null;for(var r=new Array(t);t-- >0;)r[t]=e[t];return r},forEachEntry:function(e,t){for(var r,n=(e&&e[Symbol.iterator]).call(e);(r=n.next())&&!r.done;){var o=r.value;t.call(e,o[0],o[1])}},matchAll:function(e,t){for(var r,n=[];null!==(r=e.exec(t));)n.push(r);return n},isHTMLForm:te,hasOwnProperty:re,hasOwnProp:re,reduceDescriptors:oe,freezeMethods:function(e){oe(e,(function(t,r){if(F(e)&&-1!==["arguments","caller","callee"].indexOf(r))return!1;var n=e[r];F(n)&&(t.enumerable=!1,"writable"in t?t.writable=!1:t.set||(t.set=function(){throw Error("Can not rewrite read-only method '"+r+"'")}))}))},toObjectSet:function(e,t){var r={},n=function(e){e.forEach((function(e){r[e]=!0}))};return L(e)?n(e):n(String(e).split(t)),r},toCamelCase:function(e){return e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,(function(e,t,r){return t.toUpperCase()+r}))},noop:function(){},toFiniteNumber:function(e,t){return null!=e&&Number.isFinite(e=+e)?e:t},findKey:$,global:Y,isContextDefined:Q,ALPHABET:se,generateString:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:16,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:se.ALPHA_DIGIT,r="",n=t.length;e--;)r+=t[Math.random()*n|0];return r},isSpecCompliantForm:function(e){return!!(e&&F(e.append)&&"FormData"===e[Symbol.toStringTag]&&e[Symbol.iterator])},toJSONObject:function(e){var t=new Array(10);return function e(r,n){if(B(r)){if(t.indexOf(r)>=0)return;if(!("toJSON"in r)){t[n]=r;var o=L(r)?[]:{};return X(r,(function(t,r){var i=e(t,n+1);!N(i)&&(o[r]=i)})),t[n]=void 0,o}}return r}(e,0)},isAsyncFn:he,isThenable:function(e){return e&&(B(e)||F(e))&&F(e.then)&&F(e.catch)},setImmediate:pe,asap:de};function ye(e,t,r,n,o){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack,this.message=e,this.name="AxiosError",t&&(this.code=t),r&&(this.config=r),n&&(this.request=n),o&&(this.response=o,this.status=o.status?o.status:null)}ve.inherits(ye,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:ve.toJSONObject(this.config),code:this.code,status:this.status}}});var me=ye.prototype,be={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach((function(e){be[e]={value:e}})),Object.defineProperties(ye,be),Object.defineProperty(me,"isAxiosError",{value:!0}),ye.from=function(e,t,r,n,o,i){var a=Object.create(me);return ve.toFlatObject(e,a,(function(e){return e!==Error.prototype}),(function(e){return"isAxiosError"!==e})),ye.call(a,e.message,t,r,n,o),a.cause=e,a.name=e.name,i&&Object.assign(a,i),a};function ge(e){return ve.isPlainObject(e)||ve.isArray(e)}function we(e){return ve.endsWith(e,"[]")?e.slice(0,-2):e}function Ee(e,t,r){return e?e.concat(t).map((function(e,t){return e=we(e),!r&&t?"["+e+"]":e})).join(r?".":""):t}var Oe=ve.toFlatObject(ve,{},null,(function(e){return/^is[A-Z]/.test(e)}));function Se(e,t,r){if(!ve.isObject(e))throw new TypeError("target must be an object");t=t||new FormData;var n=(r=ve.toFlatObject(r,{metaTokens:!0,dots:!1,indexes:!1},!1,(function(e,t){return!ve.isUndefined(t[e])}))).metaTokens,o=r.visitor||c,i=r.dots,a=r.indexes,s=(r.Blob||"undefined"!=typeof Blob&&Blob)&&ve.isSpecCompliantForm(t);if(!ve.isFunction(o))throw new TypeError("visitor must be a function");function u(e){if(null===e)return"";if(ve.isDate(e))return e.toISOString();if(!s&&ve.isBlob(e))throw new ye("Blob is not supported. Use a Buffer instead.");return ve.isArrayBuffer(e)||ve.isTypedArray(e)?s&&"function"==typeof Blob?new Blob([e]):Buffer.from(e):e}function c(e,r,o){var s=e;if(e&&!o&&"object"===f(e))if(ve.endsWith(r,"{}"))r=n?r:r.slice(0,-2),e=JSON.stringify(e);else if(ve.isArray(e)&&function(e){return ve.isArray(e)&&!e.some(ge)}(e)||(ve.isFileList(e)||ve.endsWith(r,"[]"))&&(s=ve.toArray(e)))return r=we(r),s.forEach((function(e,n){!ve.isUndefined(e)&&null!==e&&t.append(!0===a?Ee([r],n,i):null===a?r:r+"[]",u(e))})),!1;return!!ge(e)||(t.append(Ee(o,r,i),u(e)),!1)}var l=[],h=Object.assign(Oe,{defaultVisitor:c,convertValue:u,isVisitable:ge});if(!ve.isObject(e))throw new TypeError("data must be an object");return function e(r,n){if(!ve.isUndefined(r)){if(-1!==l.indexOf(r))throw Error("Circular reference detected in "+n.join("."));l.push(r),ve.forEach(r,(function(r,i){!0===(!(ve.isUndefined(r)||null===r)&&o.call(t,r,ve.isString(i)?i.trim():i,n,h))&&e(r,n?n.concat(i):[i])})),l.pop()}}(e),t}function xe(e){var t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,(function(e){return t[e]}))}function Re(e,t){this._pairs=[],e&&Se(e,this,t)}var Te=Re.prototype;function je(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function ke(e,t,r){if(!t)return e;var n,o=r&&r.encode||je,i=r&&r.serialize;if(n=i?i(t,r):ve.isURLSearchParams(t)?t.toString():new Re(t,r).toString(o)){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+n}return e}Te.append=function(e,t){this._pairs.push([e,t])},Te.toString=function(e){var t=e?function(t){return e.call(this,t,xe)}:xe;return this._pairs.map((function(e){return t(e[0])+"="+t(e[1])}),"").join("&")};var Ae=function(){function e(){p(this,e),this.handlers=[]}return v(e,[{key:"use",value:function(e,t,r){return this.handlers.push({fulfilled:e,rejected:t,synchronous:!!r&&r.synchronous,runWhen:r?r.runWhen:null}),this.handlers.length-1}},{key:"eject",value:function(e){this.handlers[e]&&(this.handlers[e]=null)}},{key:"clear",value:function(){this.handlers&&(this.handlers=[])}},{key:"forEach",value:function(e){ve.forEach(this.handlers,(function(t){null!==t&&e(t)}))}}]),e}(),Pe={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},Le={isBrowser:!0,classes:{URLSearchParams:"undefined"!=typeof URLSearchParams?URLSearchParams:Re,FormData:"undefined"!=typeof FormData?FormData:null,Blob:"undefined"!=typeof Blob?Blob:null},protocols:["http","https","file","blob","url","data"]},Ne="undefined"!=typeof window&&"undefined"!=typeof document,_e="object"===("undefined"==typeof navigator?"undefined":f(navigator))&&navigator||void 0,Ce=Ne&&(!_e||["ReactNative","NativeScript","NS"].indexOf(_e.product)<0),Fe="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope&&"function"==typeof self.importScripts,Ue=Ne&&window.location.href||"http://localhost",Be=s(s({},Object.freeze({__proto__:null,hasBrowserEnv:Ne,hasStandardBrowserWebWorkerEnv:Fe,hasStandardBrowserEnv:Ce,navigator:_e,origin:Ue})),Le);function De(e){function t(e,r,n,o){var i=e[o++];if("__proto__"===i)return!0;var a=Number.isFinite(+i),s=o>=e.length;return i=!i&&ve.isArray(n)?n.length:i,s?(ve.hasOwnProp(n,i)?n[i]=[n[i],r]:n[i]=r,!a):(n[i]&&ve.isObject(n[i])||(n[i]=[]),t(e,r,n[i],o)&&ve.isArray(n[i])&&(n[i]=function(e){var t,r,n={},o=Object.keys(e),i=o.length;for(t=0;t-1,i=ve.isObject(e);if(i&&ve.isHTMLForm(e)&&(e=new FormData(e)),ve.isFormData(e))return o?JSON.stringify(De(e)):e;if(ve.isArrayBuffer(e)||ve.isBuffer(e)||ve.isStream(e)||ve.isFile(e)||ve.isBlob(e)||ve.isReadableStream(e))return e;if(ve.isArrayBufferView(e))return e.buffer;if(ve.isURLSearchParams(e))return t.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();if(i){if(n.indexOf("application/x-www-form-urlencoded")>-1)return function(e,t){return Se(e,new Be.classes.URLSearchParams,Object.assign({visitor:function(e,t,r,n){return Be.isNode&&ve.isBuffer(e)?(this.append(t,e.toString("base64")),!1):n.defaultVisitor.apply(this,arguments)}},t))}(e,this.formSerializer).toString();if((r=ve.isFileList(e))||n.indexOf("multipart/form-data")>-1){var a=this.env&&this.env.FormData;return Se(r?{"files[]":e}:e,a&&new a,this.formSerializer)}}return i||o?(t.setContentType("application/json",!1),function(e,t,r){if(ve.isString(e))try{return(t||JSON.parse)(e),ve.trim(e)}catch(e){if("SyntaxError"!==e.name)throw e}return(r||JSON.stringify)(e)}(e)):e}],transformResponse:[function(e){var t=this.transitional||Ie.transitional,r=t&&t.forcedJSONParsing,n="json"===this.responseType;if(ve.isResponse(e)||ve.isReadableStream(e))return e;if(e&&ve.isString(e)&&(r&&!this.responseType||n)){var o=!(t&&t.silentJSONParsing)&&n;try{return JSON.parse(e)}catch(e){if(o){if("SyntaxError"===e.name)throw ye.from(e,ye.ERR_BAD_RESPONSE,this,null,this.response);throw e}}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:Be.classes.FormData,Blob:Be.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};ve.forEach(["delete","get","head","post","put","patch"],(function(e){Ie.headers[e]={}}));var qe=Ie,Me=ve.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),ze=Symbol("internals");function He(e){return e&&String(e).trim().toLowerCase()}function Je(e){return!1===e||null==e?e:ve.isArray(e)?e.map(Je):String(e)}function We(e,t,r,n,o){return ve.isFunction(n)?n.call(this,t,r):(o&&(t=r),ve.isString(t)?ve.isString(n)?-1!==t.indexOf(n):ve.isRegExp(n)?n.test(t):void 0:void 0)}var Ge=function(e,t){function r(e){p(this,r),e&&this.set(e)}return v(r,[{key:"set",value:function(e,t,r){var n=this;function o(e,t,r){var o=He(t);if(!o)throw new Error("header name must be a non-empty string");var i=ve.findKey(n,o);(!i||void 0===n[i]||!0===r||void 0===r&&!1!==n[i])&&(n[i||t]=Je(e))}var i=function(e,t){return ve.forEach(e,(function(e,r){return o(e,r,t)}))};if(ve.isPlainObject(e)||e instanceof this.constructor)i(e,t);else if(ve.isString(e)&&(e=e.trim())&&!/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim()))i(function(e){var t,r,n,o={};return e&&e.split("\n").forEach((function(e){n=e.indexOf(":"),t=e.substring(0,n).trim().toLowerCase(),r=e.substring(n+1).trim(),!t||o[t]&&Me[t]||("set-cookie"===t?o[t]?o[t].push(r):o[t]=[r]:o[t]=o[t]?o[t]+", "+r:r)})),o}(e),t);else if(ve.isHeaders(e)){var a,s=function(e,t){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=E(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0,o=function(){};return{s:o,n:function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,a=!0,s=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return a=e.done,e},e:function(e){s=!0,i=e},f:function(){try{a||null==r.return||r.return()}finally{if(s)throw i}}}}(e.entries());try{for(s.s();!(a=s.n()).done;){var u=m(a.value,2),c=u[0];o(u[1],c,r)}}catch(e){s.e(e)}finally{s.f()}}else null!=e&&o(t,e,r);return this}},{key:"get",value:function(e,t){if(e=He(e)){var r=ve.findKey(this,e);if(r){var n=this[r];if(!t)return n;if(!0===t)return function(e){for(var t,r=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;t=n.exec(e);)r[t[1]]=t[2];return r}(n);if(ve.isFunction(t))return t.call(this,n,r);if(ve.isRegExp(t))return t.exec(n);throw new TypeError("parser must be boolean|regexp|function")}}}},{key:"has",value:function(e,t){if(e=He(e)){var r=ve.findKey(this,e);return!(!r||void 0===this[r]||t&&!We(0,this[r],r,t))}return!1}},{key:"delete",value:function(e,t){var r=this,n=!1;function o(e){if(e=He(e)){var o=ve.findKey(r,e);!o||t&&!We(0,r[o],o,t)||(delete r[o],n=!0)}}return ve.isArray(e)?e.forEach(o):o(e),n}},{key:"clear",value:function(e){for(var t=Object.keys(this),r=t.length,n=!1;r--;){var o=t[r];e&&!We(0,this[o],o,e,!0)||(delete this[o],n=!0)}return n}},{key:"normalize",value:function(e){var t=this,r={};return ve.forEach(this,(function(n,o){var i=ve.findKey(r,o);if(i)return t[i]=Je(n),void delete t[o];var a=e?function(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(function(e,t,r){return t.toUpperCase()+r}))}(o):String(o).trim();a!==o&&delete t[o],t[a]=Je(n),r[a]=!0})),this}},{key:"concat",value:function(){for(var e,t=arguments.length,r=new Array(t),n=0;n1?r-1:0),o=1;o1&&void 0!==arguments[1]?arguments[1]:Date.now();o=i,r=null,n&&(clearTimeout(n),n=null),e.apply(null,t)};return[function(){for(var e=Date.now(),t=e-o,s=arguments.length,u=new Array(s),c=0;c=i?a(u,e):(r=u,n||(n=setTimeout((function(){n=null,a(r)}),i-t)))},function(){return r&&a(r)}]}ve.inherits($e,ye,{__CANCEL__:!0});var et=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:3,n=0,o=Qe(50,250);return Ze((function(r){var i=r.loaded,a=r.lengthComputable?r.total:void 0,s=i-n,u=o(s);n=i;var c=y({loaded:i,total:a,progress:a?i/a:void 0,bytes:s,rate:u||void 0,estimated:u&&a&&i<=a?(a-i)/u:void 0,event:r,lengthComputable:null!=a},t?"download":"upload",!0);e(c)}),r)},tt=function(e,t){var r=null!=e;return[function(n){return t[0]({lengthComputable:r,total:e,loaded:n})},t[1]]},rt=function(e){return function(){for(var t=arguments.length,r=new Array(t),n=0;n1?t-1:0),n=1;n1?"since :\n"+s.map(jt).join("\n"):" "+jt(s[0]):"as no adapter specified"),"ERR_NOT_SUPPORT")}return r};function Pt(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new $e(null,e)}function Lt(e){return Pt(e),e.headers=Ke.from(e.headers),e.data=Ve.call(e,e.transformRequest),-1!==["post","put","patch"].indexOf(e.method)&&e.headers.setContentType("application/x-www-form-urlencoded",!1),At(e.adapter||qe.adapter)(e).then((function(t){return Pt(e),t.data=Ve.call(e,e.transformResponse,t),t.headers=Ke.from(t.headers),t}),(function(t){return Xe(t)||(Pt(e),t&&t.response&&(t.response.data=Ve.call(e,e.transformResponse,t.response),t.response.headers=Ke.from(t.response.headers))),Promise.reject(t)}))}var Nt="1.7.5",_t={};["object","boolean","number","function","string","symbol"].forEach((function(e,t){_t[e]=function(r){return f(r)===e||"a"+(t<1?"n ":" ")+e}}));var Ct={};_t.transitional=function(e,t,r){function n(e,t){return"[Axios v1.7.5] Transitional option '"+e+"'"+t+(r?". "+r:"")}return function(r,o,i){if(!1===e)throw new ye(n(o," has been removed"+(t?" in "+t:"")),ye.ERR_DEPRECATED);return t&&!Ct[o]&&(Ct[o]=!0,console.warn(n(o," has been deprecated since v"+t+" and will be removed in the near future"))),!e||e(r,o,i)}};var Ft={assertOptions:function(e,t,r){if("object"!==f(e))throw new ye("options must be an object",ye.ERR_BAD_OPTION_VALUE);for(var n=Object.keys(e),o=n.length;o-- >0;){var i=n[o],a=t[i];if(a){var s=e[i],u=void 0===s||a(s,i,e);if(!0!==u)throw new ye("option "+i+" must be "+u,ye.ERR_BAD_OPTION_VALUE)}else if(!0!==r)throw new ye("Unknown option "+i,ye.ERR_BAD_OPTION)}},validators:_t},Ut=Ft.validators,Bt=function(){function e(t){p(this,e),this.defaults=t,this.interceptors={request:new Ae,response:new Ae}}var t;return v(e,[{key:"request",value:(t=h(u().mark((function e(t,r){var n,o;return u().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,this._request(t,r);case 3:return e.abrupt("return",e.sent);case 6:if(e.prev=6,e.t0=e.catch(0),e.t0 instanceof Error){Error.captureStackTrace?Error.captureStackTrace(n={}):n=new Error,o=n.stack?n.stack.replace(/^.+\n/,""):"";try{e.t0.stack?o&&!String(e.t0.stack).endsWith(o.replace(/^.+\n.+\n/,""))&&(e.t0.stack+="\n"+o):e.t0.stack=o}catch(e){}}throw e.t0;case 10:case"end":return e.stop()}}),e,this,[[0,6]])}))),function(e,r){return t.apply(this,arguments)})},{key:"_request",value:function(e,t){"string"==typeof e?(t=t||{}).url=e:t=e||{};var r=t=st(this.defaults,t),n=r.transitional,o=r.paramsSerializer,i=r.headers;void 0!==n&&Ft.assertOptions(n,{silentJSONParsing:Ut.transitional(Ut.boolean),forcedJSONParsing:Ut.transitional(Ut.boolean),clarifyTimeoutError:Ut.transitional(Ut.boolean)},!1),null!=o&&(ve.isFunction(o)?t.paramsSerializer={serialize:o}:Ft.assertOptions(o,{encode:Ut.function,serialize:Ut.function},!0)),t.method=(t.method||this.defaults.method||"get").toLowerCase();var a=i&&ve.merge(i.common,i[t.method]);i&&ve.forEach(["delete","get","head","post","put","patch","common"],(function(e){delete i[e]})),t.headers=Ke.concat(a,i);var s=[],u=!0;this.interceptors.request.forEach((function(e){"function"==typeof e.runWhen&&!1===e.runWhen(t)||(u=u&&e.synchronous,s.unshift(e.fulfilled,e.rejected))}));var c,f=[];this.interceptors.response.forEach((function(e){f.push(e.fulfilled,e.rejected)}));var l,h=0;if(!u){var p=[Lt.bind(this),void 0];for(p.unshift.apply(p,s),p.push.apply(p,f),l=p.length,c=Promise.resolve(t);h0;)n._listeners[t](e);n._listeners=null}})),this.promise.then=function(e){var t,r=new Promise((function(e){n.subscribe(e),t=e})).then(e);return r.cancel=function(){n.unsubscribe(t)},r},t((function(e,t,o){n.reason||(n.reason=new $e(e,t,o),r(n.reason))}))}return v(e,[{key:"throwIfRequested",value:function(){if(this.reason)throw this.reason}},{key:"subscribe",value:function(e){this.reason?e(this.reason):this._listeners?this._listeners.push(e):this._listeners=[e]}},{key:"unsubscribe",value:function(e){if(this._listeners){var t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1)}}}],[{key:"source",value:function(){var t;return{token:new e((function(e){t=e})),cancel:t}}}]),e}(),qt=It;var Mt={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Mt).forEach((function(e){var t=m(e,2),r=t[0],n=t[1];Mt[n]=r}));var zt=Mt;var Ht=function e(t){var r=new Dt(t),n=x(Dt.prototype.request,r);return ve.extend(n,Dt.prototype,r,{allOwnKeys:!0}),ve.extend(n,r,null,{allOwnKeys:!0}),n.create=function(r){return e(st(t,r))},n}(qe);return Ht.Axios=Dt,Ht.CanceledError=$e,Ht.CancelToken=qt,Ht.isCancel=Xe,Ht.VERSION=Nt,Ht.toFormData=Se,Ht.AxiosError=ye,Ht.Cancel=Ht.CanceledError,Ht.all=function(e){return Promise.all(e)},Ht.spread=function(e){return function(t){return e.apply(null,t)}},Ht.isAxiosError=function(e){return ve.isObject(e)&&!0===e.isAxiosError},Ht.mergeConfig=st,Ht.AxiosHeaders=Ke,Ht.formToJSON=function(e){return De(ve.isHTMLForm(e)?new FormData(e):e)},Ht.getAdapter=At,Ht.HttpStatusCode=zt,Ht.default=Ht,Ht}));
+//# sourceMappingURL=axios.min.js.map
\ No newline at end of file
diff --git a/batch-quartz/src/main/resources/static/js/user/signIn.js b/batch-quartz/src/main/resources/static/js/user/signIn.js
new file mode 100644
index 0000000..4cd5478
--- /dev/null
+++ b/batch-quartz/src/main/resources/static/js/user/signIn.js
@@ -0,0 +1,20 @@
+import axiosInstance from '../common/axiosInstance.js';
+
+const login = async (username, password) => {
+ try {
+ const response = await axiosInstance.post('/sign-in', {
+ username,
+ password
+ });
+ console.log('로그인 성공:', response.data);
+ } catch (error) {
+ console.error('로그인 실패:', error);
+ }
+};
+
+document.getElementById('signinForm').addEventListener('submit', function(event) {
+ event.preventDefault(); // 기본 폼 제출 방지
+ const username = document.getElementById('username').value;
+ const password = document.getElementById('password').value;
+ login(username, password);
+});
\ No newline at end of file
diff --git a/batch-quartz/src/main/resources/templates/views/user/signIn.html b/batch-quartz/src/main/resources/templates/views/user/signIn.html
new file mode 100644
index 0000000..36a4adb
--- /dev/null
+++ b/batch-quartz/src/main/resources/templates/views/user/signIn.html
@@ -0,0 +1,33 @@
+
+
+
+
+ 로그인 페이지
+
+
+
+
+
+
+
+

+
+
로그인
+
+
+
+
\ No newline at end of file