[API] SSO 다중 도메인 로그인 작업 커밋
This commit is contained in:
@@ -26,7 +26,6 @@
|
|||||||
- `src/main/java/com/alist/api`: 애플리케이션 시작점과 업무 코드를 둔다.
|
- `src/main/java/com/alist/api`: 애플리케이션 시작점과 업무 코드를 둔다.
|
||||||
- `src/main/java/com/alist/api/modules`: 기능별 모듈 패키지를 둔다.
|
- `src/main/java/com/alist/api/modules`: 기능별 모듈 패키지를 둔다.
|
||||||
- `src/main/resources`: 설정 파일과 로깅 설정을 관리한다.
|
- `src/main/resources`: 설정 파일과 로깅 설정을 관리한다.
|
||||||
- `src/test/java`: 테스트 코드를 둔다.
|
|
||||||
- `deploy`: 배포 관련 리소스가 있으면 이 경로를 우선 확인한다.
|
- `deploy`: 배포 관련 리소스가 있으면 이 경로를 우선 확인한다.
|
||||||
|
|
||||||
## 현재 확인된 구조
|
## 현재 확인된 구조
|
||||||
@@ -35,7 +34,7 @@
|
|||||||
- MyBatis는 인터페이스와 XML을 함께 사용한다.
|
- MyBatis는 인터페이스와 XML을 함께 사용한다.
|
||||||
- Mapper 인터페이스는 `src/main/java/.../mapper`, SQL XML은 `src/main/resources/mapper/...` 경로를 짝으로 맞춘다.
|
- Mapper 인터페이스는 `src/main/java/.../mapper`, SQL XML은 `src/main/resources/mapper/...` 경로를 짝으로 맞춘다.
|
||||||
- 공통 응답은 `common/response`, 보안은 `config/security, jwt`, 전역 예외 처리는 `config/exception` 아래에 둔다.
|
- 공통 응답은 `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`를 필요한 만큼만 추가한다.
|
- 새 기능은 가능하면 `modules/{도메인명}Controller` 단위로 패키지를 만들고 그 아래에 `service`, `mapper`, `dto`, `vo`를 필요한 만큼만 추가한다.
|
||||||
@@ -50,7 +49,7 @@
|
|||||||
- `Vo`는 조회 결과나 스케줄 실행 판단에 필요한 읽기 전용 성격의 값으로 본다.
|
- `Vo`는 조회 결과나 스케줄 실행 판단에 필요한 읽기 전용 성격의 값으로 본다.
|
||||||
- `Form`은 Controller 입력 검증과 요청 바인딩 전용으로 두고, `@Valid` 와 Jakarta Validation 어노테이션을 우선 사용한다.
|
- `Form`은 Controller 입력 검증과 요청 바인딩 전용으로 두고, `@Valid` 와 Jakarta Validation 어노테이션을 우선 사용한다.
|
||||||
- 현재 코드처럼 DTO/VO는 Lombok `@Getter`, 필요한 경우에만 `@Setter`를 사용한다.
|
- 현재 코드처럼 DTO/VO는 Lombok `@Getter`, 필요한 경우에만 `@Setter`를 사용한다.
|
||||||
- Form 안에는 DTO 변환 메서드를 둘 수 있다. 예: `testUserDto()`, `fileUploadDto()`
|
- Form 안에는 DTO 변환 메서드를 둘 수 있다. 예: `userDto()`, `fileUploadDto()`
|
||||||
- DTO 안에 연관된 다른 DTO 변환이 꼭 필요할 때만 최소한의 보조 메서드를 둔다.
|
- DTO 안에 연관된 다른 DTO 변환이 꼭 필요할 때만 최소한의 보조 메서드를 둔다.
|
||||||
- 외부 응답에 노출되면 안 되는 내부 필드는 DTO 에 `@JsonIgnore`로 숨긴다.
|
- 외부 응답에 노출되면 안 되는 내부 필드는 DTO 에 `@JsonIgnore`로 숨긴다.
|
||||||
- Mapper 메서드명은 SQL 동작이 드러나도록 `select`, `insert`, `update` 접두어를 사용한다.
|
- Mapper 메서드명은 SQL 동작이 드러나도록 `select`, `insert`, `update` 접두어를 사용한다.
|
||||||
@@ -144,7 +143,7 @@
|
|||||||
- Swagger: `/v3/api-docs/**`, `/swagger-ui/**` → HTTP Basic 인증 (InMemory)
|
- Swagger: `/v3/api-docs/**`, `/swagger-ui/**` → HTTP Basic 인증 (InMemory)
|
||||||
- API: JWT Bearer 토큰 인증 (Stateless)
|
- API: JWT Bearer 토큰 인증 (Stateless)
|
||||||
- 세션/쿠키: Redis Session 저장소 사용, 쿠키 속성은 프로파일별 `cookie.*` 설정으로 제어
|
- 세션/쿠키: Redis Session 저장소 사용, 쿠키 속성은 프로파일별 `cookie.*` 설정으로 제어
|
||||||
- 공개 경로: `/`, `/actuator/health`, `/auth/**`, `/test/**`, `/files/tusHook`
|
- 공개 경로: `/`, `/actuator/health`, `/sso/**`, `/auth/**`, `/user/signup`, `/files/tusHook`
|
||||||
- Swagger 인증과 API 인증은 `SecurityFilterChain` 을 분리해서 관리한다.
|
- Swagger 인증과 API 인증은 `SecurityFilterChain` 을 분리해서 관리한다.
|
||||||
|
|
||||||
## 응답 코드 규칙
|
## 응답 코드 규칙
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ src/main/java/com/alist/api
|
|||||||
└── modules
|
└── modules
|
||||||
├── auth # 인증/세션 관련 API
|
├── auth # 인증/세션 관련 API
|
||||||
├── main # 루트 리다이렉트
|
├── main # 루트 리다이렉트
|
||||||
└── testUser # 테스트 사용자 API
|
|
||||||
```
|
```
|
||||||
|
|
||||||
리소스 파일은 아래 위치를 사용합니다.
|
리소스 파일은 아래 위치를 사용합니다.
|
||||||
@@ -141,7 +140,6 @@ java -jar build/libs/api.jar --spring.profiles.active=local
|
|||||||
- `/`
|
- `/`
|
||||||
- `/actuator/health`
|
- `/actuator/health`
|
||||||
- `/auth/**`
|
- `/auth/**`
|
||||||
- `/test/**`
|
|
||||||
- `/files/tusHook`
|
- `/files/tusHook`
|
||||||
|
|
||||||
루트 `/` 요청은 `/swagger-ui/index.html`로 리다이렉트됩니다.
|
루트 `/` 요청은 `/swagger-ui/index.html`로 리다이렉트됩니다.
|
||||||
@@ -186,7 +184,7 @@ java -jar build/libs/api.jar --spring.profiles.active=local
|
|||||||
- `POST /auth/refresh`
|
- `POST /auth/refresh`
|
||||||
- `GET /auth/loginChecked`
|
- `GET /auth/loginChecked`
|
||||||
- `POST /auth/logout`
|
- `POST /auth/logout`
|
||||||
- `POST /test/testSignup`
|
- `POST /user/signup`
|
||||||
|
|
||||||
## API 예시 요청/응답
|
## API 예시 요청/응답
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ dependencies {
|
|||||||
|
|
||||||
// Redis (SSO 세션 공유)
|
// Redis (SSO 세션 공유)
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||||
implementation 'org.springframework.session:spring-session-data-redis'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -59,7 +59,7 @@ public class SecurityConfig {
|
|||||||
.accessDeniedHandler(new JwtAccessDeniedHandler())
|
.accessDeniedHandler(new JwtAccessDeniedHandler())
|
||||||
)
|
)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers("/", "/actuator/health", "/sso/**", "/auth/**", "/test/**", "/files/tusHook").permitAll()
|
.requestMatchers("/", "/actuator/health", "/sso/**", "/auth/**", "/user/signup", "/files/tusHook").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.alist.api.config.cache;
|
package com.alist.api.config.cache;
|
||||||
|
|
||||||
import com.alist.api.modules.auth.vo.TestCorsOriginVo;
|
import com.alist.api.modules.auth.vo.CorsOriginVo;
|
||||||
import com.alist.api.modules.auth.mapper.TestCorsMapper;
|
import com.alist.api.modules.auth.mapper.CorsMapper;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -16,7 +16,7 @@ import java.util.stream.Collectors;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class CorsAllowedOriginsCache {
|
public class CorsAllowedOriginsCache {
|
||||||
|
|
||||||
private final TestCorsMapper testCorsMapper;
|
private final CorsMapper corsMapper;
|
||||||
|
|
||||||
// Thread-safe List (volatile로 가시성 보장)
|
// Thread-safe List (volatile로 가시성 보장)
|
||||||
private volatile List<String> allowedOrigins = new ArrayList<>();
|
private volatile List<String> allowedOrigins = new ArrayList<>();
|
||||||
@@ -35,9 +35,9 @@ public class CorsAllowedOriginsCache {
|
|||||||
*/
|
*/
|
||||||
public synchronized void refresh() {
|
public synchronized void refresh() {
|
||||||
try {
|
try {
|
||||||
List<String> origins = testCorsMapper.selectTestCorsAllowedList()
|
List<String> origins = corsMapper.selectCorsAllowedList()
|
||||||
.stream()
|
.stream()
|
||||||
.map(TestCorsOriginVo::getAllowedOrigin)
|
.map(CorsOriginVo::getAllowedOrigin)
|
||||||
.distinct()
|
.distinct()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
package com.alist.api.modules.auth;
|
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.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.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.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.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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
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.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Tag(name = "00. 엑세스 토큰 발급", description = "강제 엑세스 토큰 발급 (JSON 전달, 테스트용)")
|
@Tag(name = "02. 엑세스 토큰 발급", description = "엑세스 토큰 발급")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
@@ -36,9 +34,6 @@ public class AuthController {
|
|||||||
@Value("${cookie.domain}")
|
@Value("${cookie.domain}")
|
||||||
private String cookieDomain;
|
private String cookieDomain;
|
||||||
|
|
||||||
@Value("${cookie.name}")
|
|
||||||
private String cookieName;
|
|
||||||
|
|
||||||
@Value("${cookie.same-site:Lax}")
|
@Value("${cookie.same-site:Lax}")
|
||||||
private String cookieSameSite;
|
private String cookieSameSite;
|
||||||
|
|
||||||
@@ -54,22 +49,22 @@ public class AuthController {
|
|||||||
private final SsoService ssoService;
|
private final SsoService ssoService;
|
||||||
|
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
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.ssoService = ssoService;
|
||||||
this.jwtTokenProvider = jwtTokenProvider;
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
this.testAuthService = testAuthService;
|
this.authService = authService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "엑세스 토큰 발급",
|
summary = "엑세스 토큰 발급 (테스트용 실사용X)",
|
||||||
description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)"
|
description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)"
|
||||||
)
|
)
|
||||||
@PostMapping("/token")
|
@PostMapping("/token")
|
||||||
public ResponseEntity<ApiResponse<TokenDto>> token(@Valid @RequestBody TokenForm tokenForm) {
|
public ResponseEntity<ApiResponse<TokenDto>> token(@Valid @RequestBody TokenForm tokenForm) {
|
||||||
TokenDto tokenDto = new TokenDto();
|
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, "엑세스 토큰");
|
return ApiResponse.entity(tokenDto, ApiResponseCode.CODE_2001, "엑세스 토큰");
|
||||||
}
|
}
|
||||||
@@ -93,11 +88,11 @@ public class AuthController {
|
|||||||
String accessToken = jwtTokenProvider.createAccessToken(ssoSession.getUserTokenIdx(), ssoSession.getUserRole());
|
String accessToken = jwtTokenProvider.createAccessToken(ssoSession.getUserTokenIdx(), ssoSession.getUserRole());
|
||||||
String refreshToken = jwtTokenProvider.createRefreshToken(ssoSession.getUserTokenIdx());
|
String refreshToken = jwtTokenProvider.createRefreshToken(ssoSession.getUserTokenIdx());
|
||||||
|
|
||||||
TestLoginDto testLoginDto = new TestLoginDto();
|
LoginDto loginDto = new LoginDto();
|
||||||
testLoginDto.setUserTokenIdx(ssoSession.getUserTokenIdx());
|
loginDto.setUserTokenIdx(ssoSession.getUserTokenIdx());
|
||||||
testLoginDto.setRefreshToken(refreshToken);
|
loginDto.setRefreshToken(refreshToken);
|
||||||
|
|
||||||
testAuthService.updateRefreshToken(testLoginDto);
|
authService.updateRefreshToken(loginDto);
|
||||||
|
|
||||||
SessionUtil.addTokenCookie(response, "accessToken", accessToken, cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds);
|
SessionUtil.addTokenCookie(response, "accessToken", accessToken, cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds);
|
||||||
SessionUtil.addTokenCookie(response,"refreshToken", refreshToken, cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds);
|
SessionUtil.addTokenCookie(response,"refreshToken", refreshToken, cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds);
|
||||||
@@ -121,73 +116,30 @@ public class AuthController {
|
|||||||
, HttpServletResponse response
|
, 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);
|
return ApiResponse.entity(Map.of("refreshed", false), ApiResponseCode.CODE_401);
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionUtil.addTokenCookie(response, "accessToken", testLogin.getAccessToken(), cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds);
|
SessionUtil.addTokenCookie(response, "accessToken", result.getAccessToken(), cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds);
|
||||||
SessionUtil.addTokenCookie(response, "refreshToken", testLogin.getRefreshToken(), cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds);
|
SessionUtil.addTokenCookie(response, "refreshToken", result.getRefreshToken(), cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds);
|
||||||
|
|
||||||
return ApiResponse.entity(Map.of("refreshed", true), ApiResponseCode.CODE_200);
|
return ApiResponse.entity(Map.of("refreshed", true), ApiResponseCode.CODE_200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "SSO 로그인 상태 확인",
|
summary = "API Key 로그인",
|
||||||
description = "SSO 인증 받은 후 현재 세션이 있는지 확인"
|
description = "API Key로 인증하고 JSON으로 토큰을 반환합니다. (OpenAPI/외부 서비스용)"
|
||||||
)
|
)
|
||||||
@GetMapping("/loginChecked")
|
@PostMapping("/apiKeyLogin")
|
||||||
public ResponseEntity<ApiResponse<Map<String, Object>>> loginChecked(HttpServletRequest request) {
|
public ResponseEntity<ApiResponse<LoginDto>> apiKeyLogin(@Valid @RequestBody ApikeyLoginForm apiKeyLoginForm) {
|
||||||
Map<String, Object> sessionInfo = new HashMap<>();
|
LoginDto result = authService.selectUserApiKeyLogin(apiKeyLoginForm.toLoginDto());
|
||||||
HttpSession session = request.getSession(false); // 새 세션 생성 금지
|
|
||||||
|
|
||||||
if (session == null) {
|
if (result.getResultCode() == 2003) {
|
||||||
sessionInfo.put("loggedIn", false);
|
return ApiResponse.entity(result, ApiResponseCode.CODE_2003);
|
||||||
return ApiResponse.entity(sessionInfo, ApiResponseCode.CODE_200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer userIdx = (Integer) session.getAttribute("userIdx");
|
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "엑세스 토큰");
|
||||||
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<ApiResponse<Map<String, Object>>> 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<String, Object> result = new HashMap<>();
|
|
||||||
result.put("message", "로그아웃되었습니다.");
|
|
||||||
|
|
||||||
return ApiResponse.entity(result, ApiResponseCode.CODE_200);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -13,11 +13,11 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Tag(name = "03. 테스트 CRS 갱신", description = "테스트 CORS 설정 관리 API")
|
@Tag(name = "04. CRS 갱신", description = "테스트 CORS 설정 관리 API")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/test")
|
@RequestMapping("/cors")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class TestCorsAllowController {
|
public class CorsAllowController {
|
||||||
|
|
||||||
private final CorsAllowedOriginsCache corsCache;
|
private final CorsAllowedOriginsCache corsCache;
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ public class TestCorsAllowController {
|
|||||||
summary = "CORS 화이트리스트 갱신",
|
summary = "CORS 화이트리스트 갱신",
|
||||||
description = "DB에서 CORS 허용 도메인 목록을 다시 로딩합니다. DB에 도메인 추가 후 호출하세요."
|
description = "DB에서 CORS 허용 도메인 목록을 다시 로딩합니다. DB에 도메인 추가 후 호출하세요."
|
||||||
)
|
)
|
||||||
@PostMapping("/reloadCors")
|
@PostMapping("/reload")
|
||||||
public ResponseEntity<ApiResponse<List<String>>> reloadCors() {
|
public ResponseEntity<ApiResponse<List<String>>> reloadCors() {
|
||||||
corsCache.refresh();
|
corsCache.refresh();
|
||||||
List<String> cacheStatus = corsCache.getCacheStatus();
|
List<String> cacheStatus = corsCache.getCacheStatus();
|
||||||
@@ -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.SsoExchangeForm;
|
||||||
import com.alist.api.modules.auth.form.SsoLoginForm;
|
import com.alist.api.modules.auth.form.SsoLoginForm;
|
||||||
import com.alist.api.modules.auth.service.SsoService;
|
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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
@@ -23,7 +23,7 @@ import java.net.URI;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Tag(name = "02. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 관련 API")
|
@Tag(name = "01. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 관련 API")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/sso")
|
@RequestMapping("/sso")
|
||||||
public class SsoController {
|
public class SsoController {
|
||||||
@@ -42,11 +42,11 @@ public class SsoController {
|
|||||||
@Value("${sso.cookie.name}")
|
@Value("${sso.cookie.name}")
|
||||||
private String ssoCookieName;
|
private String ssoCookieName;
|
||||||
private final SsoService ssoService;
|
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.ssoService = ssoService;
|
||||||
this.testAuthService = testAuthService;
|
this.authService = authService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -59,7 +59,7 @@ public class SsoController {
|
|||||||
, HttpServletRequest request
|
, HttpServletRequest request
|
||||||
, HttpServletResponse response
|
, HttpServletResponse response
|
||||||
) {
|
) {
|
||||||
SsoLoginDto ssoLoginDto = ssoService.login(ssoLoginForm.ssoLoginDto(), request);
|
SsoLoginDto ssoLoginDto = ssoService.login(ssoLoginForm.toSsoLoginDto(), request);
|
||||||
|
|
||||||
SsoLoginCheckDto result = new SsoLoginCheckDto();
|
SsoLoginCheckDto result = new SsoLoginCheckDto();
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ public class SsoController {
|
|||||||
@Valid @RequestBody SsoExchangeForm ssoExchangeForm
|
@Valid @RequestBody SsoExchangeForm ssoExchangeForm
|
||||||
, HttpServletResponse response
|
, HttpServletResponse response
|
||||||
) {
|
) {
|
||||||
SsoExchangeDto result = ssoService.exchange(ssoExchangeForm.ssoExchangeDto());
|
SsoExchangeDto result = ssoService.exchange(ssoExchangeForm.toSsoExchangeDto());
|
||||||
|
|
||||||
SessionUtil.addTokenCookie(response, ssoCookieName, result.getSsoSessionId(), cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
|
SessionUtil.addTokenCookie(response, ssoCookieName, result.getSsoSessionId(), cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ public class SsoController {
|
|||||||
String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName);
|
String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName);
|
||||||
|
|
||||||
ssoService.logout(ssoSessionId);
|
ssoService.logout(ssoSessionId);
|
||||||
testAuthService.clearRefreshToken(refreshToken);
|
authService.clearRefreshToken(refreshToken);
|
||||||
|
|
||||||
SessionUtil.expireCookie(response, ssoCookieName, cookieDomain, cookieSecure, cookieSameSite);
|
SessionUtil.expireCookie(response, ssoCookieName, cookieDomain, cookieSecure, cookieSameSite);
|
||||||
SessionUtil.expireCookie(response, "accessToken", cookieDomain, cookieSecure, cookieSameSite);
|
SessionUtil.expireCookie(response, "accessToken", cookieDomain, cookieSecure, cookieSameSite);
|
||||||
|
|||||||
@@ -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<ApiResponse<TestLoginDto>> 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<ApiResponse<TestLoginDto>> 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, "엑세스 토큰");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -9,7 +9,7 @@ import java.time.Instant;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class TestLoginDto {
|
public class LoginDto {
|
||||||
@Schema(description = "아이디")
|
@Schema(description = "아이디")
|
||||||
private String id;
|
private String id;
|
||||||
@Schema(description = "엑세스 토큰")
|
@Schema(description = "엑세스 토큰")
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.alist.api.modules.auth.dto;
|
package com.alist.api.modules.auth.dto;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@@ -17,10 +16,10 @@ public class SsoLoginDto {
|
|||||||
private String userId;
|
private String userId;
|
||||||
private String userRole;
|
private String userRole;
|
||||||
|
|
||||||
public TestLoginDto testLoginDto() {
|
public LoginDto toLoginDto() {
|
||||||
TestLoginDto testLoginDto = new TestLoginDto();
|
LoginDto loginDto = new LoginDto();
|
||||||
testLoginDto.setId(this.id);
|
loginDto.setId(this.id);
|
||||||
testLoginDto.setPassword(this.password);
|
loginDto.setPassword(this.password);
|
||||||
return testLoginDto;
|
return loginDto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-8
@@ -1,16 +1,14 @@
|
|||||||
package com.alist.api.modules.auth.form;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Pattern;
|
|
||||||
import jakarta.validation.constraints.Size;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class TestApikeyLoginForm {
|
public class ApikeyLoginForm {
|
||||||
@Schema(
|
@Schema(
|
||||||
description = "api key",
|
description = "api key",
|
||||||
example = "aaaa111dsddd"
|
example = "aaaa111dsddd"
|
||||||
@@ -18,10 +16,10 @@ public class TestApikeyLoginForm {
|
|||||||
@NotBlank(message = "apiKey를 입력해주세요.")
|
@NotBlank(message = "apiKey를 입력해주세요.")
|
||||||
private String userApiKey;
|
private String userApiKey;
|
||||||
|
|
||||||
public TestLoginDto testLoginDto() {
|
public LoginDto toLoginDto() {
|
||||||
TestLoginDto testLoginDto = new TestLoginDto();
|
LoginDto loginDto = new LoginDto();
|
||||||
testLoginDto.setUserApiKey(this.userApiKey.trim());
|
loginDto.setUserApiKey(this.userApiKey.trim());
|
||||||
|
|
||||||
return testLoginDto;
|
return loginDto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ public class SsoExchangeForm {
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
private String clientId;
|
private String clientId;
|
||||||
|
|
||||||
public SsoExchangeDto ssoExchangeDto() {
|
public SsoExchangeDto toSsoExchangeDto() {
|
||||||
SsoExchangeDto ssoExchangeDto = new SsoExchangeDto();
|
SsoExchangeDto ssoExchangeDto = new SsoExchangeDto();
|
||||||
ssoExchangeDto.setCode(code == null ? null : code.trim());
|
ssoExchangeDto.setCode(code == null ? null : code.trim());
|
||||||
ssoExchangeDto.setClientId(clientId == null ? null : clientId.trim());
|
ssoExchangeDto.setClientId(clientId == null ? null : clientId.trim());
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class SsoLoginForm {
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
public SsoLoginDto ssoLoginDto() {
|
public SsoLoginDto toSsoLoginDto() {
|
||||||
SsoLoginDto ssoLoginDto = new SsoLoginDto();
|
SsoLoginDto ssoLoginDto = new SsoLoginDto();
|
||||||
ssoLoginDto.setId(id == null ? null : id.trim());
|
ssoLoginDto.setId(id == null ? null : id.trim());
|
||||||
ssoLoginDto.setPassword(password == null ? null : password.trim());
|
ssoLoginDto.setPassword(password == null ? null : password.trim());
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,7 @@ public class TokenForm {
|
|||||||
@Schema(description = "사용자명", example = "test")
|
@Schema(description = "사용자명", example = "test")
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
public TokenDto tokenDto() {
|
public TokenDto toTokenDto() {
|
||||||
TokenDto tokenDto = new TokenDto();
|
TokenDto tokenDto = new TokenDto();
|
||||||
tokenDto.setId(this.id.trim());
|
tokenDto.setId(this.id.trim());
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
+3
-3
@@ -1,15 +1,15 @@
|
|||||||
package com.alist.api.modules.auth.mapper;
|
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 org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface TestCorsMapper {
|
public interface CorsMapper {
|
||||||
/**
|
/**
|
||||||
* 전체 CORS 허용 도메인 목록 조회 (user_token_idx별로)
|
* 전체 CORS 허용 도메인 목록 조회 (user_token_idx별로)
|
||||||
* @return CorsOriginDto 리스트
|
* @return CorsOriginDto 리스트
|
||||||
*/
|
*/
|
||||||
List<TestCorsOriginVo> selectTestCorsAllowedList();
|
List<CorsOriginVo> selectCorsAllowedList();
|
||||||
}
|
}
|
||||||
@@ -5,5 +5,5 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface SsoMapper {
|
public interface SsoMapper {
|
||||||
SsoClientVo selectSsoClient(String clientId, String redirectUri);
|
SsoClientVo selectSsoClient(String clientId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.SsoExchangeDto;
|
||||||
import com.alist.api.modules.auth.dto.SsoLoginDto;
|
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.mapper.SsoMapper;
|
||||||
import com.alist.api.modules.auth.vo.SsoClientVo;
|
import com.alist.api.modules.auth.vo.SsoClientVo;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
@@ -18,34 +18,32 @@ import java.util.UUID;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class SsoService {
|
public class SsoService {
|
||||||
private final TestAuthService testAuthService;
|
private final AuthService authService;
|
||||||
|
|
||||||
private final SsoMapper ssoMapper;
|
private final SsoMapper ssoMapper;
|
||||||
private final StringRedisTemplate redisTemplate;
|
private final StringRedisTemplate redisTemplate;
|
||||||
|
|
||||||
private final Duration ssoSessionTtl;
|
private final Duration ssoSessionTtl;
|
||||||
private final Duration ssoCodeTtl;
|
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) {
|
public SsoService(AuthService authService, SsoMapper ssoMapper, StringRedisTemplate redisTemplate, @Value("${sso.session.ttl-seconds}") long ssoSessionTtlSeconds, @Value("${sso.code.ttl-seconds}") long ssoCodeTtlSeconds) {
|
||||||
this.testAuthService = testAuthService;
|
this.authService = authService;
|
||||||
this.ssoMapper = ssoMapper;
|
this.ssoMapper = ssoMapper;
|
||||||
this.redisTemplate = redisTemplate;
|
this.redisTemplate = redisTemplate;
|
||||||
this.ssoSessionTtl = Duration.ofSeconds(ssoSessionTtlSeconds);
|
this.ssoSessionTtl = Duration.ofSeconds(ssoSessionTtlSeconds);
|
||||||
this.ssoCodeTtl = Duration.ofSeconds(ssoCodeTtlSeconds);
|
this.ssoCodeTtl = Duration.ofSeconds(ssoCodeTtlSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SsoLoginDto login(SsoLoginDto loginDto, HttpServletRequest request) {
|
public SsoLoginDto login(SsoLoginDto ssoLoginDto, HttpServletRequest request) {
|
||||||
TestLoginDto testLoginDto = testAuthService.selectTestLogin(loginDto.testLoginDto());
|
LoginDto loginDto = authService.selectLogin(ssoLoginDto.toLoginDto());
|
||||||
|
|
||||||
SsoLoginDto result = new SsoLoginDto();
|
SsoLoginDto result = new SsoLoginDto();
|
||||||
result.setResultCode(testLoginDto.getResultCode());
|
result.setResultCode(loginDto.getResultCode());
|
||||||
|
|
||||||
if (testLoginDto.getResultCode() == 2003) {
|
if (loginDto.getResultCode() == 2003) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
String ssoSessionId = "SSO_" + UUID.randomUUID();
|
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 sessionKey = "alist:sso:session:" + ssoSessionId;
|
||||||
|
|
||||||
String oldSsoSessionId = redisTemplate.opsForValue().get(latestSessionKey);
|
String oldSsoSessionId = redisTemplate.opsForValue().get(latestSessionKey);
|
||||||
@@ -53,17 +51,17 @@ public class SsoService {
|
|||||||
redisTemplate.delete("alist:sso:session:" + oldSsoSessionId);
|
redisTemplate.delete("alist:sso:session:" + oldSsoSessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
redisTemplate.opsForHash().put(sessionKey, "userIdx", String.valueOf(testLoginDto.getUserIdx()));
|
redisTemplate.opsForHash().put(sessionKey, "userIdx", String.valueOf(loginDto.getUserIdx()));
|
||||||
redisTemplate.opsForHash().put(sessionKey, "userTokenIdx", String.valueOf(testLoginDto.getUserTokenIdx()));
|
redisTemplate.opsForHash().put(sessionKey, "userTokenIdx", String.valueOf(loginDto.getUserTokenIdx()));
|
||||||
redisTemplate.opsForHash().put(sessionKey, "userId", testLoginDto.getId());
|
redisTemplate.opsForHash().put(sessionKey, "userId", loginDto.getId());
|
||||||
redisTemplate.opsForHash().put(sessionKey, "userRole", "USER");
|
redisTemplate.opsForHash().put(sessionKey, "userRole", "USER");
|
||||||
redisTemplate.expire(sessionKey, ssoSessionTtl);
|
redisTemplate.expire(sessionKey, ssoSessionTtl);
|
||||||
|
|
||||||
redisTemplate.opsForValue().set(latestSessionKey, ssoSessionId, ssoSessionTtl);
|
redisTemplate.opsForValue().set(latestSessionKey, ssoSessionId, ssoSessionTtl);
|
||||||
|
|
||||||
result.setSsoSessionId(ssoSessionId);
|
result.setSsoSessionId(ssoSessionId);
|
||||||
result.setUserId(testLoginDto.getId());
|
result.setUserId(loginDto.getId());
|
||||||
result.setUserIdx(testLoginDto.getUserIdx());
|
result.setUserIdx(loginDto.getUserIdx());
|
||||||
result.setUserRole("USER");
|
result.setUserRole("USER");
|
||||||
result.setResultCode(200);
|
result.setResultCode(200);
|
||||||
|
|
||||||
@@ -119,7 +117,7 @@ public class SsoService {
|
|||||||
return "/login";
|
return "/login";
|
||||||
}
|
}
|
||||||
|
|
||||||
SsoClientVo ssoClientVo = ssoMapper.selectSsoClient(clientId, redirectUri);
|
SsoClientVo ssoClientVo = ssoMapper.selectSsoClient(clientId);
|
||||||
if (ssoClientVo == null) {
|
if (ssoClientVo == null) {
|
||||||
throw new IllegalArgumentException("허용되지 않은 clientId 또는 redirectUri 입니다.");
|
throw new IllegalArgumentException("허용되지 않은 clientId 또는 redirectUri 입니다.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -5,6 +5,6 @@ import lombok.Setter;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class TestCorsOriginVo {
|
public class CorsOriginVo {
|
||||||
private String allowedOrigin;
|
private String allowedOrigin;
|
||||||
}
|
}
|
||||||
+1
-1
@@ -3,7 +3,7 @@ package com.alist.api.modules.auth.vo;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class TestLoginTokenVo {
|
public class LoginTokenVo {
|
||||||
private Integer userTokenIdx;
|
private Integer userTokenIdx;
|
||||||
private String userApiKey;
|
private String userApiKey;
|
||||||
private String userRole;
|
private String userRole;
|
||||||
+1
-1
@@ -3,7 +3,7 @@ package com.alist.api.modules.auth.vo;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class TestLoginVo {
|
public class LoginVo {
|
||||||
private Integer userIdx;
|
private Integer userIdx;
|
||||||
private String id;
|
private String id;
|
||||||
private String password;
|
private String password;
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
package com.alist.api.modules.file;
|
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.common.utils.SecurityUtil;
|
||||||
import com.alist.api.modules.file.dto.*;
|
import com.alist.api.modules.file.dto.*;
|
||||||
import com.alist.api.modules.file.form.*;
|
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 com.alist.api.modules.file.service.FileService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpSession;
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.core.io.Resource;
|
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 org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -44,7 +46,7 @@ public class FileController {
|
|||||||
public ResponseEntity<ApiResponse<FileUploadDto>> uploadInit(
|
public ResponseEntity<ApiResponse<FileUploadDto>> uploadInit(
|
||||||
@Valid @RequestBody FileUploadForm fileUploadForm
|
@Valid @RequestBody FileUploadForm fileUploadForm
|
||||||
) {
|
) {
|
||||||
FileUploadDto fileUploadDto = fileUploadForm.fileUploadDto();
|
FileUploadDto fileUploadDto = fileUploadForm.toFileUploadDto();
|
||||||
|
|
||||||
Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx();
|
Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx();
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ public class FileController {
|
|||||||
public ResponseEntity<ApiResponse<UploadStatusDto>> uploadStatus(
|
public ResponseEntity<ApiResponse<UploadStatusDto>> uploadStatus(
|
||||||
@Valid @RequestBody UploadStatusForm uploadStatusForm
|
@Valid @RequestBody UploadStatusForm uploadStatusForm
|
||||||
) {
|
) {
|
||||||
UploadStatusDto status = fileService.getUploadStatus(uploadStatusForm.uploadStatusDto());
|
UploadStatusDto status = fileService.getUploadStatus(uploadStatusForm.toUploadStatusDto());
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
return ApiResponse.entity(new UploadStatusDto(), ApiResponseCode.CODE_2003);
|
return ApiResponse.entity(new UploadStatusDto(), ApiResponseCode.CODE_2003);
|
||||||
}
|
}
|
||||||
@@ -122,10 +124,10 @@ public class FileController {
|
|||||||
)
|
)
|
||||||
@PostMapping("/tusHook")
|
@PostMapping("/tusHook")
|
||||||
public ResponseEntity<ApiResponse<String>> tusHook(@RequestBody TusHookForm tusHookForm) {
|
public ResponseEntity<ApiResponse<String>> tusHook(@RequestBody TusHookForm tusHookForm) {
|
||||||
boolean accepted = fileService.updateFileUploadStatus(tusHookForm.tusHookDto());
|
boolean accepted = fileService.updateFileUploadStatus(tusHookForm.toTusHookDto());
|
||||||
|
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
fileService.saveUploadStatusRedisTusHook(tusHookForm.tusHookDto());
|
fileService.saveUploadStatusRedisTusHook(tusHookForm.toTusHookDto());
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiResponse.entity("", ApiResponseCode.CODE_200);
|
return ApiResponse.entity("", ApiResponseCode.CODE_200);
|
||||||
@@ -273,7 +275,7 @@ public class FileController {
|
|||||||
public ResponseEntity<ApiResponse<String>> fileDelete(
|
public ResponseEntity<ApiResponse<String>> fileDelete(
|
||||||
@Valid @RequestBody FileDeleteForm fileDeleteForm
|
@Valid @RequestBody FileDeleteForm fileDeleteForm
|
||||||
) {
|
) {
|
||||||
boolean deleted = fileService.updateFileDelete(fileDeleteForm.fileDeleteDto());
|
boolean deleted = fileService.updateFileDelete(fileDeleteForm.toFileDeleteDto());
|
||||||
|
|
||||||
if (!deleted) {
|
if (!deleted) {
|
||||||
return ApiResponse.entity("", ApiResponseCode.CODE_2005, "파일 삭제");
|
return ApiResponse.entity("", ApiResponseCode.CODE_2005, "파일 삭제");
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class FileDeleteForm {
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
private String fileUuid;
|
private String fileUuid;
|
||||||
|
|
||||||
public FileDeleteDto fileDeleteDto() {
|
public FileDeleteDto toFileDeleteDto() {
|
||||||
FileDeleteDto fileDeleteDto = new FileDeleteDto();
|
FileDeleteDto fileDeleteDto = new FileDeleteDto();
|
||||||
fileDeleteDto.setFileUuid(fileUuid == null ? null : fileUuid.trim());
|
fileDeleteDto.setFileUuid(fileUuid == null ? null : fileUuid.trim());
|
||||||
return fileDeleteDto;
|
return fileDeleteDto;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class FileUploadForm {
|
|||||||
@Valid
|
@Valid
|
||||||
private List<FileUploadItemForm> itemList;
|
private List<FileUploadItemForm> itemList;
|
||||||
|
|
||||||
public FileUploadDto fileUploadDto() {
|
public FileUploadDto toFileUploadDto() {
|
||||||
FileUploadDto fileUploadDto = new FileUploadDto();
|
FileUploadDto fileUploadDto = new FileUploadDto();
|
||||||
|
|
||||||
fileUploadDto.setFileCategory(this.fileCategory.trim());
|
fileUploadDto.setFileCategory(this.fileCategory.trim());
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class TusHookForm {
|
|||||||
@JsonProperty("Event")
|
@JsonProperty("Event")
|
||||||
private TusHookEventForm tusHookEventForm;
|
private TusHookEventForm tusHookEventForm;
|
||||||
|
|
||||||
public TusHookDto tusHookDto() {
|
public TusHookDto toTusHookDto() {
|
||||||
TusHookDto tusHookDto = new TusHookDto();
|
TusHookDto tusHookDto = new TusHookDto();
|
||||||
|
|
||||||
tusHookDto.setType(this.type);
|
tusHookDto.setType(this.type);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class UploadStatusForm {
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
private String fileUuid;
|
private String fileUuid;
|
||||||
|
|
||||||
public UploadStatusDto uploadStatusDto() {
|
public UploadStatusDto toUploadStatusDto() {
|
||||||
UploadStatusDto uploadStatusDto = new UploadStatusDto();
|
UploadStatusDto uploadStatusDto = new UploadStatusDto();
|
||||||
uploadStatusDto.setFileUuid(this.fileUuid == null ? null : this.fileUuid.trim());
|
uploadStatusDto.setFileUuid(this.fileUuid == null ? null : this.fileUuid.trim());
|
||||||
return uploadStatusDto;
|
return uploadStatusDto;
|
||||||
|
|||||||
@@ -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<ApiResponse<TestUserDto>> 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, "아이디");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.alist.api.modules.testUser.vo;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class TestUserVo {
|
|
||||||
private String id;
|
|
||||||
}
|
|
||||||
@@ -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<ApiResponse<UserDto>> 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, "아이디");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -6,7 +6,7 @@ import lombok.Setter;
|
|||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
public class TestUserDto {
|
public class UserDto {
|
||||||
private Integer userIdx;
|
private Integer userIdx;
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
package com.alist.api.modules.testUser.dto;
|
package com.alist.api.modules.user.dto;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
public class TestUserTokenDto {
|
public class UserTokenDto {
|
||||||
private Integer userTokenIdx;
|
private Integer userTokenIdx;
|
||||||
private Integer userIdx;
|
private Integer userIdx;
|
||||||
private String userApiKey;
|
private String userApiKey;
|
||||||
+8
-8
@@ -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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
@@ -11,7 +11,7 @@ import lombok.Setter;
|
|||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
@Schema(description = "회원 가입 요청 폼")
|
@Schema(description = "회원 가입 요청 폼")
|
||||||
public class TestUserSignupForm {
|
public class UserSignupForm {
|
||||||
@Schema(
|
@Schema(
|
||||||
description = "사용자 아이디 (공백 불가)",
|
description = "사용자 아이디 (공백 불가)",
|
||||||
example = "test"
|
example = "test"
|
||||||
@@ -31,10 +31,10 @@ public class TestUserSignupForm {
|
|||||||
)
|
)
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
public TestUserDto testUserDto() {
|
public UserDto toUserDto() {
|
||||||
TestUserDto testUserDto = new TestUserDto();
|
UserDto userDto = new UserDto();
|
||||||
testUserDto.setId(id.trim());
|
userDto.setId(id.trim());
|
||||||
testUserDto.setPassword(password);
|
userDto.setPassword(password);
|
||||||
return testUserDto;
|
return userDto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,19 +11,12 @@ spring:
|
|||||||
minimum-idle: 1 # 최소로
|
minimum-idle: 1 # 최소로
|
||||||
connection-timeout: 10000
|
connection-timeout: 10000
|
||||||
idle-timeout: 30000
|
idle-timeout: 30000
|
||||||
|
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: 121.160.234.222
|
host: 121.160.234.222
|
||||||
port: 3001
|
port: 3001
|
||||||
password: 1qaz2wsx!@
|
password: 1qaz2wsx!@
|
||||||
|
|
||||||
session:
|
|
||||||
store-type: redis
|
|
||||||
timeout: 7200 # 2시간 (초 단위)
|
|
||||||
redis:
|
|
||||||
namespace: spring:session:alist
|
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
config: classpath:logback-local.xml
|
config: classpath:logback-local.xml
|
||||||
|
|
||||||
@@ -33,10 +26,10 @@ jwt:
|
|||||||
refresh-token-validity-seconds: 2592000
|
refresh-token-validity-seconds: 2592000
|
||||||
|
|
||||||
cookie:
|
cookie:
|
||||||
secure: true # 로컬 개발 환경 (HTTP)
|
secure: true # (HTTP) -> false
|
||||||
domain: api-alist.pjt.kr
|
domain: api-alist.pjt.kr # (HTTP) -> 비워두세요
|
||||||
name: ALIST_SSO
|
name: ALIST_SSO
|
||||||
same-site: None
|
same-site: None # (HTTP) -> Lax
|
||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
login:
|
login:
|
||||||
|
|||||||
@@ -13,19 +13,12 @@ spring:
|
|||||||
minimum-idle: 1 # 최소로
|
minimum-idle: 1 # 최소로
|
||||||
connection-timeout: 10000
|
connection-timeout: 10000
|
||||||
idle-timeout: 30000
|
idle-timeout: 30000
|
||||||
|
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: ${REDIS_HOST}
|
host: ${REDIS_HOST}
|
||||||
port: ${REDIS_PORT}
|
port: ${REDIS_PORT}
|
||||||
password: ${REDIS_PASSWORD}
|
password: ${REDIS_PASSWORD}
|
||||||
|
|
||||||
session:
|
|
||||||
store-type: redis
|
|
||||||
timeout: 7200 # 2시간 (초 단위)
|
|
||||||
redis:
|
|
||||||
namespace: spring:session:alist
|
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
config: classpath:logback-pjt.xml
|
config: classpath:logback-pjt.xml
|
||||||
|
|
||||||
@@ -36,7 +29,7 @@ jwt:
|
|||||||
|
|
||||||
cookie:
|
cookie:
|
||||||
secure: true # 프로젝트 환경 (HTTPS)
|
secure: true # 프로젝트 환경 (HTTPS)
|
||||||
domain: api-alist.pjt.kr # sso 인증시 주석해제
|
domain: api-alist.pjt.kr
|
||||||
name: ALIST_SSO
|
name: ALIST_SSO
|
||||||
same-site: None
|
same-site: None
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
|
||||||
|
<mapper namespace="com.alist.api.modules.user.mapper.UserMapper">
|
||||||
|
<insert id="insertUserSignup" useGeneratedKeys="true" keyProperty="userIdx">
|
||||||
|
/*UserMapper.insertUserSignup*/
|
||||||
|
insert into ALISTLMS.test_user (id, password, del_yn, create_at, update_at)
|
||||||
|
value (#{id}, #{newPassword}, 1, now(), now())
|
||||||
|
</insert>
|
||||||
|
<insert id="insertUserTokenSignup" useGeneratedKeys="true" keyProperty="userTokenIdx">
|
||||||
|
/*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())
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="selectDuplicateUserCount" resultType="int">
|
||||||
|
/*UserMapper.selectDuplicateUserCount*/
|
||||||
|
select count(*)
|
||||||
|
from ALISTLMS.test_user
|
||||||
|
where id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
|
||||||
|
<mapper namespace="com.alist.api.modules.auth.mapper.CorsMapper">
|
||||||
|
<select id="selectCorsAllowedList" resultType="com.alist.api.modules.auth.vo.CorsOriginVo">
|
||||||
|
/* CorsMapper.selectCorsAllowedList */
|
||||||
|
SELECT allowed_origin
|
||||||
|
FROM ALISTLMS.test_cors_allowed_list
|
||||||
|
WHERE del_yn = 1
|
||||||
|
ORDER BY cors_idx
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
+20
-20
@@ -1,65 +1,65 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
|
||||||
<mapper namespace="com.alist.api.modules.auth.mapper.TestAuthMapper">
|
<mapper namespace="com.alist.api.modules.auth.mapper.AuthMapper">
|
||||||
<update id="updateRefreshToken">
|
<update id="updateRefreshToken">
|
||||||
/*TestLoginMapper.selectTestLogin*/
|
/*LoginMapper.updateRefreshToken*/
|
||||||
update test_user_token
|
update ALISTLMS.test_user_token
|
||||||
set refresh_token = #{refreshToken}
|
set refresh_token = #{refreshToken}
|
||||||
, expires_at = #{expiresAt}
|
, expires_at = #{expiresAt}
|
||||||
, updated_at = now()
|
, updated_at = now()
|
||||||
where user_token_idx = #{userTokenIdx}
|
where user_token_idx = #{userTokenIdx}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<select id="selectTestLogin" resultType="com.alist.api.modules.auth.vo.TestLoginVo">
|
<select id="selectLogin" resultType="com.alist.api.modules.auth.vo.LoginVo">
|
||||||
/*TestLoginMapper.selectTestLogin*/
|
/*LoginMapper.selectLogin*/
|
||||||
select user_idx
|
select user_idx
|
||||||
, id
|
, id
|
||||||
, password
|
, password
|
||||||
from test_user
|
from ALISTLMS.test_user
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
and del_yn = 1
|
and del_yn = 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectTestUserTokenByUserId" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
|
<select id="selectUserTokenByUserId" resultType="com.alist.api.modules.auth.vo.LoginTokenVo">
|
||||||
/*TestLoginMapper.selectUserTokenByUserId*/
|
/*LoginMapper.selectUserTokenByUserId*/
|
||||||
select user_token_idx
|
select user_token_idx
|
||||||
, user_role
|
, user_role
|
||||||
, refresh_token
|
, refresh_token
|
||||||
from test_user_token
|
from ALISTLMS.test_user_token
|
||||||
where user_idx = #{userIdx}
|
where user_idx = #{userIdx}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectTestUserTokenByUserApiKey" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
|
<select id="selectUserTokenByUserApiKey" resultType="com.alist.api.modules.auth.vo.LoginTokenVo">
|
||||||
/*TestLoginMapper.selectTestUserTokenByUserApiKey*/
|
/*LoginMapper.selectUserTokenByUserApiKey*/
|
||||||
select user_token_idx
|
select user_token_idx
|
||||||
, user_role
|
, user_role
|
||||||
, refresh_token
|
, refresh_token
|
||||||
from test_user_token
|
from ALISTLMS.test_user_token
|
||||||
where user_api_key = #{userApiKey}
|
where user_api_key = #{userApiKey}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectTestUserTokenByUserTokenIdx" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
|
<select id="selectUserTokenByUserTokenIdx" resultType="com.alist.api.modules.auth.vo.LoginTokenVo">
|
||||||
/*TestLoginMapper.selectTestUserTokenByUserTokenIdx*/
|
/*LoginMapper.selectUserTokenByUserTokenIdx*/
|
||||||
select user_token_idx
|
select user_token_idx
|
||||||
, user_role
|
, user_role
|
||||||
, refresh_token
|
, refresh_token
|
||||||
from test_user_token
|
from ALISTLMS.test_user_token
|
||||||
where user_token_idx = #{userTokenIdx}
|
where user_token_idx = #{userTokenIdx}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<update id="clearRefreshTokenByUserTokenIdx">
|
<update id="clearRefreshTokenByUserTokenIdx">
|
||||||
/*TestLoginMapper.clearRefreshTokenByUserTokenIdx*/
|
/*LoginMapper.clearRefreshTokenByUserTokenIdx*/
|
||||||
update test_user_token
|
update ALISTLMS.test_user_token
|
||||||
set refresh_token = null
|
set refresh_token = null
|
||||||
, expires_at = null
|
, expires_at = null
|
||||||
, updated_at = now()
|
, updated_at = now()
|
||||||
where user_token_idx = #{userTokenIdx}
|
where user_token_idx = #{userTokenIdx}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<update id="updateTestLoginByLastAt">
|
<update id="updateLoginByLastAt">
|
||||||
/*TestLoginMapper.updateTestLoginByLastAt*/
|
/*LoginMapper.updateLoginByLastAt*/
|
||||||
update test_user
|
update ALISTLMS.test_user
|
||||||
set last_login_at = now()
|
set last_login_at = now()
|
||||||
, update_at = now()
|
, update_at = now()
|
||||||
where user_idx = #{userIdx}
|
where user_idx = #{userIdx}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
, REDIRECT_URI
|
, REDIRECT_URI
|
||||||
, MEMO
|
, MEMO
|
||||||
, USE_YN
|
, USE_YN
|
||||||
FROM SSO_CLIENT
|
FROM ALISTLMS.SSO_CLIENT
|
||||||
WHERE CLIENT_ID = #{clientId}
|
WHERE CLIENT_ID = #{clientId}
|
||||||
AND USE_YN = 'Y'
|
AND USE_YN = 'Y'
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
|
|
||||||
<mapper namespace="com.alist.api.modules.auth.mapper.TestCorsMapper">
|
|
||||||
<select id="selectTestCorsAllowedList" resultType="com.alist.api.modules.auth.vo.TestCorsOriginVo">
|
|
||||||
/* TestCorsMapper.selectTestCorsAllowedList */
|
|
||||||
SELECT allowed_origin
|
|
||||||
FROM test_cors_allowed_list
|
|
||||||
WHERE del_yn = 1
|
|
||||||
ORDER BY cors_idx
|
|
||||||
</select>
|
|
||||||
</mapper>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
|
|
||||||
<mapper namespace="com.alist.api.modules.testUser.mapper.TestUserMapper">
|
|
||||||
<insert id="insertTestUserSignup" useGeneratedKeys="true" keyProperty="userIdx">
|
|
||||||
/*UserSignupMapper.insertTestUserSignup*/
|
|
||||||
insert into test_user (id, password, del_yn, create_at, update_at)
|
|
||||||
value (#{id}, #{newPassword}, 1, now(), now())
|
|
||||||
</insert>
|
|
||||||
<insert id="insertTestUserTokenSignup" useGeneratedKeys="true" keyProperty="userTokenIdx">
|
|
||||||
/*UserSignupMapper.insertTestUserTokenSignup*/
|
|
||||||
insert into test_user_token (user_idx, user_api_key, user_role, created_at, updated_at)
|
|
||||||
value (#{userIdx}, #{userApiKey}, 'user', now(), now())
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<select id="selectDuplicateTestUserCount" resultType="int">
|
|
||||||
/*UserSignupMapper.selectDuplicateTestUserCount*/
|
|
||||||
select count(*)
|
|
||||||
from test_user
|
|
||||||
where id = #{id}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
</mapper>
|
|
||||||
Reference in New Issue
Block a user