[API] SSO 다중 도메인 로그인 작업 커밋

This commit is contained in:
2026-03-31 17:46:33 +09:00
parent 07683e843d
commit 9da2585f29
50 changed files with 466 additions and 722 deletions
@@ -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())
)
.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);
@@ -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<String> allowedOrigins = new ArrayList<>();
@@ -35,9 +35,9 @@ public class CorsAllowedOriginsCache {
*/
public synchronized void refresh() {
try {
List<String> origins = testCorsMapper.selectTestCorsAllowedList()
List<String> origins = corsMapper.selectCorsAllowedList()
.stream()
.map(TestCorsOriginVo::getAllowedOrigin)
.map(CorsOriginVo::getAllowedOrigin)
.distinct()
.collect(Collectors.toList());
@@ -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<ApiResponse<TokenDto>> 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<ApiResponse<Map<String, Object>>> loginChecked(HttpServletRequest request) {
Map<String, Object> sessionInfo = new HashMap<>();
HttpSession session = request.getSession(false); // 새 세션 생성 금지
@PostMapping("/apiKeyLogin")
public ResponseEntity<ApiResponse<LoginDto>> 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<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);
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "엑세스 토큰");
}
}
@@ -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<ApiResponse<List<String>>> reloadCors() {
corsCache.refresh();
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.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);
@@ -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, "엑세스 토큰");
}
}
@@ -9,7 +9,7 @@ import java.time.Instant;
@Getter
@Setter
public class TestLoginDto {
public class LoginDto {
@Schema(description = "아이디")
private String id;
@Schema(description = "엑세스 토큰")
@@ -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;
}
}
@@ -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;
}
}
@@ -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());
@@ -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());
@@ -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")
private String id;
public TokenDto tokenDto() {
public TokenDto toTokenDto() {
TokenDto tokenDto = new TokenDto();
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);
}
@@ -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<TestCorsOriginVo> selectTestCorsAllowedList();
List<CorsOriginVo> selectCorsAllowedList();
}
@@ -5,5 +5,5 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
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.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 입니다.");
}
@@ -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);
}
}
@@ -5,6 +5,6 @@ import lombok.Setter;
@Getter
@Setter
public class TestCorsOriginVo {
public class CorsOriginVo {
private String allowedOrigin;
}
@@ -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;
@@ -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;
@@ -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<ApiResponse<FileUploadDto>> 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<ApiResponse<UploadStatusDto>> 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<ApiResponse<String>> 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<ApiResponse<String>> 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, "파일 삭제");
@@ -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;
@@ -23,7 +23,7 @@ public class FileUploadForm {
@Valid
private List<FileUploadItemForm> itemList;
public FileUploadDto fileUploadDto() {
public FileUploadDto toFileUploadDto() {
FileUploadDto fileUploadDto = new FileUploadDto();
fileUploadDto.setFileCategory(this.fileCategory.trim());
@@ -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);
@@ -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;
@@ -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, "아이디");
}
}
@@ -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;
@@ -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;
@@ -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;
}
}
@@ -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.");
}
}
+3 -10
View File
@@ -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:
+1 -8
View File
@@ -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
@@ -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>
@@ -1,65 +1,65 @@
<?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.TestAuthMapper">
<mapper namespace="com.alist.api.modules.auth.mapper.AuthMapper">
<update id="updateRefreshToken">
/*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}
</update>
<select id="selectTestLogin" resultType="com.alist.api.modules.auth.vo.TestLoginVo">
/*TestLoginMapper.selectTestLogin*/
<select id="selectLogin" resultType="com.alist.api.modules.auth.vo.LoginVo">
/*LoginMapper.selectLogin*/
select user_idx
, id
, password
from test_user
from ALISTLMS.test_user
where id = #{id}
and del_yn = 1
</select>
<select id="selectTestUserTokenByUserId" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
/*TestLoginMapper.selectUserTokenByUserId*/
<select id="selectUserTokenByUserId" resultType="com.alist.api.modules.auth.vo.LoginTokenVo">
/*LoginMapper.selectUserTokenByUserId*/
select user_token_idx
, user_role
, refresh_token
from test_user_token
from ALISTLMS.test_user_token
where user_idx = #{userIdx}
</select>
<select id="selectTestUserTokenByUserApiKey" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
/*TestLoginMapper.selectTestUserTokenByUserApiKey*/
<select id="selectUserTokenByUserApiKey" resultType="com.alist.api.modules.auth.vo.LoginTokenVo">
/*LoginMapper.selectUserTokenByUserApiKey*/
select user_token_idx
, user_role
, refresh_token
from test_user_token
from ALISTLMS.test_user_token
where user_api_key = #{userApiKey}
</select>
<select id="selectTestUserTokenByUserTokenIdx" resultType="com.alist.api.modules.auth.vo.TestLoginTokenVo">
/*TestLoginMapper.selectTestUserTokenByUserTokenIdx*/
<select id="selectUserTokenByUserTokenIdx" resultType="com.alist.api.modules.auth.vo.LoginTokenVo">
/*LoginMapper.selectUserTokenByUserTokenIdx*/
select user_token_idx
, user_role
, refresh_token
from test_user_token
from ALISTLMS.test_user_token
where user_token_idx = #{userTokenIdx}
</select>
<update id="clearRefreshTokenByUserTokenIdx">
/*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}
</update>
<update id="updateTestLoginByLastAt">
/*TestLoginMapper.updateTestLoginByLastAt*/
update test_user
<update id="updateLoginByLastAt">
/*LoginMapper.updateLoginByLastAt*/
update ALISTLMS.test_user
set last_login_at = now()
, update_at = now()
where user_idx = #{userIdx}
+1 -1
View File
@@ -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
@@ -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>