From 9da2585f291f6dceb4c7d396e00a7d6240a63092 Mon Sep 17 00:00:00 2001 From: sdw086 Date: Tue, 31 Mar 2026 17:46:33 +0900 Subject: [PATCH] =?UTF-8?q?[API]=20SSO=20=EB=8B=A4=EC=A4=91=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 7 +- README.md | 4 +- build.gradle | 1 - .../alist/api/config/RedisSessionConfig.java | 44 ------ .../com/alist/api/config/SecurityConfig.java | 2 +- .../config/cache/CorsAllowedOriginsCache.java | 10 +- .../api/modules/auth/AuthController.java | 108 ++++--------- ...ntroller.java => CorsAllowController.java} | 8 +- .../alist/api/modules/auth/SsoController.java | 16 +- .../api/modules/auth/TestAuthController.java | 92 ----------- .../dto/{TestLoginDto.java => LoginDto.java} | 2 +- .../api/modules/auth/dto/SsoLoginDto.java | 11 +- ...keyLoginForm.java => ApikeyLoginForm.java} | 14 +- .../modules/auth/form/SsoExchangeForm.java | 2 +- .../api/modules/auth/form/SsoLoginForm.java | 2 +- .../api/modules/auth/form/TestLoginForm.java | 40 ----- .../api/modules/auth/form/TokenForm.java | 2 +- .../api/modules/auth/mapper/AuthMapper.java | 24 +++ .../{TestCorsMapper.java => CorsMapper.java} | 6 +- .../api/modules/auth/mapper/SsoMapper.java | 2 +- .../modules/auth/mapper/TestAuthMapper.java | 24 --- .../api/modules/auth/service/AuthService.java | 144 ++++++++++++++++++ .../api/modules/auth/service/SsoService.java | 32 ++-- .../modules/auth/service/TestAuthService.java | 144 ------------------ ...estCorsOriginVo.java => CorsOriginVo.java} | 2 +- ...estLoginTokenVo.java => LoginTokenVo.java} | 2 +- .../vo/{TestLoginVo.java => LoginVo.java} | 2 +- .../api/modules/file/FileController.java | 20 +-- .../api/modules/file/form/FileDeleteForm.java | 2 +- .../api/modules/file/form/FileUploadForm.java | 2 +- .../api/modules/file/form/TusHookForm.java | 2 +- .../modules/file/form/UploadStatusForm.java | 2 +- .../modules/testUser/TestUserController.java | 46 ------ .../testUser/mapper/TestUserMapper.java | 14 -- .../testUser/service/TestUserService.java | 65 -------- .../api/modules/testUser/vo/TestUserVo.java | 8 - .../api/modules/user/UserController.java | 46 ++++++ .../dto/UserDto.java} | 4 +- .../dto/UserTokenDto.java} | 4 +- .../form/UserSignupForm.java} | 16 +- .../api/modules/user/mapper/UserMapper.java | 14 ++ .../api/modules/user/service/UserService.java | 62 ++++++++ src/main/resources/application-local.yaml | 13 +- src/main/resources/application-pjt.yaml | 9 +- src/main/resources/mapper/User/UserMapper.xml | 23 +++ src/main/resources/mapper/auth/CorsMapper.xml | 12 ++ .../{TestLoginMapper.xml => LoginMapper.xml} | 40 ++--- src/main/resources/mapper/auth/SsoMapper.xml | 2 +- .../resources/mapper/auth/TestCorsMapper.xml | 12 -- .../mapper/testUser/TestUserMapper.xml | 23 --- 50 files changed, 466 insertions(+), 722 deletions(-) delete mode 100644 src/main/java/com/alist/api/config/RedisSessionConfig.java rename src/main/java/com/alist/api/modules/auth/{TestCorsAllowController.java => CorsAllowController.java} (86%) delete mode 100644 src/main/java/com/alist/api/modules/auth/TestAuthController.java rename src/main/java/com/alist/api/modules/auth/dto/{TestLoginDto.java => LoginDto.java} (96%) rename src/main/java/com/alist/api/modules/auth/form/{TestApikeyLoginForm.java => ApikeyLoginForm.java} (51%) delete mode 100644 src/main/java/com/alist/api/modules/auth/form/TestLoginForm.java create mode 100644 src/main/java/com/alist/api/modules/auth/mapper/AuthMapper.java rename src/main/java/com/alist/api/modules/auth/mapper/{TestCorsMapper.java => CorsMapper.java} (63%) delete mode 100644 src/main/java/com/alist/api/modules/auth/mapper/TestAuthMapper.java create mode 100644 src/main/java/com/alist/api/modules/auth/service/AuthService.java delete mode 100644 src/main/java/com/alist/api/modules/auth/service/TestAuthService.java rename src/main/java/com/alist/api/modules/auth/vo/{TestCorsOriginVo.java => CorsOriginVo.java} (81%) rename src/main/java/com/alist/api/modules/auth/vo/{TestLoginTokenVo.java => LoginTokenVo.java} (85%) rename src/main/java/com/alist/api/modules/auth/vo/{TestLoginVo.java => LoginVo.java} (84%) delete mode 100644 src/main/java/com/alist/api/modules/testUser/TestUserController.java delete mode 100644 src/main/java/com/alist/api/modules/testUser/mapper/TestUserMapper.java delete mode 100644 src/main/java/com/alist/api/modules/testUser/service/TestUserService.java delete mode 100644 src/main/java/com/alist/api/modules/testUser/vo/TestUserVo.java create mode 100644 src/main/java/com/alist/api/modules/user/UserController.java rename src/main/java/com/alist/api/modules/{testUser/dto/TestUserDto.java => user/dto/UserDto.java} (79%) rename src/main/java/com/alist/api/modules/{testUser/dto/TestUserTokenDto.java => user/dto/UserTokenDto.java} (72%) rename src/main/java/com/alist/api/modules/{testUser/form/TestUserSignupForm.java => user/form/UserSignupForm.java} (74%) create mode 100644 src/main/java/com/alist/api/modules/user/mapper/UserMapper.java create mode 100644 src/main/java/com/alist/api/modules/user/service/UserService.java create mode 100644 src/main/resources/mapper/User/UserMapper.xml create mode 100644 src/main/resources/mapper/auth/CorsMapper.xml rename src/main/resources/mapper/auth/{TestLoginMapper.xml => LoginMapper.xml} (50%) delete mode 100644 src/main/resources/mapper/auth/TestCorsMapper.xml delete mode 100644 src/main/resources/mapper/testUser/TestUserMapper.xml diff --git a/AGENTS.md b/AGENTS.md index d0bb047..af719e0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,6 @@ - `src/main/java/com/alist/api`: 애플리케이션 시작점과 업무 코드를 둔다. - `src/main/java/com/alist/api/modules`: 기능별 모듈 패키지를 둔다. - `src/main/resources`: 설정 파일과 로깅 설정을 관리한다. -- `src/test/java`: 테스트 코드를 둔다. - `deploy`: 배포 관련 리소스가 있으면 이 경로를 우선 확인한다. ## 현재 확인된 구조 @@ -35,7 +34,7 @@ - MyBatis는 인터페이스와 XML을 함께 사용한다. - Mapper 인터페이스는 `src/main/java/.../mapper`, SQL XML은 `src/main/resources/mapper/...` 경로를 짝으로 맞춘다. - 공통 응답은 `common/response`, 보안은 `config/security, jwt`, 전역 예외 처리는 `config/exception` 아래에 둔다. -- 모듈 패키지는 현재 `auth`, `file`, `main`, `testUser` 형태로 구성되어 있고, 필요한 모듈만 `dto`, `form`, `mapper`, `service`, `vo`를 둔다. +- 모듈 패키지는 현재 `auth`, `file`, `main`, `user` 형태로 구성되어 있고, 필요한 모듈만 `dto`, `form`, `mapper`, `service`, `vo`를 둔다. ## 파일 생성 규칙 - 새 기능은 가능하면 `modules/{도메인명}Controller` 단위로 패키지를 만들고 그 아래에 `service`, `mapper`, `dto`, `vo`를 필요한 만큼만 추가한다. @@ -50,7 +49,7 @@ - `Vo`는 조회 결과나 스케줄 실행 판단에 필요한 읽기 전용 성격의 값으로 본다. - `Form`은 Controller 입력 검증과 요청 바인딩 전용으로 두고, `@Valid` 와 Jakarta Validation 어노테이션을 우선 사용한다. - 현재 코드처럼 DTO/VO는 Lombok `@Getter`, 필요한 경우에만 `@Setter`를 사용한다. -- Form 안에는 DTO 변환 메서드를 둘 수 있다. 예: `testUserDto()`, `fileUploadDto()` +- Form 안에는 DTO 변환 메서드를 둘 수 있다. 예: `userDto()`, `fileUploadDto()` - DTO 안에 연관된 다른 DTO 변환이 꼭 필요할 때만 최소한의 보조 메서드를 둔다. - 외부 응답에 노출되면 안 되는 내부 필드는 DTO 에 `@JsonIgnore`로 숨긴다. - Mapper 메서드명은 SQL 동작이 드러나도록 `select`, `insert`, `update` 접두어를 사용한다. @@ -144,7 +143,7 @@ - Swagger: `/v3/api-docs/**`, `/swagger-ui/**` → HTTP Basic 인증 (InMemory) - API: JWT Bearer 토큰 인증 (Stateless) - 세션/쿠키: Redis Session 저장소 사용, 쿠키 속성은 프로파일별 `cookie.*` 설정으로 제어 -- 공개 경로: `/`, `/actuator/health`, `/auth/**`, `/test/**`, `/files/tusHook` +- 공개 경로: `/`, `/actuator/health`, `/sso/**`, `/auth/**`, `/user/signup`, `/files/tusHook` - Swagger 인증과 API 인증은 `SecurityFilterChain` 을 분리해서 관리한다. ## 응답 코드 규칙 diff --git a/README.md b/README.md index dd1bdd3..8d13390 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,6 @@ src/main/java/com/alist/api └── modules ├── auth # 인증/세션 관련 API ├── main # 루트 리다이렉트 - └── testUser # 테스트 사용자 API ``` 리소스 파일은 아래 위치를 사용합니다. @@ -141,7 +140,6 @@ java -jar build/libs/api.jar --spring.profiles.active=local - `/` - `/actuator/health` - `/auth/**` -- `/test/**` - `/files/tusHook` 루트 `/` 요청은 `/swagger-ui/index.html`로 리다이렉트됩니다. @@ -186,7 +184,7 @@ java -jar build/libs/api.jar --spring.profiles.active=local - `POST /auth/refresh` - `GET /auth/loginChecked` - `POST /auth/logout` -- `POST /test/testSignup` +- `POST /user/signup` ## API 예시 요청/응답 diff --git a/build.gradle b/build.gradle index dba8f3a..a54cd0d 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,6 @@ dependencies { // Redis (SSO 세션 공유) implementation 'org.springframework.boot:spring-boot-starter-data-redis' - implementation 'org.springframework.session:spring-session-data-redis' } tasks.named('test') { diff --git a/src/main/java/com/alist/api/config/RedisSessionConfig.java b/src/main/java/com/alist/api/config/RedisSessionConfig.java deleted file mode 100644 index b922cd1..0000000 --- a/src/main/java/com/alist/api/config/RedisSessionConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.alist.api.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; -import org.springframework.session.web.http.CookieSerializer; -import org.springframework.session.web.http.DefaultCookieSerializer; - -@Configuration -@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 7200) // 2시간 -public class RedisSessionConfig { - - @Value("${cookie.secure}") - private boolean cookieSecure; - - @Value("${cookie.domain:}") - private String cookieDomain; - - @Value("${cookie.name}") - private String cookieName; - - @Value("${cookie.same-site:Lax}") - private String cookieSameSite; - - @Bean - public CookieSerializer cookieSerializer() { - DefaultCookieSerializer serializer = new DefaultCookieSerializer(); - - // SSO를 위한 쿠키 설정 - serializer.setCookieName(cookieName); - serializer.setDomainName(cookieDomain); // 모든 서브도메인에서 공유 - serializer.setCookiePath("/"); - serializer.setUseSecureCookie(cookieSecure); // 환경별 설정 (local: false, pjt: true) - serializer.setUseHttpOnlyCookie(true); // XSS 방지 - serializer.setSameSite(cookieSameSite); // CSRF 방지 - - if (cookieDomain != null && !cookieDomain.isBlank()) { - serializer.setDomainName(cookieDomain.trim()); - } - - return serializer; - } -} diff --git a/src/main/java/com/alist/api/config/SecurityConfig.java b/src/main/java/com/alist/api/config/SecurityConfig.java index 6d08425..d30933b 100644 --- a/src/main/java/com/alist/api/config/SecurityConfig.java +++ b/src/main/java/com/alist/api/config/SecurityConfig.java @@ -59,7 +59,7 @@ public class SecurityConfig { .accessDeniedHandler(new JwtAccessDeniedHandler()) ) .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/actuator/health", "/sso/**", "/auth/**", "/test/**", "/files/tusHook").permitAll() + .requestMatchers("/", "/actuator/health", "/sso/**", "/auth/**", "/user/signup", "/files/tusHook").permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); diff --git a/src/main/java/com/alist/api/config/cache/CorsAllowedOriginsCache.java b/src/main/java/com/alist/api/config/cache/CorsAllowedOriginsCache.java index 28a8a8b..75b3fc7 100644 --- a/src/main/java/com/alist/api/config/cache/CorsAllowedOriginsCache.java +++ b/src/main/java/com/alist/api/config/cache/CorsAllowedOriginsCache.java @@ -1,7 +1,7 @@ package com.alist.api.config.cache; -import com.alist.api.modules.auth.vo.TestCorsOriginVo; -import com.alist.api.modules.auth.mapper.TestCorsMapper; +import com.alist.api.modules.auth.vo.CorsOriginVo; +import com.alist.api.modules.auth.mapper.CorsMapper; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -16,7 +16,7 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class CorsAllowedOriginsCache { - private final TestCorsMapper testCorsMapper; + private final CorsMapper corsMapper; // Thread-safe List (volatile로 가시성 보장) private volatile List allowedOrigins = new ArrayList<>(); @@ -35,9 +35,9 @@ public class CorsAllowedOriginsCache { */ public synchronized void refresh() { try { - List origins = testCorsMapper.selectTestCorsAllowedList() + List origins = corsMapper.selectCorsAllowedList() .stream() - .map(TestCorsOriginVo::getAllowedOrigin) + .map(CorsOriginVo::getAllowedOrigin) .distinct() .collect(Collectors.toList()); diff --git a/src/main/java/com/alist/api/modules/auth/AuthController.java b/src/main/java/com/alist/api/modules/auth/AuthController.java index fe3787d..3b24aa7 100644 --- a/src/main/java/com/alist/api/modules/auth/AuthController.java +++ b/src/main/java/com/alist/api/modules/auth/AuthController.java @@ -1,31 +1,29 @@ package com.alist.api.modules.auth; +import com.alist.api.common.response.ApiResponse; +import com.alist.api.common.response.ApiResponseCode; import com.alist.api.common.utils.SessionUtil; +import com.alist.api.config.jwt.JwtTokenProvider; import com.alist.api.modules.auth.dto.SsoExchangeDto; -import com.alist.api.modules.auth.dto.TestLoginDto; +import com.alist.api.modules.auth.dto.LoginDto; import com.alist.api.modules.auth.dto.TokenDto; +import com.alist.api.modules.auth.form.ApikeyLoginForm; +import com.alist.api.modules.auth.form.TokenForm; import com.alist.api.modules.auth.service.SsoService; -import com.alist.api.modules.auth.service.TestAuthService; +import com.alist.api.modules.auth.service.AuthService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import com.alist.api.common.response.ApiResponse; -import com.alist.api.common.response.ApiResponseCode; -import com.alist.api.config.jwt.JwtTokenProvider; -import com.alist.api.modules.auth.form.TokenForm; - -import jakarta.validation.Valid; - import java.util.HashMap; import java.util.Map; -@Tag(name = "00. 엑세스 토큰 발급", description = "강제 엑세스 토큰 발급 (JSON 전달, 테스트용)") +@Tag(name = "02. 엑세스 토큰 발급", description = "엑세스 토큰 발급") @RestController @RequestMapping("/auth") public class AuthController { @@ -36,9 +34,6 @@ public class AuthController { @Value("${cookie.domain}") private String cookieDomain; - @Value("${cookie.name}") - private String cookieName; - @Value("${cookie.same-site:Lax}") private String cookieSameSite; @@ -54,22 +49,22 @@ public class AuthController { private final SsoService ssoService; private final JwtTokenProvider jwtTokenProvider; - private final TestAuthService testAuthService; + private final AuthService authService; - public AuthController(SsoService ssoService, JwtTokenProvider jwtTokenProvider, TestAuthService testAuthService) { + public AuthController(SsoService ssoService, JwtTokenProvider jwtTokenProvider, AuthService authService) { this.ssoService = ssoService; this.jwtTokenProvider = jwtTokenProvider; - this.testAuthService = testAuthService; + this.authService = authService; } @Operation( - summary = "엑세스 토큰 발급", + summary = "엑세스 토큰 발급 (테스트용 실사용X)", description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)" ) @PostMapping("/token") public ResponseEntity> token(@Valid @RequestBody TokenForm tokenForm) { TokenDto tokenDto = new TokenDto(); - tokenDto.setAccessToken(jwtTokenProvider.createToken(tokenForm.tokenDto().getId())); + tokenDto.setAccessToken(jwtTokenProvider.createToken(tokenForm.toTokenDto().getId())); return ApiResponse.entity(tokenDto, ApiResponseCode.CODE_2001, "엑세스 토큰"); } @@ -93,11 +88,11 @@ public class AuthController { String accessToken = jwtTokenProvider.createAccessToken(ssoSession.getUserTokenIdx(), ssoSession.getUserRole()); String refreshToken = jwtTokenProvider.createRefreshToken(ssoSession.getUserTokenIdx()); - TestLoginDto testLoginDto = new TestLoginDto(); - testLoginDto.setUserTokenIdx(ssoSession.getUserTokenIdx()); - testLoginDto.setRefreshToken(refreshToken); + LoginDto loginDto = new LoginDto(); + loginDto.setUserTokenIdx(ssoSession.getUserTokenIdx()); + loginDto.setRefreshToken(refreshToken); - testAuthService.updateRefreshToken(testLoginDto); + authService.updateRefreshToken(loginDto); SessionUtil.addTokenCookie(response, "accessToken", accessToken, cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds); SessionUtil.addTokenCookie(response,"refreshToken", refreshToken, cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds); @@ -121,73 +116,30 @@ public class AuthController { , HttpServletResponse response ) { - TestLoginDto testLogin = testAuthService.refreshByToken(refreshToken); + LoginDto result = authService.refreshByToken(refreshToken); - if (testLogin == null) { + if (result == null) { return ApiResponse.entity(Map.of("refreshed", false), ApiResponseCode.CODE_401); } - SessionUtil.addTokenCookie(response, "accessToken", testLogin.getAccessToken(), cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds); - SessionUtil.addTokenCookie(response, "refreshToken", testLogin.getRefreshToken(), cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds); + SessionUtil.addTokenCookie(response, "accessToken", result.getAccessToken(), cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds); + SessionUtil.addTokenCookie(response, "refreshToken", result.getRefreshToken(), cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds); return ApiResponse.entity(Map.of("refreshed", true), ApiResponseCode.CODE_200); } @Operation( - summary = "SSO 로그인 상태 확인", - description = "SSO 인증 받은 후 현재 세션이 있는지 확인" + summary = "API Key 로그인", + description = "API Key로 인증하고 JSON으로 토큰을 반환합니다. (OpenAPI/외부 서비스용)" ) - @GetMapping("/loginChecked") - public ResponseEntity>> loginChecked(HttpServletRequest request) { - Map sessionInfo = new HashMap<>(); - HttpSession session = request.getSession(false); // 새 세션 생성 금지 + @PostMapping("/apiKeyLogin") + public ResponseEntity> apiKeyLogin(@Valid @RequestBody ApikeyLoginForm apiKeyLoginForm) { + LoginDto result = authService.selectUserApiKeyLogin(apiKeyLoginForm.toLoginDto()); - if (session == null) { - sessionInfo.put("loggedIn", false); - return ApiResponse.entity(sessionInfo, ApiResponseCode.CODE_200); + if (result.getResultCode() == 2003) { + return ApiResponse.entity(result, ApiResponseCode.CODE_2003); } - Integer userIdx = (Integer) session.getAttribute("userIdx"); - if (userIdx == null) { - sessionInfo.put("loggedIn", false); - return ApiResponse.entity(sessionInfo, ApiResponseCode.CODE_200); - } - - sessionInfo.put("loggedIn", true); - sessionInfo.put("userIdx", userIdx); - sessionInfo.put("userTokenIdx", session.getAttribute("userTokenIdx")); - sessionInfo.put("userRole", session.getAttribute("userRole")); - sessionInfo.put("userId", session.getAttribute("userId")); - sessionInfo.put("sessionId", session.getId()); - - return ApiResponse.entity(sessionInfo, ApiResponseCode.CODE_200); - } - - @Operation( - summary = "로그아웃", - description = "로그아웃시 redis 에 세션 삭제 합니다." - ) - @PostMapping("/logout") - public ResponseEntity>> logout( - HttpServletRequest request - , HttpServletResponse response - , @CookieValue(name = "refreshToken", required = false) String refreshToken - ) { - - HttpSession session = request.getSession(false); - if (session != null) { - session.invalidate(); - } - - testAuthService.clearRefreshToken(refreshToken); - - SessionUtil.expireCookie(response, cookieName, cookieDomain, cookieSecure, cookieSameSite); - SessionUtil.expireCookie(response, "accessToken", cookieDomain, cookieSecure, cookieSameSite); - SessionUtil.expireCookie(response, "refreshToken", cookieDomain, cookieSecure, cookieSameSite); - - Map result = new HashMap<>(); - result.put("message", "로그아웃되었습니다."); - - return ApiResponse.entity(result, ApiResponseCode.CODE_200); + return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "엑세스 토큰"); } } diff --git a/src/main/java/com/alist/api/modules/auth/TestCorsAllowController.java b/src/main/java/com/alist/api/modules/auth/CorsAllowController.java similarity index 86% rename from src/main/java/com/alist/api/modules/auth/TestCorsAllowController.java rename to src/main/java/com/alist/api/modules/auth/CorsAllowController.java index 74c6f94..e800226 100644 --- a/src/main/java/com/alist/api/modules/auth/TestCorsAllowController.java +++ b/src/main/java/com/alist/api/modules/auth/CorsAllowController.java @@ -13,11 +13,11 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; -@Tag(name = "03. 테스트 CRS 갱신", description = "테스트 CORS 설정 관리 API") +@Tag(name = "04. CRS 갱신", description = "테스트 CORS 설정 관리 API") @RestController -@RequestMapping("/test") +@RequestMapping("/cors") @RequiredArgsConstructor -public class TestCorsAllowController { +public class CorsAllowController { private final CorsAllowedOriginsCache corsCache; @@ -25,7 +25,7 @@ public class TestCorsAllowController { summary = "CORS 화이트리스트 갱신", description = "DB에서 CORS 허용 도메인 목록을 다시 로딩합니다. DB에 도메인 추가 후 호출하세요." ) - @PostMapping("/reloadCors") + @PostMapping("/reload") public ResponseEntity>> reloadCors() { corsCache.refresh(); List cacheStatus = corsCache.getCacheStatus(); diff --git a/src/main/java/com/alist/api/modules/auth/SsoController.java b/src/main/java/com/alist/api/modules/auth/SsoController.java index 427c79b..d4c4cf9 100644 --- a/src/main/java/com/alist/api/modules/auth/SsoController.java +++ b/src/main/java/com/alist/api/modules/auth/SsoController.java @@ -9,7 +9,7 @@ import com.alist.api.modules.auth.dto.SsoLoginDto; import com.alist.api.modules.auth.form.SsoExchangeForm; import com.alist.api.modules.auth.form.SsoLoginForm; import com.alist.api.modules.auth.service.SsoService; -import com.alist.api.modules.auth.service.TestAuthService; +import com.alist.api.modules.auth.service.AuthService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -23,7 +23,7 @@ import java.net.URI; import java.util.HashMap; import java.util.Map; -@Tag(name = "02. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 관련 API") +@Tag(name = "01. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 관련 API") @RestController @RequestMapping("/sso") public class SsoController { @@ -42,11 +42,11 @@ public class SsoController { @Value("${sso.cookie.name}") private String ssoCookieName; private final SsoService ssoService; - private final TestAuthService testAuthService; + private final AuthService authService; - public SsoController(SsoService ssoService, TestAuthService testAuthService) { + public SsoController(SsoService ssoService, AuthService authService) { this.ssoService = ssoService; - this.testAuthService = testAuthService; + this.authService = authService; } @Operation( @@ -59,7 +59,7 @@ public class SsoController { , HttpServletRequest request , HttpServletResponse response ) { - SsoLoginDto ssoLoginDto = ssoService.login(ssoLoginForm.ssoLoginDto(), request); + SsoLoginDto ssoLoginDto = ssoService.login(ssoLoginForm.toSsoLoginDto(), request); SsoLoginCheckDto result = new SsoLoginCheckDto(); @@ -136,7 +136,7 @@ public class SsoController { @Valid @RequestBody SsoExchangeForm ssoExchangeForm , HttpServletResponse response ) { - SsoExchangeDto result = ssoService.exchange(ssoExchangeForm.ssoExchangeDto()); + SsoExchangeDto result = ssoService.exchange(ssoExchangeForm.toSsoExchangeDto()); SessionUtil.addTokenCookie(response, ssoCookieName, result.getSsoSessionId(), cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds); @@ -156,7 +156,7 @@ public class SsoController { String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName); ssoService.logout(ssoSessionId); - testAuthService.clearRefreshToken(refreshToken); + authService.clearRefreshToken(refreshToken); SessionUtil.expireCookie(response, ssoCookieName, cookieDomain, cookieSecure, cookieSameSite); SessionUtil.expireCookie(response, "accessToken", cookieDomain, cookieSecure, cookieSameSite); diff --git a/src/main/java/com/alist/api/modules/auth/TestAuthController.java b/src/main/java/com/alist/api/modules/auth/TestAuthController.java deleted file mode 100644 index a6ce920..0000000 --- a/src/main/java/com/alist/api/modules/auth/TestAuthController.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.alist.api.modules.auth; - -import com.alist.api.common.response.ApiResponse; -import com.alist.api.common.response.ApiResponseCode; -import com.alist.api.common.utils.SessionUtil; -import com.alist.api.modules.auth.dto.TestLoginDto; -import com.alist.api.modules.auth.form.TestApikeyLoginForm; -import com.alist.api.modules.auth.form.TestLoginForm; -import com.alist.api.modules.auth.service.TestAuthService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; -import jakarta.validation.Valid; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@Tag(name = "01. 테스트 인증", description = "테스트 사용자 로그인 및 API Key 인증 관련 API") -@RestController -@RequestMapping("/test") -public class TestAuthController { - private final TestAuthService testAuthService; - @Value("${cookie.secure}") - private boolean cookieSecure; - - @Value("${cookie.domain}") - private String cookieDomain; - - @Value("${cookie.same-site:Lax}") - private String cookieSameSite; - - @Value("${jwt.access-token-validity-seconds}") - private long accessTokenValiditySeconds; - - @Value("${jwt.refresh-token-validity-seconds}") - private long refreshTokenValiditySeconds; - - public TestAuthController(TestAuthService testAuthService) { - this.testAuthService = testAuthService; - } - - @Operation( - summary = "테스트 사용자 로그인", - description = "아이디/비밀번호로 로그인하고 HttpOnly 쿠키로 토큰을 전달합니다. (웹 브라우저용)" - ) - @PostMapping("/testLogin") - public ResponseEntity> testUserLogin( - @Valid @RequestBody TestLoginForm testLoginForm - , HttpServletResponse response - , HttpSession session - ) { - TestLoginDto testLoginDto = testAuthService.selectTestLogin(testLoginForm.testLoginDto()); - - if (testLoginDto.getResultCode() == 2003) { - return ApiResponse.entity(testLoginDto, ApiResponseCode.CODE_2003); - } - - // SSO를 위한 세션에 사용자 정보 저장 - session.setAttribute("userIdx", testLoginDto.getUserIdx()); - session.setAttribute("userTokenIdx", testLoginDto.getUserTokenIdx()); - session.setAttribute("userId", testLoginDto.getId()); - session.setAttribute("userRole", "USER"); - - // accessToken 쿠키 설정 - SessionUtil.addTokenCookie(response, "accessToken", testLoginDto.getAccessToken(), cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds); - - // refreshToken 쿠키 설정 - SessionUtil.addTokenCookie(response, "refreshToken", testLoginDto.getRefreshToken(), cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds); - - // JSON 응답에서는 토큰 제거 (쿠키로만 전달) - testLoginDto.setAccessToken(null); - testLoginDto.setRefreshToken(null); - - return ApiResponse.entity(testLoginDto, ApiResponseCode.CODE_2001, "로그인"); - } - - @Operation( - summary = "테스트 API Key 로그인", - description = "API Key로 인증하고 JSON으로 토큰을 반환합니다. (OpenAPI/외부 서비스용)" - ) - @PostMapping("/testApiKeyLogin") - public ResponseEntity> testUserApkKeyLogin(@Valid @RequestBody TestApikeyLoginForm testApiKeyLoginForm) { - TestLoginDto testLoginDto = testAuthService.selectTestUserApiKeyLogin(testApiKeyLoginForm.testLoginDto()); - - if (testLoginDto.getResultCode() == 2003) { - return ApiResponse.entity(testLoginDto, ApiResponseCode.CODE_2003); - } - - return ApiResponse.entity(testLoginDto, ApiResponseCode.CODE_2001, "엑세스 토큰"); - } -} diff --git a/src/main/java/com/alist/api/modules/auth/dto/TestLoginDto.java b/src/main/java/com/alist/api/modules/auth/dto/LoginDto.java similarity index 96% rename from src/main/java/com/alist/api/modules/auth/dto/TestLoginDto.java rename to src/main/java/com/alist/api/modules/auth/dto/LoginDto.java index a7f5347..df597cb 100644 --- a/src/main/java/com/alist/api/modules/auth/dto/TestLoginDto.java +++ b/src/main/java/com/alist/api/modules/auth/dto/LoginDto.java @@ -9,7 +9,7 @@ import java.time.Instant; @Getter @Setter -public class TestLoginDto { +public class LoginDto { @Schema(description = "아이디") private String id; @Schema(description = "엑세스 토큰") diff --git a/src/main/java/com/alist/api/modules/auth/dto/SsoLoginDto.java b/src/main/java/com/alist/api/modules/auth/dto/SsoLoginDto.java index 5c1506f..65776e5 100644 --- a/src/main/java/com/alist/api/modules/auth/dto/SsoLoginDto.java +++ b/src/main/java/com/alist/api/modules/auth/dto/SsoLoginDto.java @@ -1,6 +1,5 @@ package com.alist.api.modules.auth.dto; -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; @@ -17,10 +16,10 @@ public class SsoLoginDto { private String userId; private String userRole; - public TestLoginDto testLoginDto() { - TestLoginDto testLoginDto = new TestLoginDto(); - testLoginDto.setId(this.id); - testLoginDto.setPassword(this.password); - return testLoginDto; + public LoginDto toLoginDto() { + LoginDto loginDto = new LoginDto(); + loginDto.setId(this.id); + loginDto.setPassword(this.password); + return loginDto; } } diff --git a/src/main/java/com/alist/api/modules/auth/form/TestApikeyLoginForm.java b/src/main/java/com/alist/api/modules/auth/form/ApikeyLoginForm.java similarity index 51% rename from src/main/java/com/alist/api/modules/auth/form/TestApikeyLoginForm.java rename to src/main/java/com/alist/api/modules/auth/form/ApikeyLoginForm.java index 751f4db..9115423 100644 --- a/src/main/java/com/alist/api/modules/auth/form/TestApikeyLoginForm.java +++ b/src/main/java/com/alist/api/modules/auth/form/ApikeyLoginForm.java @@ -1,16 +1,14 @@ package com.alist.api.modules.auth.form; -import com.alist.api.modules.auth.dto.TestLoginDto; +import com.alist.api.modules.auth.dto.LoginDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.Setter; @Getter @Setter -public class TestApikeyLoginForm { +public class ApikeyLoginForm { @Schema( description = "api key", example = "aaaa111dsddd" @@ -18,10 +16,10 @@ public class TestApikeyLoginForm { @NotBlank(message = "apiKey를 입력해주세요.") private String userApiKey; - public TestLoginDto testLoginDto() { - TestLoginDto testLoginDto = new TestLoginDto(); - testLoginDto.setUserApiKey(this.userApiKey.trim()); + public LoginDto toLoginDto() { + LoginDto loginDto = new LoginDto(); + loginDto.setUserApiKey(this.userApiKey.trim()); - return testLoginDto; + return loginDto; } } diff --git a/src/main/java/com/alist/api/modules/auth/form/SsoExchangeForm.java b/src/main/java/com/alist/api/modules/auth/form/SsoExchangeForm.java index 5431bd6..2d77108 100644 --- a/src/main/java/com/alist/api/modules/auth/form/SsoExchangeForm.java +++ b/src/main/java/com/alist/api/modules/auth/form/SsoExchangeForm.java @@ -12,7 +12,7 @@ public class SsoExchangeForm { @NotBlank private String clientId; - public SsoExchangeDto ssoExchangeDto() { + public SsoExchangeDto toSsoExchangeDto() { SsoExchangeDto ssoExchangeDto = new SsoExchangeDto(); ssoExchangeDto.setCode(code == null ? null : code.trim()); ssoExchangeDto.setClientId(clientId == null ? null : clientId.trim()); diff --git a/src/main/java/com/alist/api/modules/auth/form/SsoLoginForm.java b/src/main/java/com/alist/api/modules/auth/form/SsoLoginForm.java index 20e8284..186ac9a 100644 --- a/src/main/java/com/alist/api/modules/auth/form/SsoLoginForm.java +++ b/src/main/java/com/alist/api/modules/auth/form/SsoLoginForm.java @@ -12,7 +12,7 @@ public class SsoLoginForm { @NotBlank private String password; - public SsoLoginDto ssoLoginDto() { + public SsoLoginDto toSsoLoginDto() { SsoLoginDto ssoLoginDto = new SsoLoginDto(); ssoLoginDto.setId(id == null ? null : id.trim()); ssoLoginDto.setPassword(password == null ? null : password.trim()); diff --git a/src/main/java/com/alist/api/modules/auth/form/TestLoginForm.java b/src/main/java/com/alist/api/modules/auth/form/TestLoginForm.java deleted file mode 100644 index d10b530..0000000 --- a/src/main/java/com/alist/api/modules/auth/form/TestLoginForm.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.alist.api.modules.auth.form; - -import com.alist.api.modules.auth.dto.TestLoginDto; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class TestLoginForm { - @Schema( - description = "사용자 아이디 (공백 불가)", - example = "test" - ) - @NotBlank(message = "아이디를 입력해주세요.") - private String id; - - @Schema( - description = "비밀번호 (8~64자, 영문 + 숫자 조합, 공백 불가)", - example = "pass1234" - ) - @NotBlank(message = "비밀번호를 입력해주세요.") - @Size(min = 8, max = 64, message = "비밀번호는 8~64자여야 합니다.") - @Pattern( - regexp = "^(?=.*[A-Za-z])(?=.*\\d)\\S+$", - message = "비밀번호는 영문과 숫자를 포함하고 공백이 없어야 합니다." - ) - private String password; - - public TestLoginDto testLoginDto() { - TestLoginDto testLoginDto = new TestLoginDto(); - testLoginDto.setId(this.id.trim()); - testLoginDto.setPassword(this.password); - - return testLoginDto; - } -} diff --git a/src/main/java/com/alist/api/modules/auth/form/TokenForm.java b/src/main/java/com/alist/api/modules/auth/form/TokenForm.java index cc39d6a..aed3c0b 100644 --- a/src/main/java/com/alist/api/modules/auth/form/TokenForm.java +++ b/src/main/java/com/alist/api/modules/auth/form/TokenForm.java @@ -15,7 +15,7 @@ public class TokenForm { @Schema(description = "사용자명", example = "test") private String id; - public TokenDto tokenDto() { + public TokenDto toTokenDto() { TokenDto tokenDto = new TokenDto(); tokenDto.setId(this.id.trim()); diff --git a/src/main/java/com/alist/api/modules/auth/mapper/AuthMapper.java b/src/main/java/com/alist/api/modules/auth/mapper/AuthMapper.java new file mode 100644 index 0000000..4d305ee --- /dev/null +++ b/src/main/java/com/alist/api/modules/auth/mapper/AuthMapper.java @@ -0,0 +1,24 @@ +package com.alist.api.modules.auth.mapper; + +import com.alist.api.modules.auth.dto.LoginDto; +import com.alist.api.modules.auth.vo.LoginTokenVo; +import com.alist.api.modules.auth.vo.LoginVo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +@Mapper +public interface AuthMapper { + LoginVo selectLogin(LoginDto loginDto); + + LoginTokenVo selectUserTokenByUserId(LoginDto loginDto); + + LoginTokenVo selectUserTokenByUserApiKey(LoginDto loginDto); + + void updateRefreshToken(LoginDto loginDto); + + LoginTokenVo selectUserTokenByUserTokenIdx(int userTokenIdx); + + void clearRefreshTokenByUserTokenIdx(int userTokenIdx); + + void updateLoginByLastAt(LoginDto loginDto); +} diff --git a/src/main/java/com/alist/api/modules/auth/mapper/TestCorsMapper.java b/src/main/java/com/alist/api/modules/auth/mapper/CorsMapper.java similarity index 63% rename from src/main/java/com/alist/api/modules/auth/mapper/TestCorsMapper.java rename to src/main/java/com/alist/api/modules/auth/mapper/CorsMapper.java index 0a090f8..5f70680 100644 --- a/src/main/java/com/alist/api/modules/auth/mapper/TestCorsMapper.java +++ b/src/main/java/com/alist/api/modules/auth/mapper/CorsMapper.java @@ -1,15 +1,15 @@ package com.alist.api.modules.auth.mapper; -import com.alist.api.modules.auth.vo.TestCorsOriginVo; +import com.alist.api.modules.auth.vo.CorsOriginVo; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper -public interface TestCorsMapper { +public interface CorsMapper { /** * 전체 CORS 허용 도메인 목록 조회 (user_token_idx별로) * @return CorsOriginDto 리스트 */ - List selectTestCorsAllowedList(); + List selectCorsAllowedList(); } diff --git a/src/main/java/com/alist/api/modules/auth/mapper/SsoMapper.java b/src/main/java/com/alist/api/modules/auth/mapper/SsoMapper.java index 84494c8..4c0ffb3 100644 --- a/src/main/java/com/alist/api/modules/auth/mapper/SsoMapper.java +++ b/src/main/java/com/alist/api/modules/auth/mapper/SsoMapper.java @@ -5,5 +5,5 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface SsoMapper { - SsoClientVo selectSsoClient(String clientId, String redirectUri); + SsoClientVo selectSsoClient(String clientId); } diff --git a/src/main/java/com/alist/api/modules/auth/mapper/TestAuthMapper.java b/src/main/java/com/alist/api/modules/auth/mapper/TestAuthMapper.java deleted file mode 100644 index bb726d2..0000000 --- a/src/main/java/com/alist/api/modules/auth/mapper/TestAuthMapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.alist.api.modules.auth.mapper; - -import com.alist.api.modules.auth.dto.TestLoginDto; -import com.alist.api.modules.auth.vo.TestLoginTokenVo; -import com.alist.api.modules.auth.vo.TestLoginVo; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -@Mapper -public interface TestAuthMapper { - TestLoginVo selectTestLogin(TestLoginDto testLoginDto); - - TestLoginTokenVo selectTestUserTokenByUserId(TestLoginDto testLoginDto); - - TestLoginTokenVo selectTestUserTokenByUserApiKey(TestLoginDto testLoginDto); - - void updateRefreshToken(TestLoginDto testLoginDto); - - TestLoginTokenVo selectTestUserTokenByUserTokenIdx(@Param("userTokenIdx") int userTokenIdx); - - void clearRefreshTokenByUserTokenIdx(@Param("userTokenIdx") int userTokenIdx); - - void updateTestLoginByLastAt(TestLoginDto testLoginDto); -} diff --git a/src/main/java/com/alist/api/modules/auth/service/AuthService.java b/src/main/java/com/alist/api/modules/auth/service/AuthService.java new file mode 100644 index 0000000..7b1208a --- /dev/null +++ b/src/main/java/com/alist/api/modules/auth/service/AuthService.java @@ -0,0 +1,144 @@ +package com.alist.api.modules.auth.service; + +import com.alist.api.config.jwt.JwtTokenProvider; +import com.alist.api.config.properties.JwtProperties; +import com.alist.api.modules.auth.dto.LoginDto; +import com.alist.api.modules.auth.mapper.AuthMapper; +import com.alist.api.modules.auth.vo.LoginTokenVo; +import com.alist.api.modules.auth.vo.LoginVo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; + +@Slf4j +@Service +public class AuthService { + private final AuthMapper authMapper; + private final PasswordEncoder passwordEncoder; + private final JwtTokenProvider jwtTokenProvider; + private final JwtProperties jwtProperties; + + public AuthService(AuthMapper authMapper, PasswordEncoder passwordEncoder, JwtTokenProvider jwtTokenProvider, JwtProperties jwtProperties) { + this.authMapper = authMapper; + this.passwordEncoder = passwordEncoder; + this.jwtTokenProvider = jwtTokenProvider; + this.jwtProperties = jwtProperties; + } + + // id login + public LoginDto selectLogin(LoginDto loginDto) { + LoginVo loginVo = authMapper.selectLogin(loginDto); + + if (loginVo != null) { + if (passwordEncoder.matches(loginDto.getPassword(), loginVo.getPassword())) { + + loginDto.setUserIdx(loginVo.getUserIdx()); + + LoginTokenVo loginTokenVo = authMapper.selectUserTokenByUserId(loginDto); + + if (loginTokenVo != null) { + loginDto.setAccessToken(jwtTokenProvider.createAccessToken(loginTokenVo.getUserTokenIdx(), loginTokenVo.getUserRole())); + loginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(loginTokenVo.getUserTokenIdx())); + + loginDto.setUserTokenIdx(loginTokenVo.getUserTokenIdx()); + + Instant now = Instant.now(); + Instant expiresAt = now.plusSeconds(jwtProperties.getRefreshTokenValiditySeconds()); + + loginDto.setExpiresAt(expiresAt); + + authMapper.updateRefreshToken(loginDto); + + // 마지막 로그인 시간 추가 + authMapper.updateLoginByLastAt(loginDto); + + loginDto.setResultCode(2001); + } else { + loginDto.setResultCode(2003); + } + } else { + loginDto.setResultCode(2003); + } + } else { + loginDto.setResultCode(2003); + } + + return loginDto; + } + + // api key login + public LoginDto selectUserApiKeyLogin(LoginDto loginDto) { + if (loginDto.getUserApiKey() != null) { + LoginTokenVo loginTokenVo = authMapper.selectUserTokenByUserApiKey(loginDto); + + if (loginTokenVo != null) { + loginDto.setAccessToken(jwtTokenProvider.createAccessToken(loginTokenVo.getUserTokenIdx(), loginTokenVo.getUserRole())); + loginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(loginTokenVo.getUserTokenIdx())); + + loginDto.setResultCode(2001); + } else { + loginDto.setResultCode(2003); + } + } else { + loginDto.setResultCode(2003); + } + + return loginDto; + } + + public LoginDto refreshByToken(String refreshToken) { + if (refreshToken == null || !jwtTokenProvider.validateToken(refreshToken)) { + return null; + } + + int userTokenIdx; + try { + userTokenIdx = Integer.parseInt(jwtTokenProvider.getUserTokenIdx(refreshToken)); + } catch (NumberFormatException e) { + return null; + } + + LoginTokenVo tokenVo = authMapper.selectUserTokenByUserTokenIdx(userTokenIdx); + if (tokenVo == null || tokenVo.getRefreshToken() == null) { + return null; + } + + if (!refreshToken.equals(tokenVo.getRefreshToken())) { + return null; + } + + LoginDto result = new LoginDto(); + result.setUserTokenIdx(userTokenIdx); + result.setAccessToken(jwtTokenProvider.createAccessToken(tokenVo.getUserTokenIdx(), tokenVo.getUserRole())); + result.setRefreshToken(jwtTokenProvider.createRefreshToken(tokenVo.getUserTokenIdx())); + result.setExpiresAt(Instant.now().plusSeconds(jwtProperties.getRefreshTokenValiditySeconds())); + + authMapper.updateRefreshToken(result); + return result; + } + + public void clearRefreshToken(String refreshToken) { + if (refreshToken == null || !jwtTokenProvider.validateToken(refreshToken)) { + return; + } + + try { + int userTokenIdx = Integer.parseInt(jwtTokenProvider.getUserTokenIdx(refreshToken)); + authMapper.clearRefreshTokenByUserTokenIdx(userTokenIdx); + } catch (NumberFormatException ignored) { + } + } + + @Transactional + public void updateRefreshToken(LoginDto loginDto) { + Instant now = Instant.now(); + Instant expiresAt = now.plusSeconds(jwtProperties.getRefreshTokenValiditySeconds()); + + loginDto.setExpiresAt(expiresAt); + + authMapper.updateRefreshToken(loginDto); + } +} \ No newline at end of file diff --git a/src/main/java/com/alist/api/modules/auth/service/SsoService.java b/src/main/java/com/alist/api/modules/auth/service/SsoService.java index 2547444..442e0b4 100644 --- a/src/main/java/com/alist/api/modules/auth/service/SsoService.java +++ b/src/main/java/com/alist/api/modules/auth/service/SsoService.java @@ -2,7 +2,7 @@ package com.alist.api.modules.auth.service; import com.alist.api.modules.auth.dto.SsoExchangeDto; import com.alist.api.modules.auth.dto.SsoLoginDto; -import com.alist.api.modules.auth.dto.TestLoginDto; +import com.alist.api.modules.auth.dto.LoginDto; import com.alist.api.modules.auth.mapper.SsoMapper; import com.alist.api.modules.auth.vo.SsoClientVo; import jakarta.servlet.http.HttpServletRequest; @@ -18,34 +18,32 @@ import java.util.UUID; @Service public class SsoService { - private final TestAuthService testAuthService; - + private final AuthService authService; private final SsoMapper ssoMapper; private final StringRedisTemplate redisTemplate; - private final Duration ssoSessionTtl; private final Duration ssoCodeTtl; - public SsoService(TestAuthService testAuthService, SsoMapper ssoMapper, StringRedisTemplate redisTemplate, @Value("${sso.session.ttl-seconds}") long ssoSessionTtlSeconds, @Value("${sso.code.ttl-seconds}") long ssoCodeTtlSeconds) { - this.testAuthService = testAuthService; + public SsoService(AuthService authService, SsoMapper ssoMapper, StringRedisTemplate redisTemplate, @Value("${sso.session.ttl-seconds}") long ssoSessionTtlSeconds, @Value("${sso.code.ttl-seconds}") long ssoCodeTtlSeconds) { + this.authService = authService; this.ssoMapper = ssoMapper; this.redisTemplate = redisTemplate; this.ssoSessionTtl = Duration.ofSeconds(ssoSessionTtlSeconds); this.ssoCodeTtl = Duration.ofSeconds(ssoCodeTtlSeconds); } - public SsoLoginDto login(SsoLoginDto loginDto, HttpServletRequest request) { - TestLoginDto testLoginDto = testAuthService.selectTestLogin(loginDto.testLoginDto()); + public SsoLoginDto login(SsoLoginDto ssoLoginDto, HttpServletRequest request) { + LoginDto loginDto = authService.selectLogin(ssoLoginDto.toLoginDto()); SsoLoginDto result = new SsoLoginDto(); - result.setResultCode(testLoginDto.getResultCode()); + result.setResultCode(loginDto.getResultCode()); - if (testLoginDto.getResultCode() == 2003) { + if (loginDto.getResultCode() == 2003) { return result; } String ssoSessionId = "SSO_" + UUID.randomUUID(); - String latestSessionKey = "alist:sso:userIdx:" + testLoginDto.getUserIdx(); + String latestSessionKey = "alist:sso:userIdx:" + loginDto.getUserIdx(); String sessionKey = "alist:sso:session:" + ssoSessionId; String oldSsoSessionId = redisTemplate.opsForValue().get(latestSessionKey); @@ -53,17 +51,17 @@ public class SsoService { redisTemplate.delete("alist:sso:session:" + oldSsoSessionId); } - redisTemplate.opsForHash().put(sessionKey, "userIdx", String.valueOf(testLoginDto.getUserIdx())); - redisTemplate.opsForHash().put(sessionKey, "userTokenIdx", String.valueOf(testLoginDto.getUserTokenIdx())); - redisTemplate.opsForHash().put(sessionKey, "userId", testLoginDto.getId()); + redisTemplate.opsForHash().put(sessionKey, "userIdx", String.valueOf(loginDto.getUserIdx())); + redisTemplate.opsForHash().put(sessionKey, "userTokenIdx", String.valueOf(loginDto.getUserTokenIdx())); + redisTemplate.opsForHash().put(sessionKey, "userId", loginDto.getId()); redisTemplate.opsForHash().put(sessionKey, "userRole", "USER"); redisTemplate.expire(sessionKey, ssoSessionTtl); redisTemplate.opsForValue().set(latestSessionKey, ssoSessionId, ssoSessionTtl); result.setSsoSessionId(ssoSessionId); - result.setUserId(testLoginDto.getId()); - result.setUserIdx(testLoginDto.getUserIdx()); + result.setUserId(loginDto.getId()); + result.setUserIdx(loginDto.getUserIdx()); result.setUserRole("USER"); result.setResultCode(200); @@ -119,7 +117,7 @@ public class SsoService { return "/login"; } - SsoClientVo ssoClientVo = ssoMapper.selectSsoClient(clientId, redirectUri); + SsoClientVo ssoClientVo = ssoMapper.selectSsoClient(clientId); if (ssoClientVo == null) { throw new IllegalArgumentException("허용되지 않은 clientId 또는 redirectUri 입니다."); } diff --git a/src/main/java/com/alist/api/modules/auth/service/TestAuthService.java b/src/main/java/com/alist/api/modules/auth/service/TestAuthService.java deleted file mode 100644 index e13e7d9..0000000 --- a/src/main/java/com/alist/api/modules/auth/service/TestAuthService.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.alist.api.modules.auth.service; - -import com.alist.api.config.jwt.JwtTokenProvider; -import com.alist.api.config.properties.JwtProperties; -import com.alist.api.modules.auth.dto.TestLoginDto; -import com.alist.api.modules.auth.mapper.TestAuthMapper; -import com.alist.api.modules.auth.vo.TestLoginTokenVo; -import com.alist.api.modules.auth.vo.TestLoginVo; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.Instant; - -@Slf4j -@Service -public class TestAuthService { - private final TestAuthMapper testAuthMapper; - private final PasswordEncoder passwordEncoder; - private final JwtTokenProvider jwtTokenProvider; - private final JwtProperties jwtProperties; - - public TestAuthService(TestAuthMapper testAuthMapper, PasswordEncoder passwordEncoder, JwtTokenProvider jwtTokenProvider, JwtProperties jwtProperties) { - this.testAuthMapper = testAuthMapper; - this.passwordEncoder = passwordEncoder; - this.jwtTokenProvider = jwtTokenProvider; - this.jwtProperties = jwtProperties; - } - - // id login - public TestLoginDto selectTestLogin(TestLoginDto testLoginDto) { - TestLoginVo testLoginVo = testAuthMapper.selectTestLogin(testLoginDto); - - if (testLoginVo != null) { - if (passwordEncoder.matches(testLoginDto.getPassword(), testLoginVo.getPassword())) { - - testLoginDto.setUserIdx(testLoginVo.getUserIdx()); - - TestLoginTokenVo testLoginTokenVo = testAuthMapper.selectTestUserTokenByUserId(testLoginDto); - - if (testLoginTokenVo != null) { - testLoginDto.setAccessToken(jwtTokenProvider.createAccessToken(testLoginTokenVo.getUserTokenIdx(), testLoginTokenVo.getUserRole())); - testLoginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(testLoginTokenVo.getUserTokenIdx())); - - testLoginDto.setUserTokenIdx(testLoginTokenVo.getUserTokenIdx()); - - Instant now = Instant.now(); - Instant expiresAt = now.plusSeconds(jwtProperties.getRefreshTokenValiditySeconds()); - - testLoginDto.setExpiresAt(expiresAt); - - testAuthMapper.updateRefreshToken(testLoginDto); - - // 마지막 로그인 시간 추가 - testAuthMapper.updateTestLoginByLastAt(testLoginDto); - - testLoginDto.setResultCode(2001); - } else { - testLoginDto.setResultCode(2003); - } - } else { - testLoginDto.setResultCode(2003); - } - } else { - testLoginDto.setResultCode(2003); - } - - return testLoginDto; - } - - // api key login - public TestLoginDto selectTestUserApiKeyLogin(TestLoginDto testLoginDto) { - if (testLoginDto.getUserApiKey() != null) { - TestLoginTokenVo testLoginTokenVo = testAuthMapper.selectTestUserTokenByUserApiKey(testLoginDto); - - if (testLoginTokenVo != null) { - testLoginDto.setAccessToken(jwtTokenProvider.createAccessToken(testLoginTokenVo.getUserTokenIdx(), testLoginTokenVo.getUserRole())); - testLoginDto.setRefreshToken(jwtTokenProvider.createRefreshToken(testLoginTokenVo.getUserTokenIdx())); - - testLoginDto.setResultCode(2001); - } else { - testLoginDto.setResultCode(2003); - } - } else { - testLoginDto.setResultCode(2003); - } - - return testLoginDto; - } - - public TestLoginDto refreshByToken(String refreshToken) { - if (refreshToken == null || !jwtTokenProvider.validateToken(refreshToken)) { - return null; - } - - int userTokenIdx; - try { - userTokenIdx = Integer.parseInt(jwtTokenProvider.getUserTokenIdx(refreshToken)); - } catch (NumberFormatException e) { - return null; - } - - TestLoginTokenVo tokenVo = testAuthMapper.selectTestUserTokenByUserTokenIdx(userTokenIdx); - if (tokenVo == null || tokenVo.getRefreshToken() == null) { - return null; - } - - if (!refreshToken.equals(tokenVo.getRefreshToken())) { - return null; - } - - TestLoginDto testLogin = new TestLoginDto(); - testLogin.setUserTokenIdx(userTokenIdx); - testLogin.setAccessToken(jwtTokenProvider.createAccessToken(tokenVo.getUserTokenIdx(), tokenVo.getUserRole())); - testLogin.setRefreshToken(jwtTokenProvider.createRefreshToken(tokenVo.getUserTokenIdx())); - testLogin.setExpiresAt(Instant.now().plusSeconds(jwtProperties.getRefreshTokenValiditySeconds())); - - testAuthMapper.updateRefreshToken(testLogin); - return testLogin; - } - - public void clearRefreshToken(String refreshToken) { - if (refreshToken == null || !jwtTokenProvider.validateToken(refreshToken)) { - return; - } - - try { - int userTokenIdx = Integer.parseInt(jwtTokenProvider.getUserTokenIdx(refreshToken)); - testAuthMapper.clearRefreshTokenByUserTokenIdx(userTokenIdx); - } catch (NumberFormatException ignored) { - } - } - - @Transactional - public void updateRefreshToken(TestLoginDto testLoginDto) { - Instant now = Instant.now(); - Instant expiresAt = now.plusSeconds(jwtProperties.getRefreshTokenValiditySeconds()); - - testLoginDto.setExpiresAt(expiresAt); - - testAuthMapper.updateRefreshToken(testLoginDto); - } -} \ No newline at end of file diff --git a/src/main/java/com/alist/api/modules/auth/vo/TestCorsOriginVo.java b/src/main/java/com/alist/api/modules/auth/vo/CorsOriginVo.java similarity index 81% rename from src/main/java/com/alist/api/modules/auth/vo/TestCorsOriginVo.java rename to src/main/java/com/alist/api/modules/auth/vo/CorsOriginVo.java index 1d3edbd..32ad1df 100644 --- a/src/main/java/com/alist/api/modules/auth/vo/TestCorsOriginVo.java +++ b/src/main/java/com/alist/api/modules/auth/vo/CorsOriginVo.java @@ -5,6 +5,6 @@ import lombok.Setter; @Getter @Setter -public class TestCorsOriginVo { +public class CorsOriginVo { private String allowedOrigin; } diff --git a/src/main/java/com/alist/api/modules/auth/vo/TestLoginTokenVo.java b/src/main/java/com/alist/api/modules/auth/vo/LoginTokenVo.java similarity index 85% rename from src/main/java/com/alist/api/modules/auth/vo/TestLoginTokenVo.java rename to src/main/java/com/alist/api/modules/auth/vo/LoginTokenVo.java index 11799be..dd1d73c 100644 --- a/src/main/java/com/alist/api/modules/auth/vo/TestLoginTokenVo.java +++ b/src/main/java/com/alist/api/modules/auth/vo/LoginTokenVo.java @@ -3,7 +3,7 @@ package com.alist.api.modules.auth.vo; import lombok.Getter; @Getter -public class TestLoginTokenVo { +public class LoginTokenVo { private Integer userTokenIdx; private String userApiKey; private String userRole; diff --git a/src/main/java/com/alist/api/modules/auth/vo/TestLoginVo.java b/src/main/java/com/alist/api/modules/auth/vo/LoginVo.java similarity index 84% rename from src/main/java/com/alist/api/modules/auth/vo/TestLoginVo.java rename to src/main/java/com/alist/api/modules/auth/vo/LoginVo.java index 68821b5..6c1340c 100644 --- a/src/main/java/com/alist/api/modules/auth/vo/TestLoginVo.java +++ b/src/main/java/com/alist/api/modules/auth/vo/LoginVo.java @@ -3,7 +3,7 @@ package com.alist.api.modules.auth.vo; import lombok.Getter; @Getter -public class TestLoginVo { +public class LoginVo { private Integer userIdx; private String id; private String password; diff --git a/src/main/java/com/alist/api/modules/file/FileController.java b/src/main/java/com/alist/api/modules/file/FileController.java index ff0f439..620b9d7 100644 --- a/src/main/java/com/alist/api/modules/file/FileController.java +++ b/src/main/java/com/alist/api/modules/file/FileController.java @@ -1,20 +1,22 @@ package com.alist.api.modules.file; +import com.alist.api.common.response.ApiResponse; +import com.alist.api.common.response.ApiResponseCode; import com.alist.api.common.utils.SecurityUtil; import com.alist.api.modules.file.dto.*; import com.alist.api.modules.file.form.*; -import com.alist.api.common.response.ApiResponse; -import com.alist.api.common.response.ApiResponseCode; import com.alist.api.modules.file.service.FileService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; -import org.springframework.http.*; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.io.IOException; @@ -44,7 +46,7 @@ public class FileController { public ResponseEntity> uploadInit( @Valid @RequestBody FileUploadForm fileUploadForm ) { - FileUploadDto fileUploadDto = fileUploadForm.fileUploadDto(); + FileUploadDto fileUploadDto = fileUploadForm.toFileUploadDto(); Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx(); @@ -109,7 +111,7 @@ public class FileController { public ResponseEntity> uploadStatus( @Valid @RequestBody UploadStatusForm uploadStatusForm ) { - UploadStatusDto status = fileService.getUploadStatus(uploadStatusForm.uploadStatusDto()); + UploadStatusDto status = fileService.getUploadStatus(uploadStatusForm.toUploadStatusDto()); if (status == null) { return ApiResponse.entity(new UploadStatusDto(), ApiResponseCode.CODE_2003); } @@ -122,10 +124,10 @@ public class FileController { ) @PostMapping("/tusHook") public ResponseEntity> tusHook(@RequestBody TusHookForm tusHookForm) { - boolean accepted = fileService.updateFileUploadStatus(tusHookForm.tusHookDto()); + boolean accepted = fileService.updateFileUploadStatus(tusHookForm.toTusHookDto()); if (accepted) { - fileService.saveUploadStatusRedisTusHook(tusHookForm.tusHookDto()); + fileService.saveUploadStatusRedisTusHook(tusHookForm.toTusHookDto()); } return ApiResponse.entity("", ApiResponseCode.CODE_200); @@ -273,7 +275,7 @@ public class FileController { public ResponseEntity> fileDelete( @Valid @RequestBody FileDeleteForm fileDeleteForm ) { - boolean deleted = fileService.updateFileDelete(fileDeleteForm.fileDeleteDto()); + boolean deleted = fileService.updateFileDelete(fileDeleteForm.toFileDeleteDto()); if (!deleted) { return ApiResponse.entity("", ApiResponseCode.CODE_2005, "파일 삭제"); diff --git a/src/main/java/com/alist/api/modules/file/form/FileDeleteForm.java b/src/main/java/com/alist/api/modules/file/form/FileDeleteForm.java index f5398b9..9c90f7e 100644 --- a/src/main/java/com/alist/api/modules/file/form/FileDeleteForm.java +++ b/src/main/java/com/alist/api/modules/file/form/FileDeleteForm.java @@ -11,7 +11,7 @@ public class FileDeleteForm { @NotBlank private String fileUuid; - public FileDeleteDto fileDeleteDto() { + public FileDeleteDto toFileDeleteDto() { FileDeleteDto fileDeleteDto = new FileDeleteDto(); fileDeleteDto.setFileUuid(fileUuid == null ? null : fileUuid.trim()); return fileDeleteDto; diff --git a/src/main/java/com/alist/api/modules/file/form/FileUploadForm.java b/src/main/java/com/alist/api/modules/file/form/FileUploadForm.java index 1b2385e..3c5103b 100644 --- a/src/main/java/com/alist/api/modules/file/form/FileUploadForm.java +++ b/src/main/java/com/alist/api/modules/file/form/FileUploadForm.java @@ -23,7 +23,7 @@ public class FileUploadForm { @Valid private List itemList; - public FileUploadDto fileUploadDto() { + public FileUploadDto toFileUploadDto() { FileUploadDto fileUploadDto = new FileUploadDto(); fileUploadDto.setFileCategory(this.fileCategory.trim()); diff --git a/src/main/java/com/alist/api/modules/file/form/TusHookForm.java b/src/main/java/com/alist/api/modules/file/form/TusHookForm.java index a7e921b..76df398 100644 --- a/src/main/java/com/alist/api/modules/file/form/TusHookForm.java +++ b/src/main/java/com/alist/api/modules/file/form/TusHookForm.java @@ -16,7 +16,7 @@ public class TusHookForm { @JsonProperty("Event") private TusHookEventForm tusHookEventForm; - public TusHookDto tusHookDto() { + public TusHookDto toTusHookDto() { TusHookDto tusHookDto = new TusHookDto(); tusHookDto.setType(this.type); diff --git a/src/main/java/com/alist/api/modules/file/form/UploadStatusForm.java b/src/main/java/com/alist/api/modules/file/form/UploadStatusForm.java index 04331ac..6cf8177 100644 --- a/src/main/java/com/alist/api/modules/file/form/UploadStatusForm.java +++ b/src/main/java/com/alist/api/modules/file/form/UploadStatusForm.java @@ -11,7 +11,7 @@ public class UploadStatusForm { @NotBlank private String fileUuid; - public UploadStatusDto uploadStatusDto() { + public UploadStatusDto toUploadStatusDto() { UploadStatusDto uploadStatusDto = new UploadStatusDto(); uploadStatusDto.setFileUuid(this.fileUuid == null ? null : this.fileUuid.trim()); return uploadStatusDto; diff --git a/src/main/java/com/alist/api/modules/testUser/TestUserController.java b/src/main/java/com/alist/api/modules/testUser/TestUserController.java deleted file mode 100644 index 7bc300e..0000000 --- a/src/main/java/com/alist/api/modules/testUser/TestUserController.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.alist.api.modules.testUser; - -import com.alist.api.common.response.ApiResponse; -import com.alist.api.common.response.ApiResponseCode; -import com.alist.api.modules.testUser.dto.TestUserDto; -import com.alist.api.modules.testUser.form.TestUserSignupForm; -import com.alist.api.modules.testUser.service.TestUserService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Tag(name = "02. 테스트 사용자 관리", description = "테스트 사용자 회원가입 및 관리 API") -@Slf4j -@RestController -@RequestMapping("/test") -public class TestUserController { - - public final TestUserService testUserService; - - public TestUserController(TestUserService testUserService) { - this.testUserService = testUserService; - } - - @Operation( - summary = "테스트 사용자 회원가입", - description = "새로운 테스트 사용자를 등록합니다. 아이디 중복 시 오류를 반환합니다." - ) - @PostMapping("/testSignup") - public ResponseEntity> testSignup(@Valid @RequestBody TestUserSignupForm testUserSignupForm) { - - TestUserDto testUserDto = testUserService.insertTestUserProc(testUserSignupForm.testUserDto()); - - if (testUserDto.getResultCode() == 2004) { - return ApiResponse.entity(testUserDto, ApiResponseCode.CODE_2004, "아이디"); - } - - return ApiResponse.entity(testUserDto, ApiResponseCode.CODE_2002, "아이디"); - } - -} \ No newline at end of file diff --git a/src/main/java/com/alist/api/modules/testUser/mapper/TestUserMapper.java b/src/main/java/com/alist/api/modules/testUser/mapper/TestUserMapper.java deleted file mode 100644 index 5ff3e0a..0000000 --- a/src/main/java/com/alist/api/modules/testUser/mapper/TestUserMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.alist.api.modules.testUser.mapper; - -import com.alist.api.modules.testUser.dto.TestUserDto; -import com.alist.api.modules.testUser.dto.TestUserTokenDto; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface TestUserMapper { - int selectDuplicateTestUserCount(TestUserDto testUserDto); - - int insertTestUserSignup(TestUserDto testUserDto); - - int insertTestUserTokenSignup(TestUserTokenDto testUserTokenDto); -} diff --git a/src/main/java/com/alist/api/modules/testUser/service/TestUserService.java b/src/main/java/com/alist/api/modules/testUser/service/TestUserService.java deleted file mode 100644 index 6b4d4f6..0000000 --- a/src/main/java/com/alist/api/modules/testUser/service/TestUserService.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.alist.api.modules.testUser.service; - -import com.alist.api.common.utils.ApiKeyGenerator; -import com.alist.api.modules.testUser.dto.TestUserDto; -import com.alist.api.modules.testUser.dto.TestUserTokenDto; -import com.alist.api.modules.testUser.mapper.TestUserMapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.dao.DuplicateKeyException; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -public class TestUserService { - private final TestUserMapper testUserMapper; - private final PasswordEncoder passwordEncoder; - - public TestUserService(TestUserMapper testUserMapper, PasswordEncoder passwordEncoder) { - this.testUserMapper = testUserMapper; - this.passwordEncoder = passwordEncoder; - } - - @Transactional - public TestUserDto insertTestUserProc(TestUserDto testUserDto) { - // 중복체크 - if (testUserMapper.selectDuplicateTestUserCount(testUserDto) > 0) { - testUserDto.setResultCode(2004); - return testUserDto; - } - - // 비밀번호 생성 - testUserDto.setNewPassword(passwordEncoder.encode(testUserDto.getPassword())); - - // 회원정보 입력 - int result = testUserMapper.insertTestUserSignup(testUserDto); - - if (result > 0) { - // user_api_key 생성 - TestUserTokenDto testUserTokenDto = new TestUserTokenDto(); - testUserTokenDto.setUserIdx(testUserDto.getUserIdx()); - - apiKeyWithRetry(testUserTokenDto); - } - - log.info("cnt : " + testUserDto.getUserIdx()); - log.info("userIdx : " + testUserDto.getUserIdx()); - - return testUserDto; - } - - @Transactional - public int apiKeyWithRetry(TestUserTokenDto testUserTokenDto) { - for (int i = 0; i < 5; i++) { - testUserTokenDto.setUserApiKey(ApiKeyGenerator.userApiKeyProc()); - try { - return testUserMapper.insertTestUserTokenSignup(testUserTokenDto); - } catch (DuplicateKeyException e) { - // 충돌이면 다시 생성 - log.warn("Duplicate API key. retrying... userId={}", testUserTokenDto.getUserIdx()); - } - } - throw new IllegalStateException("API key generation failed after retries."); - } -} diff --git a/src/main/java/com/alist/api/modules/testUser/vo/TestUserVo.java b/src/main/java/com/alist/api/modules/testUser/vo/TestUserVo.java deleted file mode 100644 index 9d08a7b..0000000 --- a/src/main/java/com/alist/api/modules/testUser/vo/TestUserVo.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.alist.api.modules.testUser.vo; - -import lombok.Getter; - -@Getter -public class TestUserVo { - private String id; -} diff --git a/src/main/java/com/alist/api/modules/user/UserController.java b/src/main/java/com/alist/api/modules/user/UserController.java new file mode 100644 index 0000000..c877046 --- /dev/null +++ b/src/main/java/com/alist/api/modules/user/UserController.java @@ -0,0 +1,46 @@ +package com.alist.api.modules.user; + +import com.alist.api.common.response.ApiResponse; +import com.alist.api.common.response.ApiResponseCode; +import com.alist.api.modules.user.dto.UserDto; +import com.alist.api.modules.user.form.UserSignupForm; +import com.alist.api.modules.user.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "05. 사용자 관리", description = "사용자 회원가입 및 관리 API") +@Slf4j +@RestController +@RequestMapping("/user") +public class UserController { + + public final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + @Operation( + summary = "사용자 회원가입", + description = "새로운 사용자를 등록합니다. 아이디 중복 시 오류를 반환합니다." + ) + @PostMapping("/signup") + public ResponseEntity> signup(@Valid @RequestBody UserSignupForm userSignupForm) { + + UserDto userDto = userService.insertUser(userSignupForm.toUserDto()); + + if (userDto.getResultCode() == 2004) { + return ApiResponse.entity(userDto, ApiResponseCode.CODE_2004, "아이디"); + } + + return ApiResponse.entity(userDto, ApiResponseCode.CODE_2002, "아이디"); + } + +} \ No newline at end of file diff --git a/src/main/java/com/alist/api/modules/testUser/dto/TestUserDto.java b/src/main/java/com/alist/api/modules/user/dto/UserDto.java similarity index 79% rename from src/main/java/com/alist/api/modules/testUser/dto/TestUserDto.java rename to src/main/java/com/alist/api/modules/user/dto/UserDto.java index 8e58b54..28334a0 100644 --- a/src/main/java/com/alist/api/modules/testUser/dto/TestUserDto.java +++ b/src/main/java/com/alist/api/modules/user/dto/UserDto.java @@ -1,4 +1,4 @@ -package com.alist.api.modules.testUser.dto; +package com.alist.api.modules.user.dto; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; @@ -6,7 +6,7 @@ import lombok.Setter; @Setter @Getter -public class TestUserDto { +public class UserDto { private Integer userIdx; private String id; diff --git a/src/main/java/com/alist/api/modules/testUser/dto/TestUserTokenDto.java b/src/main/java/com/alist/api/modules/user/dto/UserTokenDto.java similarity index 72% rename from src/main/java/com/alist/api/modules/testUser/dto/TestUserTokenDto.java rename to src/main/java/com/alist/api/modules/user/dto/UserTokenDto.java index c4bb693..ada656d 100644 --- a/src/main/java/com/alist/api/modules/testUser/dto/TestUserTokenDto.java +++ b/src/main/java/com/alist/api/modules/user/dto/UserTokenDto.java @@ -1,11 +1,11 @@ -package com.alist.api.modules.testUser.dto; +package com.alist.api.modules.user.dto; import lombok.Getter; import lombok.Setter; @Setter @Getter -public class TestUserTokenDto { +public class UserTokenDto { private Integer userTokenIdx; private Integer userIdx; private String userApiKey; diff --git a/src/main/java/com/alist/api/modules/testUser/form/TestUserSignupForm.java b/src/main/java/com/alist/api/modules/user/form/UserSignupForm.java similarity index 74% rename from src/main/java/com/alist/api/modules/testUser/form/TestUserSignupForm.java rename to src/main/java/com/alist/api/modules/user/form/UserSignupForm.java index 9098cca..e52f274 100644 --- a/src/main/java/com/alist/api/modules/testUser/form/TestUserSignupForm.java +++ b/src/main/java/com/alist/api/modules/user/form/UserSignupForm.java @@ -1,6 +1,6 @@ -package com.alist.api.modules.testUser.form; +package com.alist.api.modules.user.form; -import com.alist.api.modules.testUser.dto.TestUserDto; +import com.alist.api.modules.user.dto.UserDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; @@ -11,7 +11,7 @@ import lombok.Setter; @Setter @Getter @Schema(description = "회원 가입 요청 폼") -public class TestUserSignupForm { +public class UserSignupForm { @Schema( description = "사용자 아이디 (공백 불가)", example = "test" @@ -31,10 +31,10 @@ public class TestUserSignupForm { ) private String password; - public TestUserDto testUserDto() { - TestUserDto testUserDto = new TestUserDto(); - testUserDto.setId(id.trim()); - testUserDto.setPassword(password); - return testUserDto; + public UserDto toUserDto() { + UserDto userDto = new UserDto(); + userDto.setId(id.trim()); + userDto.setPassword(password); + return userDto; } } diff --git a/src/main/java/com/alist/api/modules/user/mapper/UserMapper.java b/src/main/java/com/alist/api/modules/user/mapper/UserMapper.java new file mode 100644 index 0000000..61d7c1f --- /dev/null +++ b/src/main/java/com/alist/api/modules/user/mapper/UserMapper.java @@ -0,0 +1,14 @@ +package com.alist.api.modules.user.mapper; + +import com.alist.api.modules.user.dto.UserDto; +import com.alist.api.modules.user.dto.UserTokenDto; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface UserMapper { + int selectDuplicateUserCount(UserDto userDto); + + int insertUserSignup(UserDto userDto); + + int insertUserTokenSignup(UserTokenDto userTokenDto); +} diff --git a/src/main/java/com/alist/api/modules/user/service/UserService.java b/src/main/java/com/alist/api/modules/user/service/UserService.java new file mode 100644 index 0000000..df0ffc9 --- /dev/null +++ b/src/main/java/com/alist/api/modules/user/service/UserService.java @@ -0,0 +1,62 @@ +package com.alist.api.modules.user.service; + +import com.alist.api.common.utils.ApiKeyGenerator; +import com.alist.api.modules.user.dto.UserDto; +import com.alist.api.modules.user.dto.UserTokenDto; +import com.alist.api.modules.user.mapper.UserMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +public class UserService { + private final UserMapper userMapper; + private final PasswordEncoder passwordEncoder; + + public UserService(UserMapper userMapper, PasswordEncoder passwordEncoder) { + this.userMapper = userMapper; + this.passwordEncoder = passwordEncoder; + } + + @Transactional + public UserDto insertUser(UserDto userDto) { + // 중복체크 + if (userMapper.selectDuplicateUserCount(userDto) > 0) { + userDto.setResultCode(2004); + return userDto; + } + + // 비밀번호 생성 + userDto.setNewPassword(passwordEncoder.encode(userDto.getPassword())); + + // 회원정보 입력 + int result = userMapper.insertUserSignup(userDto); + + if (result > 0) { + // user_api_key 생성 + UserTokenDto userTokenDto = new UserTokenDto(); + userTokenDto.setUserIdx(userDto.getUserIdx()); + + apiKeyWithRetry(userTokenDto); + } + + return userDto; + } + + @Transactional + public int apiKeyWithRetry(UserTokenDto userTokenDto) { + for (int i = 0; i < 5; i++) { + userTokenDto.setUserApiKey(ApiKeyGenerator.userApiKeyProc()); + try { + return userMapper.insertUserTokenSignup(userTokenDto); + } catch (DuplicateKeyException e) { + // 충돌이면 다시 생성 + log.warn("Duplicate API key. retrying... userId={}", userTokenDto.getUserIdx()); + } + } + throw new IllegalStateException("API key generation failed after retries."); + } +} diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 3fc3cf6..9bf3e99 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -11,19 +11,12 @@ spring: minimum-idle: 1 # 최소로 connection-timeout: 10000 idle-timeout: 30000 - data: redis: host: 121.160.234.222 port: 3001 password: 1qaz2wsx!@ - session: - store-type: redis - timeout: 7200 # 2시간 (초 단위) - redis: - namespace: spring:session:alist - logging: config: classpath:logback-local.xml @@ -33,10 +26,10 @@ jwt: refresh-token-validity-seconds: 2592000 cookie: - secure: true # 로컬 개발 환경 (HTTP) - domain: api-alist.pjt.kr + secure: true # (HTTP) -> false + domain: api-alist.pjt.kr # (HTTP) -> 비워두세요 name: ALIST_SSO - same-site: None + same-site: None # (HTTP) -> Lax swagger: login: diff --git a/src/main/resources/application-pjt.yaml b/src/main/resources/application-pjt.yaml index ea84795..ddae41e 100644 --- a/src/main/resources/application-pjt.yaml +++ b/src/main/resources/application-pjt.yaml @@ -13,19 +13,12 @@ spring: minimum-idle: 1 # 최소로 connection-timeout: 10000 idle-timeout: 30000 - data: redis: host: ${REDIS_HOST} port: ${REDIS_PORT} password: ${REDIS_PASSWORD} - session: - store-type: redis - timeout: 7200 # 2시간 (초 단위) - redis: - namespace: spring:session:alist - logging: config: classpath:logback-pjt.xml @@ -36,7 +29,7 @@ jwt: cookie: secure: true # 프로젝트 환경 (HTTPS) - domain: api-alist.pjt.kr # sso 인증시 주석해제 + domain: api-alist.pjt.kr name: ALIST_SSO same-site: None diff --git a/src/main/resources/mapper/User/UserMapper.xml b/src/main/resources/mapper/User/UserMapper.xml new file mode 100644 index 0000000..b9c2e5b --- /dev/null +++ b/src/main/resources/mapper/User/UserMapper.xml @@ -0,0 +1,23 @@ + + + + + + /*UserMapper.insertUserSignup*/ + insert into ALISTLMS.test_user (id, password, del_yn, create_at, update_at) + value (#{id}, #{newPassword}, 1, now(), now()) + + + /*UserMapper.insertUserTokenSignup*/ + insert into ALISTLMS.test_user_token (user_idx, user_api_key, user_role, created_at, updated_at) + value (#{userIdx}, #{userApiKey}, 'user', now(), now()) + + + + + diff --git a/src/main/resources/mapper/auth/CorsMapper.xml b/src/main/resources/mapper/auth/CorsMapper.xml new file mode 100644 index 0000000..58da55a --- /dev/null +++ b/src/main/resources/mapper/auth/CorsMapper.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/auth/TestLoginMapper.xml b/src/main/resources/mapper/auth/LoginMapper.xml similarity index 50% rename from src/main/resources/mapper/auth/TestLoginMapper.xml rename to src/main/resources/mapper/auth/LoginMapper.xml index 29a4d57..5590cf4 100644 --- a/src/main/resources/mapper/auth/TestLoginMapper.xml +++ b/src/main/resources/mapper/auth/LoginMapper.xml @@ -1,65 +1,65 @@ - + - /*TestLoginMapper.selectTestLogin*/ - update test_user_token + /*LoginMapper.updateRefreshToken*/ + update ALISTLMS.test_user_token set refresh_token = #{refreshToken} , expires_at = #{expiresAt} , updated_at = now() where user_token_idx = #{userTokenIdx} - + /*LoginMapper.selectLogin*/ select user_idx , id , password - from test_user + from ALISTLMS.test_user where id = #{id} and del_yn = 1 - + /*LoginMapper.selectUserTokenByUserId*/ select user_token_idx , user_role , refresh_token - from test_user_token + from ALISTLMS.test_user_token where user_idx = #{userIdx} - + /*LoginMapper.selectUserTokenByUserApiKey*/ select user_token_idx , user_role , refresh_token - from test_user_token + from ALISTLMS.test_user_token where user_api_key = #{userApiKey} - + /*LoginMapper.selectUserTokenByUserTokenIdx*/ select user_token_idx , user_role , refresh_token - from test_user_token + from ALISTLMS.test_user_token where user_token_idx = #{userTokenIdx} - /*TestLoginMapper.clearRefreshTokenByUserTokenIdx*/ - update test_user_token + /*LoginMapper.clearRefreshTokenByUserTokenIdx*/ + update ALISTLMS.test_user_token set refresh_token = null , expires_at = null , updated_at = now() where user_token_idx = #{userTokenIdx} - - /*TestLoginMapper.updateTestLoginByLastAt*/ - update test_user + + /*LoginMapper.updateLoginByLastAt*/ + update ALISTLMS.test_user set last_login_at = now() , update_at = now() where user_idx = #{userIdx} diff --git a/src/main/resources/mapper/auth/SsoMapper.xml b/src/main/resources/mapper/auth/SsoMapper.xml index 066e11e..84d1ea1 100644 --- a/src/main/resources/mapper/auth/SsoMapper.xml +++ b/src/main/resources/mapper/auth/SsoMapper.xml @@ -10,7 +10,7 @@ , REDIRECT_URI , MEMO , USE_YN - FROM SSO_CLIENT + FROM ALISTLMS.SSO_CLIENT WHERE CLIENT_ID = #{clientId} AND USE_YN = 'Y' LIMIT 1 diff --git a/src/main/resources/mapper/auth/TestCorsMapper.xml b/src/main/resources/mapper/auth/TestCorsMapper.xml deleted file mode 100644 index da88620..0000000 --- a/src/main/resources/mapper/auth/TestCorsMapper.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/main/resources/mapper/testUser/TestUserMapper.xml b/src/main/resources/mapper/testUser/TestUserMapper.xml deleted file mode 100644 index bb47054..0000000 --- a/src/main/resources/mapper/testUser/TestUserMapper.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - /*UserSignupMapper.insertTestUserSignup*/ - insert into test_user (id, password, del_yn, create_at, update_at) - value (#{id}, #{newPassword}, 1, now(), now()) - - - /*UserSignupMapper.insertTestUserTokenSignup*/ - insert into test_user_token (user_idx, user_api_key, user_role, created_at, updated_at) - value (#{userIdx}, #{userApiKey}, 'user', now(), now()) - - - - -