admin/front 분리
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
FROM amazoncorretto:21
|
||||
|
||||
RUN yum -y install curl && yum clean all
|
||||
|
||||
WORKDIR /api
|
||||
|
||||
COPY build/libs/api-front.jar api-front.jar
|
||||
|
||||
ENV JAVA_OPTS=""
|
||||
|
||||
EXPOSE 8112
|
||||
|
||||
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /api/api-front.jar"]
|
||||
@@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':01-api-core')
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.0'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
|
||||
bootJar {
|
||||
archiveFileName = 'api-front.jar'
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.alist.api.front;
|
||||
|
||||
import com.alist.api.core.config.ApiCoreConfig;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Import(ApiCoreConfig.class)
|
||||
@SpringBootApplication
|
||||
public class ApiFrontApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ApiFrontApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.alist.api.front.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
private static final String SECURITY_SCHEME_NAME = "bearerAuth";
|
||||
|
||||
@Value("${api.version}")
|
||||
private String apiVersion;
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("A*List Front API")
|
||||
.version(apiVersion)
|
||||
.description("A*List Front API 입니다.!"))
|
||||
.addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes(SECURITY_SCHEME_NAME,
|
||||
new SecurityScheme()
|
||||
.name("Authorization")
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.alist.api.front.config;
|
||||
|
||||
import com.alist.api.core.common.jwt.JwtAccessDeniedHandler;
|
||||
import com.alist.api.core.common.jwt.JwtAuthenticationEntryPoint;
|
||||
import com.alist.api.core.common.jwt.JwtAuthenticationFilter;
|
||||
import com.alist.api.core.common.jwt.JwtTokenProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
@Value("${swagger.login.id}")
|
||||
private String swaggerLoginId;
|
||||
|
||||
@Value("${swagger.login.password}")
|
||||
private String swaggerLoginPassword;
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationFilter jwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
|
||||
return new JwtAuthenticationFilter(
|
||||
jwtTokenProvider,
|
||||
"USER",
|
||||
"accessToken"
|
||||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain swaggerFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.securityMatcher("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html")
|
||||
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.csrf(csrf -> csrf.disable());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain apiFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
|
||||
http
|
||||
.cors(Customizer.withDefaults())
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.exceptionHandling(ex -> ex
|
||||
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
|
||||
.accessDeniedHandler(new JwtAccessDeniedHandler())
|
||||
)
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(
|
||||
"/"
|
||||
, "/actuator/health"
|
||||
, "/sso/**"
|
||||
, "/auth/**"
|
||||
, "/user/add"
|
||||
, "/user/migration/list"
|
||||
, "/file/**"
|
||||
).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() {
|
||||
UserDetails alist = User.builder()
|
||||
.username(swaggerLoginId)
|
||||
.password(passwordEncoder().encode(swaggerLoginPassword))
|
||||
.roles("ADMIN")
|
||||
.build();
|
||||
|
||||
return new InMemoryUserDetailsManager(alist);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.alist.api.front.modules.auth;
|
||||
|
||||
import com.alist.api.core.common.response.ApiResponse;
|
||||
import com.alist.api.core.common.response.ApiResponseCode;
|
||||
import com.alist.api.front.modules.auth.form.AuthApiKeyLoginForm;
|
||||
import com.alist.api.front.modules.auth.form.AuthTokenForm;
|
||||
import com.alist.api.front.modules.auth.service.AuthService;
|
||||
import com.alist.api.front.modules.auth.vo.AuthAccessVo;
|
||||
import com.alist.api.front.modules.auth.vo.AuthApiKeyLoginVo;
|
||||
import com.alist.api.front.modules.auth.vo.AuthRefreshVo;
|
||||
import com.alist.api.front.modules.auth.vo.AuthTokenVo;
|
||||
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.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.CookieValue;
|
||||
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 = "A-2. 엑세스 토큰 발급", description = "SSO 기반 Access Token 발급 및 갱신 API")
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
private final AuthService authService;
|
||||
|
||||
public AuthController(AuthService authService) {
|
||||
this.authService = authService;
|
||||
}
|
||||
|
||||
@Operation(summary = "SSO 기반 토큰 발급", description = "유효한 SSO 세션을 확인한 뒤 Access Token과 Refresh Token 쿠키를 발급합니다.")
|
||||
@PostMapping("/access")
|
||||
public ResponseEntity<ApiResponse<AuthAccessVo>> authAccess(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
AuthAccessVo authAccessVo = authService.selectAuthAccess(request, response);
|
||||
|
||||
if (authAccessVo.getResultCode() == 401) {
|
||||
return ApiResponse.entity(authAccessVo, ApiResponseCode.CODE_401);
|
||||
}
|
||||
|
||||
return ApiResponse.entity(authAccessVo, ApiResponseCode.CODE_2001, "Access Token");
|
||||
}
|
||||
|
||||
@Operation(summary = "외부 API Key 로그인", description = "외부 연동용으로 Access Token과 Refresh Token을 JSON으로 반환합니다.")
|
||||
@PostMapping("/apiKeyLogin")
|
||||
public ResponseEntity<ApiResponse<AuthApiKeyLoginVo>> authApiKeyLogin(
|
||||
@Valid @RequestBody AuthApiKeyLoginForm authApiKeyLoginForm
|
||||
) {
|
||||
AuthApiKeyLoginVo authApiKeyLoginVo = authService.selectAuthApiKeyLogin(authApiKeyLoginForm);
|
||||
|
||||
if (authApiKeyLoginVo.getResultCode() == 2003) {
|
||||
return ApiResponse.entity(authApiKeyLoginVo, ApiResponseCode.CODE_2003);
|
||||
}
|
||||
|
||||
return ApiResponse.entity(authApiKeyLoginVo, ApiResponseCode.CODE_2001, "Access Token");
|
||||
}
|
||||
|
||||
@Operation(summary = "Access Token 재발급", description = "유효한 Refresh Token 쿠키를 검증하고 새 Access Token과 Refresh Token 쿠키를 발급합니다.")
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<ApiResponse<AuthRefreshVo>> authRefresh(
|
||||
@CookieValue(name = "refreshToken", required = false) String refreshToken,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
AuthRefreshVo authRefreshVo = authService.selectAuthRefresh(refreshToken, response);
|
||||
|
||||
if (authRefreshVo.getResultCode() == 401) {
|
||||
return ApiResponse.entity(authRefreshVo, ApiResponseCode.CODE_401);
|
||||
}
|
||||
|
||||
return ApiResponse.entity(authRefreshVo, ApiResponseCode.CODE_200);
|
||||
}
|
||||
|
||||
@Operation(summary = "테스트 Access Token 발급", description = "개발 테스트 전용 Access Token을 반환합니다. 실서비스 인증에는 사용하지 않습니다.")
|
||||
@PostMapping("/token")
|
||||
public ResponseEntity<ApiResponse<AuthTokenVo>> authToken(
|
||||
@Valid @RequestBody AuthTokenForm authTokenForm
|
||||
) {
|
||||
AuthTokenVo authTokenVo = authService.selectAuthToken(authTokenForm);
|
||||
|
||||
return ApiResponse.entity(authTokenVo, ApiResponseCode.CODE_2001, "테스트 Access Token");
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.alist.api.front.modules.auth.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "외부 API Key 로그인 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthApiKeyLoginForm {
|
||||
@Schema(description = "발급된 사용자 API Key", example = "YjeWJr9-N-XaJ7ix2M2spnfjOyG9nTmRlY9t6sKw4_M", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "API Key는 필수입니다.")
|
||||
private String userApiKey;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.alist.api.front.modules.auth.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "테스트 Access Token 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthTokenForm {
|
||||
@Schema(description = "사용자명", example = "test", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "사용자명은 필수입니다.")
|
||||
private String id;
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package com.alist.api.front.modules.auth.service;
|
||||
|
||||
import com.alist.api.core.common.jwt.JwtTokenProvider;
|
||||
import com.alist.api.core.common.utils.SessionUtil;
|
||||
import com.alist.api.core.modules.bespoke.testUserToken.dto.TestUserTokenApiKeyLoginViewDto;
|
||||
import com.alist.api.core.modules.bespoke.testUserToken.dto.TestUserTokenRefreshViewDto;
|
||||
import com.alist.api.core.modules.bespoke.testUserToken.service.TestUserTokenAuthService;
|
||||
import com.alist.api.core.modules.bespoke.testUserToken.vo.TestUserTokenApiKeyLoginViewVo;
|
||||
import com.alist.api.core.modules.bespoke.testUserToken.vo.TestUserTokenRefreshViewVo;
|
||||
import com.alist.api.core.modules.standard.testUser.dto.TestUserModifyDto;
|
||||
import com.alist.api.core.modules.standard.testUser.service.TestUserService;
|
||||
import com.alist.api.core.modules.standard.testUserToken.dto.TestUserTokenModifyDto;
|
||||
import com.alist.api.core.modules.standard.testUserToken.service.TestUserTokenService;
|
||||
import com.alist.api.front.modules.auth.form.AuthApiKeyLoginForm;
|
||||
import com.alist.api.front.modules.auth.form.AuthTokenForm;
|
||||
import com.alist.api.front.modules.auth.vo.AuthAccessVo;
|
||||
import com.alist.api.front.modules.auth.vo.AuthApiKeyLoginVo;
|
||||
import com.alist.api.front.modules.auth.vo.AuthRefreshVo;
|
||||
import com.alist.api.front.modules.auth.vo.AuthTokenVo;
|
||||
import com.alist.api.front.modules.sso.service.SsoService;
|
||||
import com.alist.api.front.modules.sso.vo.SsoLoginCheckedVo;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class AuthService {
|
||||
private static final List<String> FRONT_USER_TYPES = List.of("T", "S");
|
||||
private static final String USER_SCOPE = "USER";
|
||||
private static final String REFRESH_TOKEN_TYPE = "REFRESH";
|
||||
private static final String ACCESS_TOKEN_COOKIE_NAME = "accessToken";
|
||||
private static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken";
|
||||
|
||||
@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;
|
||||
|
||||
private final SsoService ssoService;
|
||||
private final TestUserTokenAuthService testUserTokenAuthService;
|
||||
private final TestUserTokenService testUserTokenService;
|
||||
private final TestUserService testUserService;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
public AuthService(
|
||||
SsoService ssoService,
|
||||
TestUserTokenAuthService testUserTokenAuthService,
|
||||
TestUserTokenService testUserTokenService,
|
||||
TestUserService testUserService,
|
||||
JwtTokenProvider jwtTokenProvider
|
||||
) {
|
||||
this.ssoService = ssoService;
|
||||
this.testUserTokenAuthService = testUserTokenAuthService;
|
||||
this.testUserTokenService = testUserTokenService;
|
||||
this.testUserService = testUserService;
|
||||
this.jwtTokenProvider = jwtTokenProvider;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AuthAccessVo selectAuthAccess(HttpServletRequest request, HttpServletResponse response) {
|
||||
SsoLoginCheckedVo ssoLoginCheckedVo = ssoService.selectSsoLoginChecked(request, response);
|
||||
|
||||
if (!ssoLoginCheckedVo.isLoggedIn() || ssoLoginCheckedVo.getUserTokenIdx() == null) {
|
||||
AuthAccessVo authAccessVo = new AuthAccessVo();
|
||||
authAccessVo.setAccessToken(false);
|
||||
authAccessVo.setResultCode(401);
|
||||
|
||||
return authAccessVo;
|
||||
}
|
||||
|
||||
issueTokenCookie(ssoLoginCheckedVo.getUserTokenIdx(), ssoLoginCheckedVo.getUserRole(), response);
|
||||
updateLastLoginAt(ssoLoginCheckedVo.getUserIdx());
|
||||
|
||||
AuthAccessVo authAccessVo = new AuthAccessVo();
|
||||
authAccessVo.setAccessToken(true);
|
||||
authAccessVo.setUserIdx(ssoLoginCheckedVo.getUserIdx());
|
||||
authAccessVo.setUserId(ssoLoginCheckedVo.getUserId());
|
||||
authAccessVo.setUserRole(ssoLoginCheckedVo.getUserRole());
|
||||
authAccessVo.setUserType(ssoLoginCheckedVo.getUserType());
|
||||
authAccessVo.setResultCode(2001);
|
||||
|
||||
return authAccessVo;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AuthApiKeyLoginVo selectAuthApiKeyLogin(AuthApiKeyLoginForm authApiKeyLoginForm) {
|
||||
TestUserTokenApiKeyLoginViewDto testUserTokenApiKeyLoginViewDto = new TestUserTokenApiKeyLoginViewDto();
|
||||
testUserTokenApiKeyLoginViewDto.setUserApiKey(authApiKeyLoginForm.getUserApiKey().trim());
|
||||
testUserTokenApiKeyLoginViewDto.setUserTypes(FRONT_USER_TYPES);
|
||||
|
||||
TestUserTokenApiKeyLoginViewVo testUserTokenApiKeyLoginViewVo = testUserTokenAuthService.testUserTokenApiKeyLoginView(testUserTokenApiKeyLoginViewDto);
|
||||
|
||||
if (testUserTokenApiKeyLoginViewVo == null) {
|
||||
AuthApiKeyLoginVo authApiKeyLoginVo = new AuthApiKeyLoginVo();
|
||||
authApiKeyLoginVo.setResultCode(2003);
|
||||
|
||||
return authApiKeyLoginVo;
|
||||
}
|
||||
|
||||
String accessToken = jwtTokenProvider.createAccessToken(testUserTokenApiKeyLoginViewVo.getUserTokenIdx(), testUserTokenApiKeyLoginViewVo.getUserRole());
|
||||
String refreshToken = jwtTokenProvider.createRefreshToken(testUserTokenApiKeyLoginViewVo.getUserTokenIdx());
|
||||
|
||||
updateRefreshToken(testUserTokenApiKeyLoginViewVo.getUserTokenIdx(), refreshToken);
|
||||
updateLastLoginAt(testUserTokenApiKeyLoginViewVo.getUserIdx());
|
||||
|
||||
AuthApiKeyLoginVo authApiKeyLoginVo = new AuthApiKeyLoginVo();
|
||||
authApiKeyLoginVo.setId(testUserTokenApiKeyLoginViewVo.getId());
|
||||
authApiKeyLoginVo.setAccessToken(accessToken);
|
||||
authApiKeyLoginVo.setRefreshToken(refreshToken);
|
||||
authApiKeyLoginVo.setResultCode(2001);
|
||||
|
||||
return authApiKeyLoginVo;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AuthRefreshVo selectAuthRefresh(String refreshToken, HttpServletResponse response) {
|
||||
AuthRefreshVo authRefreshVo = new AuthRefreshVo();
|
||||
|
||||
if (refreshToken == null
|
||||
|| !jwtTokenProvider.validateToken(refreshToken)
|
||||
|| !USER_SCOPE.equals(jwtTokenProvider.getScope(refreshToken))
|
||||
|| !REFRESH_TOKEN_TYPE.equals(jwtTokenProvider.getTokenType(refreshToken))) {
|
||||
authRefreshVo.setRefreshed(false);
|
||||
authRefreshVo.setResultCode(401);
|
||||
|
||||
return authRefreshVo;
|
||||
}
|
||||
|
||||
Integer userTokenIdx;
|
||||
try {
|
||||
userTokenIdx = Integer.valueOf(jwtTokenProvider.getUserTokenIdx(refreshToken));
|
||||
} catch (NumberFormatException e) {
|
||||
authRefreshVo.setRefreshed(false);
|
||||
authRefreshVo.setResultCode(401);
|
||||
|
||||
return authRefreshVo;
|
||||
}
|
||||
|
||||
TestUserTokenRefreshViewDto testUserTokenRefreshViewDto = new TestUserTokenRefreshViewDto();
|
||||
testUserTokenRefreshViewDto.setUserTokenIdx(userTokenIdx);
|
||||
testUserTokenRefreshViewDto.setUserTypes(FRONT_USER_TYPES);
|
||||
|
||||
TestUserTokenRefreshViewVo testUserTokenRefreshViewVo = testUserTokenAuthService.testUserTokenRefreshView(testUserTokenRefreshViewDto);
|
||||
if (testUserTokenRefreshViewVo == null || !refreshToken.equals(testUserTokenRefreshViewVo.getRefreshToken())) {
|
||||
authRefreshVo.setRefreshed(false);
|
||||
authRefreshVo.setResultCode(401);
|
||||
|
||||
return authRefreshVo;
|
||||
}
|
||||
|
||||
issueTokenCookie(testUserTokenRefreshViewVo.getUserTokenIdx(), testUserTokenRefreshViewVo.getUserRole(), response);
|
||||
|
||||
authRefreshVo.setRefreshed(true);
|
||||
authRefreshVo.setResultCode(200);
|
||||
|
||||
return authRefreshVo;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public AuthTokenVo selectAuthToken(AuthTokenForm authTokenForm) {
|
||||
AuthTokenVo authTokenVo = new AuthTokenVo();
|
||||
authTokenVo.setAccessToken(jwtTokenProvider.createToken(authTokenForm.getId().trim()));
|
||||
|
||||
return authTokenVo;
|
||||
}
|
||||
|
||||
private void issueTokenCookie(Integer userTokenIdx, String userRole, HttpServletResponse response) {
|
||||
String accessToken = jwtTokenProvider.createAccessToken(userTokenIdx, userRole);
|
||||
String refreshToken = jwtTokenProvider.createRefreshToken(userTokenIdx);
|
||||
|
||||
updateRefreshToken(userTokenIdx, refreshToken);
|
||||
|
||||
SessionUtil.addTokenCookie(response, ACCESS_TOKEN_COOKIE_NAME, accessToken, cookieDomain, cookieSecure, cookieSameSite, accessTokenValiditySeconds);
|
||||
SessionUtil.addTokenCookie(response, REFRESH_TOKEN_COOKIE_NAME, refreshToken, cookieDomain, cookieSecure, cookieSameSite, refreshTokenValiditySeconds);
|
||||
}
|
||||
|
||||
private void updateRefreshToken(Integer userTokenIdx, String refreshToken) {
|
||||
TestUserTokenModifyDto testUserTokenModifyDto = new TestUserTokenModifyDto();
|
||||
testUserTokenModifyDto.setUserTokenIdx(userTokenIdx);
|
||||
testUserTokenModifyDto.setRefreshToken(refreshToken);
|
||||
testUserTokenModifyDto.setExpiresAt(LocalDateTime.now().plusSeconds(refreshTokenValiditySeconds));
|
||||
|
||||
testUserTokenService.updateTestUserTokenModify(testUserTokenModifyDto);
|
||||
}
|
||||
|
||||
private void updateLastLoginAt(Integer userIdx) {
|
||||
TestUserModifyDto testUserModifyDto = new TestUserModifyDto();
|
||||
testUserModifyDto.setUserIdx(userIdx);
|
||||
testUserModifyDto.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
testUserService.updateTestUserModify(testUserModifyDto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.alist.api.front.modules.auth.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "SSO 기반 토큰 발급 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthAccessVo {
|
||||
@JsonProperty("isAccessToken")
|
||||
@Schema(description = "Access Token 발급 여부", example = "true")
|
||||
private boolean accessToken;
|
||||
|
||||
@Schema(description = "회원 PK", example = "1")
|
||||
private Integer userIdx;
|
||||
|
||||
@Schema(description = "회원 아이디", example = "teacher01")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "회원 권한", example = "TEACHER")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "회원 유형", example = "T")
|
||||
private String userType;
|
||||
|
||||
@JsonIgnore
|
||||
@Schema(hidden = true)
|
||||
private int resultCode;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.alist.api.front.modules.auth.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "외부 API Key 로그인 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthApiKeyLoginVo {
|
||||
@Schema(description = "회원 아이디", example = "teacher01")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "외부 API용 Access Token")
|
||||
private String accessToken;
|
||||
|
||||
@Schema(description = "외부 API용 Refresh Token")
|
||||
private String refreshToken;
|
||||
|
||||
@JsonIgnore
|
||||
@Schema(hidden = true)
|
||||
private int resultCode;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.alist.api.front.modules.auth.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "Access Token 재발급 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthRefreshVo {
|
||||
@Schema(description = "토큰 재발급 여부", example = "true")
|
||||
private boolean refreshed;
|
||||
|
||||
@JsonIgnore
|
||||
@Schema(hidden = true)
|
||||
private int resultCode;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.alist.api.front.modules.auth.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "테스트 Access Token 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthTokenVo {
|
||||
@Schema(description = "테스트용 Access Token")
|
||||
private String accessToken;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.alist.api.front.modules.file;
|
||||
|
||||
import com.alist.api.core.common.response.ApiResponse;
|
||||
import com.alist.api.core.common.response.ApiResponseCode;
|
||||
import com.alist.api.front.modules.file.form.SunEditorUploadForm;
|
||||
import com.alist.api.front.modules.file.service.FileService;
|
||||
import com.alist.api.front.modules.file.vo.FileUploadVo;
|
||||
import com.alist.api.front.modules.file.vo.SunEditorUploadVo;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
|
||||
@Tag(name = "D-1. 파일 업로드", description = "공통 파일 저장, 보기, 다운로드 API")
|
||||
@RestController
|
||||
@RequestMapping("/file")
|
||||
public class FileController {
|
||||
private final FileService fileService;
|
||||
|
||||
public FileController(FileService fileService) {
|
||||
this.fileService = fileService;
|
||||
}
|
||||
|
||||
@Operation(summary = "단순 파일 업로드", description = "DB 기록 없이 파일을 저장하고 업로드 경로를 반환합니다.")
|
||||
@PostMapping(
|
||||
value = "/upload"
|
||||
, consumes = MediaType.MULTIPART_FORM_DATA_VALUE
|
||||
)
|
||||
public ResponseEntity<ApiResponse<FileUploadVo>> fileUpload(
|
||||
@Parameter(description = "저장 폴더 키", example = "notice", required = true) @RequestParam String folder
|
||||
, @Parameter(description = "업로드 파일", required = true) @RequestPart("file") MultipartFile multipartFile
|
||||
) {
|
||||
FileUploadVo fileUploadVo = fileService.insertFileUpload(folder, multipartFile);
|
||||
|
||||
return ApiResponse.entity(fileUploadVo, ApiResponseCode.CODE_200, "파일 업로드");
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "SunEditor 이미지 업로드"
|
||||
, description = "여러 이미지를 저장하고 SunEditor result 배열 형식으로 공개 파일 URL을 반환합니다."
|
||||
, requestBody = @RequestBody(content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE, schema = @Schema(implementation = SunEditorUploadForm.class)))
|
||||
)
|
||||
@PostMapping(
|
||||
value = "/suneditor/upload"
|
||||
, consumes = MediaType.MULTIPART_FORM_DATA_VALUE
|
||||
)
|
||||
public ResponseEntity<SunEditorUploadVo> fileSunEditorUpload(
|
||||
@Parameter(description = "저장 폴더 키", example = "editor") @RequestParam(value = "folder", defaultValue = "editor") String folder
|
||||
, MultipartHttpServletRequest multipartHttpServletRequest
|
||||
) {
|
||||
return ResponseEntity.ok(fileService.insertFileSunEditorUpload(folder, multipartHttpServletRequest));
|
||||
}
|
||||
|
||||
@Operation(summary = "파일 보기", description = "uploadPath 기준으로 파일을 inline 응답합니다.")
|
||||
@GetMapping("/view")
|
||||
public ResponseEntity<Resource> fileView(@Parameter(description = "업로드 파일 경로", example = "/uploads/notice/2026/07/20/a1b2c3d4.png", required = true) @RequestParam String path) {
|
||||
return fileService.selectFileView(path);
|
||||
}
|
||||
|
||||
@Operation(summary = "파일 다운로드", description = "uploadPath 기준으로 파일을 attachment 응답합니다.")
|
||||
@GetMapping("/download")
|
||||
public ResponseEntity<Resource> fileDownload(@Parameter(description = "업로드 파일 경로", example = "/uploads/notice/2026/07/20/a1b2c3d4.png", required = true) @RequestParam String path) {
|
||||
return fileService.selectFileDownload(path);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.alist.api.front.modules.file.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "SunEditor 이미지 업로드 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
public class SunEditorUploadForm {
|
||||
@ArraySchema(schema = @Schema(type = "string", format = "binary", description = "업로드 이미지 파일"))
|
||||
private List<MultipartFile> files;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.alist.api.front.modules.file.service;
|
||||
|
||||
import com.alist.api.core.common.file.service.FileStorageService;
|
||||
import com.alist.api.core.common.file.vo.FileStorageUploadVo;
|
||||
import com.alist.api.front.modules.file.vo.FileUploadVo;
|
||||
import com.alist.api.front.modules.file.vo.SunEditorUploadVo;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class FileService {
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
public FileService(FileStorageService fileStorageService) {
|
||||
this.fileStorageService = fileStorageService;
|
||||
}
|
||||
|
||||
public FileUploadVo insertFileUpload(String folder, MultipartFile multipartFile) {
|
||||
FileStorageUploadVo fileStorageUploadVo = fileStorageService.saveFileUpload(folder, multipartFile);
|
||||
FileUploadVo fileUploadVo = new FileUploadVo();
|
||||
fileUploadVo.setUploadPath(fileStorageUploadVo.getUploadPath());
|
||||
fileUploadVo.setOriginalFileName(fileStorageUploadVo.getOriginalFileName());
|
||||
fileUploadVo.setStoredFileName(fileStorageUploadVo.getStoredFileName());
|
||||
fileUploadVo.setFileExtension(fileStorageUploadVo.getFileExtension());
|
||||
fileUploadVo.setContentType(fileStorageUploadVo.getContentType());
|
||||
fileUploadVo.setFileSize(fileStorageUploadVo.getFileSize());
|
||||
fileUploadVo.setWidth(fileStorageUploadVo.getWidth());
|
||||
fileUploadVo.setHeight(fileStorageUploadVo.getHeight());
|
||||
|
||||
return fileUploadVo;
|
||||
}
|
||||
|
||||
public SunEditorUploadVo insertFileSunEditorUpload(String folder, MultipartHttpServletRequest multipartHttpServletRequest) {
|
||||
List<SunEditorUploadVo.Item> sunEditorUploadItemList = new ArrayList<>();
|
||||
|
||||
for (MultipartFile multipartFile : multipartHttpServletRequest.getFileMap().values()) {
|
||||
FileStorageUploadVo fileStorageUploadVo = fileStorageService.saveFileUpload(folder, multipartFile);
|
||||
String fileViewUrl = fileStorageService.buildFileViewUrl(fileStorageUploadVo.getUploadPath());
|
||||
sunEditorUploadItemList.add(new SunEditorUploadVo.Item(fileViewUrl, fileStorageUploadVo.getOriginalFileName(), fileStorageUploadVo.getFileSize()));
|
||||
}
|
||||
|
||||
return new SunEditorUploadVo(sunEditorUploadItemList);
|
||||
}
|
||||
|
||||
public ResponseEntity<Resource> selectFileView(String path) {
|
||||
return fileStorageService.loadFileResource(path, true);
|
||||
}
|
||||
|
||||
public ResponseEntity<Resource> selectFileDownload(String path) {
|
||||
return fileStorageService.loadFileResource(path, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.alist.api.front.modules.file.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "파일 업로드 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class FileUploadVo {
|
||||
@Schema(description = "업로드 파일 접근 경로", example = "/uploads/notice/2026/07/20/a1b2c3d4.png")
|
||||
private String uploadPath;
|
||||
|
||||
@Schema(description = "원본 파일명", example = "sample.png")
|
||||
private String originalFileName;
|
||||
|
||||
@Schema(description = "저장 파일명", example = "a1b2c3d4.png")
|
||||
private String storedFileName;
|
||||
|
||||
@Schema(description = "파일 확장자", example = "png")
|
||||
private String fileExtension;
|
||||
|
||||
@Schema(description = "Content-Type", example = "image/png")
|
||||
private String contentType;
|
||||
|
||||
@Schema(description = "파일 크기(byte)", example = "102400")
|
||||
private Long fileSize;
|
||||
|
||||
@Schema(description = "이미지 너비(px)", example = "800")
|
||||
private Integer width;
|
||||
|
||||
@Schema(description = "이미지 높이(px)", example = "600")
|
||||
private Integer height;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.alist.api.front.modules.file.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "SunEditor 이미지 업로드 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class SunEditorUploadVo {
|
||||
@Schema(description = "업로드 결과 목록")
|
||||
private List<Item> result;
|
||||
|
||||
public SunEditorUploadVo(List<Item> result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Schema(description = "SunEditor 업로드 파일 항목")
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Item {
|
||||
@Schema(description = "업로드 파일 URL", example = "https://file-alist.pjt.kr/uploads/editor/2026/07/20/a1b2c3d4.png")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "원본 파일명", example = "sample.png")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "파일 크기(byte)", example = "102400")
|
||||
private Long size;
|
||||
|
||||
public Item(String url, String name, Long size) {
|
||||
this.url = url;
|
||||
this.name = name;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.alist.api.front.modules.main;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Hidden
|
||||
@Controller
|
||||
public class MainController {
|
||||
|
||||
@GetMapping(value = "/")
|
||||
public String index() {
|
||||
return "redirect:/swagger-ui/index.html";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.alist.api.front.modules.sso;
|
||||
|
||||
import com.alist.api.core.common.response.ApiResponse;
|
||||
import com.alist.api.core.common.response.ApiResponseCode;
|
||||
import com.alist.api.core.common.utils.SessionUtil;
|
||||
import com.alist.api.front.modules.sso.form.SsoExchangeForm;
|
||||
import com.alist.api.front.modules.sso.form.SsoLoginForm;
|
||||
import com.alist.api.front.modules.sso.service.SsoService;
|
||||
import com.alist.api.front.modules.sso.vo.SsoExchangeVo;
|
||||
import com.alist.api.front.modules.sso.vo.SsoLoginCheckedVo;
|
||||
import com.alist.api.front.modules.sso.vo.SsoLoginVo;
|
||||
import com.alist.api.front.modules.sso.vo.SsoLogoutVo;
|
||||
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.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.CookieValue;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@Tag(name = "A-1. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 API")
|
||||
@RestController
|
||||
@RequestMapping("/sso")
|
||||
public class SsoController {
|
||||
@Value("${sso.cookie.name}")
|
||||
private String ssoCookieName;
|
||||
|
||||
private final SsoService ssoService;
|
||||
|
||||
public SsoController(SsoService ssoService) {
|
||||
this.ssoService = ssoService;
|
||||
}
|
||||
|
||||
@Operation(summary = "SSO 로그인", description = "아이디와 비밀번호를 검증하고 공통 SSO HttpOnly 쿠키를 발급합니다.")
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<ApiResponse<SsoLoginVo>> ssoLogin(
|
||||
@Valid @RequestBody SsoLoginForm ssoLoginForm,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
SsoLoginVo ssoLoginVo = ssoService.selectSsoLogin(ssoLoginForm, response);
|
||||
|
||||
if (ssoLoginVo.getResultCode() == 2003) {
|
||||
return ApiResponse.entity(ssoLoginVo, ApiResponseCode.CODE_2003);
|
||||
}
|
||||
|
||||
return ApiResponse.entity(ssoLoginVo, ApiResponseCode.CODE_2001, "SSO 로그인");
|
||||
}
|
||||
|
||||
@Operation(summary = "SSO 로그인 상태 확인", description = "SSO 세션과 Access/Refresh Token 쿠키 존재 여부를 조회합니다.")
|
||||
@GetMapping("/loginChecked")
|
||||
public ResponseEntity<ApiResponse<SsoLoginCheckedVo>> ssoLoginChecked(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
SsoLoginCheckedVo ssoLoginCheckedVo = ssoService.selectSsoLoginChecked(request, response);
|
||||
|
||||
return ApiResponse.entity(ssoLoginCheckedVo, ApiResponseCode.CODE_200);
|
||||
}
|
||||
|
||||
@Operation(summary = "SSO 인가 코드 발급", description = "SSO 세션을 확인하고 등록된 서비스의 redirect URI로 1회용 인가 코드를 전달합니다.")
|
||||
@GetMapping("/authorize")
|
||||
public ResponseEntity<Void> ssoAuthorize(
|
||||
@RequestParam String clientId,
|
||||
@RequestParam String redirectUri,
|
||||
@RequestParam(required = false) String state,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName);
|
||||
String redirectUrl = ssoService.selectSsoAuthorize(clientId, redirectUri, state, ssoSessionId);
|
||||
|
||||
return ResponseEntity.status(302).location(URI.create(redirectUrl)).build();
|
||||
}
|
||||
|
||||
@Operation(summary = "SSO 인가 코드 교환", description = "1회용 인가 코드를 검증하고 서비스 세션 생성에 필요한 사용자 정보를 반환합니다.")
|
||||
@PostMapping("/exchange")
|
||||
public ResponseEntity<ApiResponse<SsoExchangeVo>> ssoExchange(
|
||||
@Valid @RequestBody SsoExchangeForm ssoExchangeForm,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
SsoExchangeVo ssoExchangeVo = ssoService.selectSsoExchange(ssoExchangeForm, response);
|
||||
|
||||
return ApiResponse.entity(ssoExchangeVo, ApiResponseCode.CODE_2001, "SSO code 교환");
|
||||
}
|
||||
|
||||
@Operation(summary = "SSO 로그아웃", description = "SSO 세션과 USER Refresh Token을 무효화하고 관련 쿠키를 삭제합니다.")
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<ApiResponse<SsoLogoutVo>> ssoLogout(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@CookieValue(name = "refreshToken", required = false) String refreshToken
|
||||
) {
|
||||
String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName);
|
||||
SsoLogoutVo ssoLogoutVo = ssoService.updateSsoLogout(ssoSessionId, refreshToken, response);
|
||||
|
||||
return ApiResponse.entity(ssoLogoutVo, ApiResponseCode.CODE_200);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.alist.api.front.modules.sso.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "SSO 인가 코드 교환 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
public class SsoExchangeForm {
|
||||
@Schema(description = "SSO 인가 코드", example = "CODE_123e4567-e89b-12d3-a456-426614174000", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "인가 코드는 필수입니다.")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "SSO 클라이언트 ID", example = "alist-main", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "클라이언트 ID는 필수입니다.")
|
||||
private String clientId;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.alist.api.front.modules.sso.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "SSO 로그인 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
public class SsoLoginForm {
|
||||
@Schema(description = "회원 아이디", example = "teacher01", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "아이디는 필수입니다.")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "비밀번호", example = "password1234", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "비밀번호는 필수입니다.")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package com.alist.api.front.modules.sso.service;
|
||||
|
||||
import com.alist.api.core.common.jwt.JwtTokenProvider;
|
||||
import com.alist.api.core.common.utils.SessionUtil;
|
||||
import com.alist.api.core.modules.bespoke.testUser.dto.TestUserSsoLoginViewDto;
|
||||
import com.alist.api.core.modules.bespoke.testUser.service.TestUserSsoService;
|
||||
import com.alist.api.core.modules.bespoke.testUser.vo.TestUserSsoLoginViewVo;
|
||||
import com.alist.api.core.modules.standard.ssoClient.dto.SsoClientAuthorizeViewDto;
|
||||
import com.alist.api.core.modules.standard.ssoClient.service.SsoClientService;
|
||||
import com.alist.api.core.modules.standard.ssoClient.vo.SsoClientAuthorizeViewVo;
|
||||
import com.alist.api.core.modules.standard.testUser.dto.TestUserModifyDto;
|
||||
import com.alist.api.core.modules.standard.testUser.service.TestUserService;
|
||||
import com.alist.api.core.modules.standard.testUserToken.dto.TestUserTokenModifyDto;
|
||||
import com.alist.api.core.modules.standard.testUserToken.service.TestUserTokenService;
|
||||
import com.alist.api.front.modules.sso.form.SsoExchangeForm;
|
||||
import com.alist.api.front.modules.sso.form.SsoLoginForm;
|
||||
import com.alist.api.front.modules.sso.vo.SsoExchangeVo;
|
||||
import com.alist.api.front.modules.sso.vo.SsoLoginCheckedVo;
|
||||
import com.alist.api.front.modules.sso.vo.SsoLoginVo;
|
||||
import com.alist.api.front.modules.sso.vo.SsoLogoutVo;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class SsoService {
|
||||
private static final String SSO_SESSION_KEY_PREFIX = "alist:sso:session:";
|
||||
private static final String SSO_USER_KEY_PREFIX = "alist:sso:userIdx:";
|
||||
private static final String SSO_CODE_KEY_PREFIX = "alist:sso:code:";
|
||||
private static final String USER_SCOPE = "USER";
|
||||
private static final String REFRESH_TOKEN_TYPE = "REFRESH";
|
||||
|
||||
@Value("${cookie.secure}")
|
||||
private boolean cookieSecure;
|
||||
|
||||
@Value("${cookie.domain:}")
|
||||
private String cookieDomain;
|
||||
|
||||
@Value("${cookie.same-site:Lax}")
|
||||
private String cookieSameSite;
|
||||
|
||||
@Value("${sso.session.ttl-seconds}")
|
||||
private long ssoSessionTtlSeconds;
|
||||
|
||||
@Value("${sso.code.ttl-seconds}")
|
||||
private long ssoCodeTtlSeconds;
|
||||
|
||||
@Value("${sso.cookie.name}")
|
||||
private String ssoCookieName;
|
||||
|
||||
private final TestUserSsoService testUserSsoService;
|
||||
private final SsoClientService ssoClientService;
|
||||
private final TestUserService testUserService;
|
||||
private final TestUserTokenService testUserTokenService;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
public SsoService(
|
||||
TestUserSsoService testUserSsoService,
|
||||
SsoClientService ssoClientService,
|
||||
TestUserService testUserService,
|
||||
TestUserTokenService testUserTokenService,
|
||||
StringRedisTemplate stringRedisTemplate,
|
||||
PasswordEncoder passwordEncoder,
|
||||
JwtTokenProvider jwtTokenProvider
|
||||
) {
|
||||
this.testUserSsoService = testUserSsoService;
|
||||
this.ssoClientService = ssoClientService;
|
||||
this.testUserService = testUserService;
|
||||
this.testUserTokenService = testUserTokenService;
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.jwtTokenProvider = jwtTokenProvider;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public SsoLoginVo selectSsoLogin(SsoLoginForm ssoLoginForm, HttpServletResponse response) {
|
||||
TestUserSsoLoginViewDto testUserSsoLoginViewDto = new TestUserSsoLoginViewDto();
|
||||
testUserSsoLoginViewDto.setId(ssoLoginForm.getId().trim());
|
||||
|
||||
TestUserSsoLoginViewVo testUserSsoLoginViewVo = testUserSsoService.testUserSsoLoginView(testUserSsoLoginViewDto);
|
||||
|
||||
if (testUserSsoLoginViewVo == null
|
||||
|| !passwordEncoder.matches(ssoLoginForm.getPassword(), testUserSsoLoginViewVo.getPassword())) {
|
||||
SsoLoginVo ssoLoginVo = new SsoLoginVo();
|
||||
ssoLoginVo.setLogin(false);
|
||||
ssoLoginVo.setResultCode(2003);
|
||||
|
||||
return ssoLoginVo;
|
||||
}
|
||||
|
||||
String ssoSessionId = "SSO_" + UUID.randomUUID();
|
||||
String ssoSessionKey = SSO_SESSION_KEY_PREFIX + ssoSessionId;
|
||||
String ssoUserKey = SSO_USER_KEY_PREFIX + testUserSsoLoginViewVo.getUserIdx();
|
||||
String previousSsoSessionId = stringRedisTemplate.opsForValue().get(ssoUserKey);
|
||||
|
||||
if (previousSsoSessionId != null && !previousSsoSessionId.isBlank()) {
|
||||
stringRedisTemplate.delete(SSO_SESSION_KEY_PREFIX + previousSsoSessionId);
|
||||
}
|
||||
|
||||
stringRedisTemplate.opsForHash().put(ssoSessionKey, "userIdx", String.valueOf(testUserSsoLoginViewVo.getUserIdx()));
|
||||
stringRedisTemplate.opsForHash().put(ssoSessionKey, "userTokenIdx", String.valueOf(testUserSsoLoginViewVo.getUserTokenIdx()));
|
||||
stringRedisTemplate.opsForHash().put(ssoSessionKey, "userId", testUserSsoLoginViewVo.getId());
|
||||
stringRedisTemplate.opsForHash().put(ssoSessionKey, "userRole", testUserSsoLoginViewVo.getUserRole());
|
||||
stringRedisTemplate.opsForHash().put(ssoSessionKey, "userType", testUserSsoLoginViewVo.getUserType());
|
||||
stringRedisTemplate.expire(ssoSessionKey, java.time.Duration.ofSeconds(ssoSessionTtlSeconds));
|
||||
stringRedisTemplate.opsForValue().set(ssoUserKey, ssoSessionId, java.time.Duration.ofSeconds(ssoSessionTtlSeconds));
|
||||
|
||||
TestUserModifyDto testUserModifyDto = new TestUserModifyDto();
|
||||
testUserModifyDto.setUserIdx(testUserSsoLoginViewVo.getUserIdx());
|
||||
testUserModifyDto.setLastLoginAt(LocalDateTime.now());
|
||||
testUserService.updateTestUserModify(testUserModifyDto);
|
||||
|
||||
SessionUtil.addTokenCookie(response, ssoCookieName, ssoSessionId, cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
|
||||
|
||||
SsoLoginVo ssoLoginVo = new SsoLoginVo();
|
||||
ssoLoginVo.setLogin(true);
|
||||
ssoLoginVo.setId(testUserSsoLoginViewVo.getId());
|
||||
ssoLoginVo.setUserIdx(testUserSsoLoginViewVo.getUserIdx());
|
||||
ssoLoginVo.setUserRole(testUserSsoLoginViewVo.getUserRole());
|
||||
ssoLoginVo.setUserType(testUserSsoLoginViewVo.getUserType());
|
||||
ssoLoginVo.setResultCode(2001);
|
||||
|
||||
return ssoLoginVo;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public SsoLoginCheckedVo selectSsoLoginChecked(HttpServletRequest request, HttpServletResponse response) {
|
||||
String ssoSessionId = SessionUtil.resolveSsoCookieValue(request, ssoCookieName);
|
||||
String accessToken = SessionUtil.resolveSsoCookieValue(request, "accessToken");
|
||||
String refreshToken = SessionUtil.resolveSsoCookieValue(request, "refreshToken");
|
||||
|
||||
SsoLoginCheckedVo ssoLoginCheckedVo = new SsoLoginCheckedVo();
|
||||
ssoLoginCheckedVo.setAccessToken(accessToken != null && !accessToken.isBlank());
|
||||
ssoLoginCheckedVo.setRefreshToken(refreshToken != null && !refreshToken.isBlank());
|
||||
|
||||
if (ssoSessionId == null || ssoSessionId.isBlank()) {
|
||||
ssoLoginCheckedVo.setLoggedIn(false);
|
||||
|
||||
return ssoLoginCheckedVo;
|
||||
}
|
||||
|
||||
String ssoSessionKey = SSO_SESSION_KEY_PREFIX + ssoSessionId;
|
||||
String userIdx = (String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userIdx");
|
||||
|
||||
if (userIdx == null || userIdx.isBlank()) {
|
||||
ssoLoginCheckedVo.setLoggedIn(false);
|
||||
|
||||
return ssoLoginCheckedVo;
|
||||
}
|
||||
|
||||
String latestSsoSessionId = stringRedisTemplate.opsForValue().get(SSO_USER_KEY_PREFIX + userIdx);
|
||||
if (!ssoSessionId.equals(latestSsoSessionId)) {
|
||||
ssoLoginCheckedVo.setLoggedIn(false);
|
||||
|
||||
return ssoLoginCheckedVo;
|
||||
}
|
||||
|
||||
refreshSsoSessionTtl(ssoSessionId, userIdx);
|
||||
SessionUtil.addTokenCookie(response, ssoCookieName, ssoSessionId, cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
|
||||
|
||||
ssoLoginCheckedVo.setLoggedIn(true);
|
||||
ssoLoginCheckedVo.setSsoSessionId(ssoSessionId);
|
||||
ssoLoginCheckedVo.setUserIdx(Integer.valueOf(userIdx));
|
||||
ssoLoginCheckedVo.setUserTokenIdx(Integer.valueOf((String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userTokenIdx")));
|
||||
ssoLoginCheckedVo.setUserId((String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userId"));
|
||||
ssoLoginCheckedVo.setUserRole((String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userRole"));
|
||||
ssoLoginCheckedVo.setUserType((String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userType"));
|
||||
|
||||
return ssoLoginCheckedVo;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public String selectSsoAuthorize(String clientId, String redirectUri, String state, String ssoSessionId) {
|
||||
if (ssoSessionId == null || ssoSessionId.isBlank()) {
|
||||
return "/login";
|
||||
}
|
||||
|
||||
String ssoSessionKey = SSO_SESSION_KEY_PREFIX + ssoSessionId;
|
||||
String userIdx = (String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userIdx");
|
||||
|
||||
if (userIdx == null || userIdx.isBlank()) {
|
||||
return "/login";
|
||||
}
|
||||
|
||||
String latestSsoSessionId = stringRedisTemplate.opsForValue().get(SSO_USER_KEY_PREFIX + userIdx);
|
||||
if (!ssoSessionId.equals(latestSsoSessionId)) {
|
||||
return "/login";
|
||||
}
|
||||
|
||||
SsoClientAuthorizeViewDto ssoClientAuthorizeViewDto = new SsoClientAuthorizeViewDto();
|
||||
ssoClientAuthorizeViewDto.setClientId(clientId.trim());
|
||||
ssoClientAuthorizeViewDto.setRedirectUri(redirectUri.trim());
|
||||
|
||||
SsoClientAuthorizeViewVo ssoClientAuthorizeViewVo = ssoClientService.selectSsoClientAuthorizeView(ssoClientAuthorizeViewDto);
|
||||
if (ssoClientAuthorizeViewVo == null) {
|
||||
throw new IllegalArgumentException("허용되지 않은 clientId 또는 redirectUri 입니다.");
|
||||
}
|
||||
|
||||
refreshSsoSessionTtl(ssoSessionId, userIdx);
|
||||
|
||||
String code = "CODE_" + UUID.randomUUID();
|
||||
String ssoCodeKey = SSO_CODE_KEY_PREFIX + code;
|
||||
|
||||
stringRedisTemplate.opsForHash().put(ssoCodeKey, "ssoSessionId", ssoSessionId);
|
||||
stringRedisTemplate.opsForHash().put(ssoCodeKey, "clientId", ssoClientAuthorizeViewVo.getClientId());
|
||||
stringRedisTemplate.opsForHash().put(ssoCodeKey, "redirectUri", ssoClientAuthorizeViewVo.getRedirectUri());
|
||||
stringRedisTemplate.expire(ssoCodeKey, java.time.Duration.ofSeconds(ssoCodeTtlSeconds));
|
||||
|
||||
URI uri = URI.create(ssoClientAuthorizeViewVo.getRedirectUri());
|
||||
StringBuilder redirectUrl = new StringBuilder(uri.getScheme() + "://" + uri.getAuthority() + uri.getPath());
|
||||
String encodedCode = URLEncoder.encode(code, StandardCharsets.UTF_8);
|
||||
|
||||
if (uri.getQuery() == null || uri.getQuery().isBlank()) {
|
||||
redirectUrl.append("?code=").append(encodedCode);
|
||||
} else {
|
||||
redirectUrl.append("?").append(uri.getQuery()).append("&code=").append(encodedCode);
|
||||
}
|
||||
|
||||
if (state != null && !state.isBlank()) {
|
||||
redirectUrl.append("&state=").append(URLEncoder.encode(state, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
return redirectUrl.toString();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public SsoExchangeVo selectSsoExchange(SsoExchangeForm ssoExchangeForm, HttpServletResponse response) {
|
||||
String ssoCodeKey = SSO_CODE_KEY_PREFIX + ssoExchangeForm.getCode().trim();
|
||||
String ssoSessionId = (String) stringRedisTemplate.opsForHash().get(ssoCodeKey, "ssoSessionId");
|
||||
String clientId = (String) stringRedisTemplate.opsForHash().get(ssoCodeKey, "clientId");
|
||||
|
||||
if (ssoSessionId == null || !ssoExchangeForm.getClientId().trim().equals(clientId)) {
|
||||
throw new IllegalArgumentException("유효하지 않은 code 입니다.");
|
||||
}
|
||||
|
||||
String ssoSessionKey = SSO_SESSION_KEY_PREFIX + ssoSessionId;
|
||||
String userIdx = (String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userIdx");
|
||||
String latestSsoSessionId = stringRedisTemplate.opsForValue().get(SSO_USER_KEY_PREFIX + userIdx);
|
||||
|
||||
if (!ssoSessionId.equals(latestSsoSessionId)) {
|
||||
throw new IllegalArgumentException("다른 곳에서 다시 로그인되어 세션이 만료되었습니다.");
|
||||
}
|
||||
|
||||
refreshSsoSessionTtl(ssoSessionId, userIdx);
|
||||
stringRedisTemplate.delete(ssoCodeKey);
|
||||
SessionUtil.addTokenCookie(response, ssoCookieName, ssoSessionId, cookieDomain, cookieSecure, cookieSameSite, ssoSessionTtlSeconds);
|
||||
|
||||
SsoExchangeVo ssoExchangeVo = new SsoExchangeVo();
|
||||
ssoExchangeVo.setSsoSessionId(ssoSessionId);
|
||||
ssoExchangeVo.setUserIdx(Integer.valueOf(userIdx));
|
||||
ssoExchangeVo.setUserTokenIdx(Integer.valueOf((String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userTokenIdx")));
|
||||
ssoExchangeVo.setUserId((String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userId"));
|
||||
ssoExchangeVo.setUserRole((String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userRole"));
|
||||
ssoExchangeVo.setUserType((String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userType"));
|
||||
|
||||
return ssoExchangeVo;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public SsoLogoutVo updateSsoLogout(String ssoSessionId, String refreshToken, HttpServletResponse response) {
|
||||
if (ssoSessionId != null && !ssoSessionId.isBlank()) {
|
||||
String ssoSessionKey = SSO_SESSION_KEY_PREFIX + ssoSessionId;
|
||||
String userIdx = (String) stringRedisTemplate.opsForHash().get(ssoSessionKey, "userIdx");
|
||||
|
||||
stringRedisTemplate.delete(ssoSessionKey);
|
||||
|
||||
if (userIdx != null && !userIdx.isBlank()) {
|
||||
stringRedisTemplate.delete(SSO_USER_KEY_PREFIX + userIdx);
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshToken != null
|
||||
&& jwtTokenProvider.validateToken(refreshToken)
|
||||
&& USER_SCOPE.equals(jwtTokenProvider.getScope(refreshToken))
|
||||
&& REFRESH_TOKEN_TYPE.equals(jwtTokenProvider.getTokenType(refreshToken))) {
|
||||
try {
|
||||
TestUserTokenModifyDto testUserTokenModifyDto = new TestUserTokenModifyDto();
|
||||
testUserTokenModifyDto.setUserTokenIdx(Integer.valueOf(jwtTokenProvider.getUserTokenIdx(refreshToken)));
|
||||
testUserTokenModifyDto.setRefreshToken("");
|
||||
testUserTokenService.updateTestUserTokenModify(testUserTokenModifyDto);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
SessionUtil.expireCookie(response, ssoCookieName, cookieDomain, cookieSecure, cookieSameSite);
|
||||
SessionUtil.expireCookie(response, "accessToken", cookieDomain, cookieSecure, cookieSameSite);
|
||||
SessionUtil.expireCookie(response, "refreshToken", cookieDomain, cookieSecure, cookieSameSite);
|
||||
|
||||
SsoLogoutVo ssoLogoutVo = new SsoLogoutVo();
|
||||
ssoLogoutVo.setLoggedOut(true);
|
||||
|
||||
return ssoLogoutVo;
|
||||
}
|
||||
|
||||
private void refreshSsoSessionTtl(String ssoSessionId, String userIdx) {
|
||||
stringRedisTemplate.expire(SSO_SESSION_KEY_PREFIX + ssoSessionId, java.time.Duration.ofSeconds(ssoSessionTtlSeconds));
|
||||
stringRedisTemplate.expire(SSO_USER_KEY_PREFIX + userIdx, java.time.Duration.ofSeconds(ssoSessionTtlSeconds));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.alist.api.front.modules.sso.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "SSO 인가 코드 교환 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class SsoExchangeVo {
|
||||
@Schema(description = "SSO 세션 ID")
|
||||
private String ssoSessionId;
|
||||
|
||||
@Schema(description = "회원 PK", example = "1")
|
||||
private Integer userIdx;
|
||||
|
||||
@Schema(description = "회원 토큰 PK", example = "1")
|
||||
private Integer userTokenIdx;
|
||||
|
||||
@Schema(description = "회원 아이디", example = "teacher01")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "회원 권한", example = "TEACHER")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "회원 유형", example = "T")
|
||||
private String userType;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.alist.api.front.modules.sso.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "SSO 로그인 상태 확인 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class SsoLoginCheckedVo {
|
||||
@JsonProperty("isAccessToken")
|
||||
@Schema(description = "Access Token 쿠키 존재 여부", example = "true")
|
||||
private boolean accessToken;
|
||||
|
||||
@JsonProperty("isRefreshToken")
|
||||
@Schema(description = "Refresh Token 쿠키 존재 여부", example = "true")
|
||||
private boolean refreshToken;
|
||||
|
||||
@Schema(description = "SSO 로그인 상태", example = "true")
|
||||
private boolean loggedIn;
|
||||
|
||||
@Schema(description = "SSO 세션 ID")
|
||||
private String ssoSessionId;
|
||||
|
||||
@Schema(description = "회원 아이디", example = "teacher01")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "회원 PK", example = "1")
|
||||
private Integer userIdx;
|
||||
|
||||
@Schema(description = "회원 토큰 PK", example = "1")
|
||||
private Integer userTokenIdx;
|
||||
|
||||
@Schema(description = "회원 권한", example = "TEACHER")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "회원 유형", example = "T")
|
||||
private String userType;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.alist.api.front.modules.sso.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "SSO 로그인 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class SsoLoginVo {
|
||||
@Schema(description = "로그인 성공 여부", example = "true")
|
||||
private boolean login;
|
||||
|
||||
@Schema(description = "회원 아이디", example = "teacher01")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "회원 PK", example = "1")
|
||||
private Integer userIdx;
|
||||
|
||||
@Schema(description = "회원 권한", example = "TEACHER")
|
||||
private String userRole;
|
||||
|
||||
@Schema(description = "회원 유형", example = "T")
|
||||
private String userType;
|
||||
|
||||
@JsonIgnore
|
||||
@Schema(hidden = true)
|
||||
private int resultCode;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.alist.api.front.modules.sso.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "SSO 로그아웃 응답")
|
||||
@Getter
|
||||
@Setter
|
||||
public class SsoLogoutVo {
|
||||
@Schema(description = "로그아웃 완료 여부", example = "true")
|
||||
private boolean loggedOut;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.alist.api.front.modules.user;
|
||||
|
||||
import com.alist.api.core.common.response.ApiResponse;
|
||||
import com.alist.api.core.common.response.ApiResponseCode;
|
||||
import com.alist.api.front.modules.user.form.*;
|
||||
import com.alist.api.front.modules.user.service.UserService;
|
||||
import com.alist.api.front.modules.user.vo.*;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "A-3. 사용자 관리", description = "사용자 회원가입 및 개인정보 관리 API")
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
public class UserController {
|
||||
private final UserService userService;
|
||||
|
||||
public UserController(UserService userService) { this.userService = userService; }
|
||||
|
||||
@Operation(summary = "사용자 회원가입", description = "새 사용자와 사용자 API Key를 등록합니다.")
|
||||
@PostMapping("/add")
|
||||
public ResponseEntity<ApiResponse<UserAddVo>> userAdd(@Valid @RequestBody UserAddForm userAddForm) {
|
||||
UserAddVo userAddVo = userService.insertUserAdd(userAddForm);
|
||||
if (userAddVo.getResultCode() == 2004) return ApiResponse.entity(userAddVo, ApiResponseCode.CODE_2004, "아이디");
|
||||
return ApiResponse.entity(userAddVo, ApiResponseCode.CODE_2002, "사용자");
|
||||
}
|
||||
|
||||
@Operation(summary = "이전 회원 정보 조회", description = "ALIST와 ELTOWN의 이전 회원 정보를 조회합니다.")
|
||||
@PostMapping("/migration/list")
|
||||
public ResponseEntity<ApiResponse<UserMigrationListVo>> userMigrationList(@Valid @RequestBody UserMigrationListForm userMigrationListForm) {
|
||||
UserMigrationListVo userMigrationListVo = userService.selectUserMigrationList(userMigrationListForm);
|
||||
if (userMigrationListVo.getAlistUserList().isEmpty() && userMigrationListVo.getEltownUserList().isEmpty()) return ApiResponse.entity(userMigrationListVo, ApiResponseCode.CODE_2003);
|
||||
return ApiResponse.entity(userMigrationListVo, ApiResponseCode.CODE_2001, "이전 회원");
|
||||
}
|
||||
|
||||
@Operation(summary = "사용자 개인정보 조회", description = "로그인한 사용자의 개인정보를 조회합니다.")
|
||||
@GetMapping("/profile/view")
|
||||
public ResponseEntity<ApiResponse<UserProfileViewVo>> userProfileView() {
|
||||
UserProfileViewVo userProfileViewVo = userService.selectUserProfileView();
|
||||
if (userProfileViewVo == null) return ApiResponse.entity(new UserProfileViewVo(), ApiResponseCode.CODE_2003);
|
||||
return ApiResponse.entity(userProfileViewVo, ApiResponseCode.CODE_2001, "개인정보");
|
||||
}
|
||||
|
||||
@Operation(summary = "사용자 비밀번호 확인", description = "로그인한 사용자의 현재 비밀번호를 확인합니다.")
|
||||
@PostMapping("/password/check")
|
||||
public ResponseEntity<ApiResponse<UserPasswordCheckVo>> userPasswordCheck(@Valid @RequestBody UserPasswordCheckForm userPasswordCheckForm) {
|
||||
UserPasswordCheckVo userPasswordCheckVo = userService.selectUserPasswordCheck(userPasswordCheckForm);
|
||||
if (!userPasswordCheckVo.isPasswordMatched()) return ApiResponse.entity(userPasswordCheckVo, userPasswordCheckVo.getResultCode() == 4003 ? ApiResponseCode.CODE_4001 : ApiResponseCode.CODE_2003);
|
||||
return ApiResponse.entity(userPasswordCheckVo, ApiResponseCode.CODE_200);
|
||||
}
|
||||
|
||||
@Operation(summary = "사용자 비밀번호 수정", description = "현재 비밀번호 확인 후 새 비밀번호로 변경합니다.")
|
||||
@PatchMapping("/password/modify")
|
||||
public ResponseEntity<ApiResponse<UserPasswordModifyVo>> userPasswordModify(@Valid @RequestBody UserPasswordModifyForm userPasswordModifyForm) {
|
||||
UserPasswordModifyVo userPasswordModifyVo = userService.updateUserPasswordModify(userPasswordModifyForm);
|
||||
if (!userPasswordModifyVo.isPasswordChanged()) return ApiResponse.entity(userPasswordModifyVo, userPasswordModifyVo.getResultCode() == 4001 || userPasswordModifyVo.getResultCode() == 4003 ? ApiResponseCode.CODE_4001 : ApiResponseCode.CODE_2003);
|
||||
return ApiResponse.entity(userPasswordModifyVo, ApiResponseCode.CODE_2005, "비밀번호 변경");
|
||||
}
|
||||
|
||||
@Operation(summary = "사용자 개인정보 수정", description = "현재 비밀번호 확인 후 이메일과 휴대폰 번호를 수정합니다.")
|
||||
@PatchMapping("/profile/modify")
|
||||
public ResponseEntity<ApiResponse<UserProfileModifyVo>> userProfileModify(@Valid @RequestBody UserProfileModifyForm userProfileModifyForm) {
|
||||
UserProfileModifyVo userProfileModifyVo = userService.updateUserProfileModify(userProfileModifyForm);
|
||||
if (!userProfileModifyVo.isProfileUpdated()) return ApiResponse.entity(userProfileModifyVo, userProfileModifyVo.getResultCode() == 4001 || userProfileModifyVo.getResultCode() == 4003 ? ApiResponseCode.CODE_4001 : ApiResponseCode.CODE_2003);
|
||||
return ApiResponse.entity(userProfileModifyVo, ApiResponseCode.CODE_2005, "개인정보 수정");
|
||||
}
|
||||
|
||||
@Operation(summary = "사용자 탈퇴 상태 변경", description = "탈퇴 요청(P) 또는 철회(N)를 처리합니다.")
|
||||
@PatchMapping("/withdraw/modify")
|
||||
public ResponseEntity<ApiResponse<UserWithdrawModifyVo>> userWithdrawModify(@Valid @RequestBody UserWithdrawModifyForm userWithdrawModifyForm) {
|
||||
UserWithdrawModifyVo userWithdrawModifyVo = userService.updateUserWithdrawModify(userWithdrawModifyForm);
|
||||
if (!userWithdrawModifyVo.isWithdrawStatusUpdated()) return ApiResponse.entity(userWithdrawModifyVo, userWithdrawModifyVo.getResultCode() == 4003 ? ApiResponseCode.CODE_4001 : ApiResponseCode.CODE_2003);
|
||||
return ApiResponse.entity(userWithdrawModifyVo, ApiResponseCode.CODE_2005, "탈퇴 상태 변경");
|
||||
}
|
||||
|
||||
@Operation(summary = "사용자 휴면 상태 변경", description = "휴면 처리(Y) 또는 해지(N)를 처리합니다.")
|
||||
@PatchMapping("/dormant/modify")
|
||||
public ResponseEntity<ApiResponse<UserDormantModifyVo>> userDormantModify(@Valid @RequestBody UserDormantModifyForm userDormantModifyForm) {
|
||||
UserDormantModifyVo userDormantModifyVo = userService.updateUserDormantModify(userDormantModifyForm);
|
||||
if (!userDormantModifyVo.isDormantStatusUpdated()) return ApiResponse.entity(userDormantModifyVo, userDormantModifyVo.getResultCode() == 4003 ? ApiResponseCode.CODE_4001 : ApiResponseCode.CODE_2003);
|
||||
return ApiResponse.entity(userDormantModifyVo, ApiResponseCode.CODE_2005, "휴면 상태 변경");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.alist.api.front.modules.user.form;
|
||||
|
||||
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;
|
||||
|
||||
@Schema(description = "사용자 회원가입 요청")
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserAddForm {
|
||||
@Schema(description = "사용자 아이디", example = "teacher02", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "아이디를 입력해주세요.") private String id;
|
||||
@Schema(description = "비밀번호", example = "pass1234", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "비밀번호를 입력해주세요.") @Size(min = 8, max = 64) @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)\\S+$", message = "비밀번호는 영문과 숫자를 포함하고 공백이 없어야 합니다.") private String password;
|
||||
@Schema(description = "회원 권한", example = "I", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Pattern(regexp = "^(CCM|SCM|I|R|E)$") private String userRole;
|
||||
@Schema(description = "회원 유형", example = "T", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Pattern(regexp = "^(S|T)$") private String userType;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.alist.api.front.modules.user.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 휴면 상태 변경 요청") @Getter @Setter
|
||||
public class UserDormantModifyForm {
|
||||
@Schema(description = "휴면 여부", example = "Y", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank @Pattern(regexp = "^[YN]$") private String dormantYn;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.alist.api.front.modules.user.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "이전 회원 조회 요청") @Getter @Setter
|
||||
public class UserMigrationListForm {
|
||||
@Schema(description = "이전 회원 아이디", example = "test", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank private String id;
|
||||
@Schema(description = "이전 회원 비밀번호", example = "pass1234", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank private String password;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.alist.api.front.modules.user.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 비밀번호 확인 요청") @Getter @Setter
|
||||
public class UserPasswordCheckForm {
|
||||
@Schema(description = "현재 비밀번호", example = "pass1234", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank private String password;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.alist.api.front.modules.user.form;
|
||||
|
||||
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;
|
||||
|
||||
@Schema(description = "사용자 비밀번호 수정 요청") @Getter @Setter
|
||||
public class UserPasswordModifyForm {
|
||||
@Schema(description = "현재 비밀번호", example = "pass1234", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank private String currentPassword;
|
||||
@Schema(description = "새 비밀번호", example = "newpass1234", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank @Size(min = 8, max = 64) @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)\\S+$") private String newPassword;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.alist.api.front.modules.user.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 개인정보 수정 요청") @Getter @Setter
|
||||
public class UserProfileModifyForm {
|
||||
@Schema(description = "현재 비밀번호", example = "pass1234", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank private String currentPassword;
|
||||
@Schema(description = "이메일", example = "user@example.com") @Email private String email;
|
||||
@Schema(description = "휴대폰 번호", example = "01012345678") private String hp;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.alist.api.front.modules.user.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 탈퇴 상태 변경 요청") @Getter @Setter
|
||||
public class UserWithdrawModifyForm {
|
||||
@Schema(description = "탈퇴 상태(P: 요청, N: 철회)", example = "P", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank @Pattern(regexp = "^[PN]$") private String withdrawStatus;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package com.alist.api.front.modules.user.service;
|
||||
|
||||
import com.alist.api.core.common.utils.ApiKeyGenerator;
|
||||
import com.alist.api.core.common.utils.SecurityUtil;
|
||||
import com.alist.api.core.modules.bespoke.testUser.dto.TestUserUserPasswordViewDto;
|
||||
import com.alist.api.core.modules.bespoke.testUser.dto.TestUserUserProfileViewDto;
|
||||
import com.alist.api.core.modules.bespoke.testUser.service.TestUserUserService;
|
||||
import com.alist.api.core.modules.bespoke.testUser.vo.TestUserUserPasswordViewVo;
|
||||
import com.alist.api.core.modules.bespoke.testUser.vo.TestUserUserProfileViewVo;
|
||||
import com.alist.api.core.modules.migration.alist.user.dto.AlistUserMigrationDto;
|
||||
import com.alist.api.core.modules.migration.alist.user.service.AlistUserMigrationService;
|
||||
import com.alist.api.core.modules.migration.alist.user.vo.AlistUserMigrationVo;
|
||||
import com.alist.api.core.modules.migration.eltown.user.dto.EltownUserMigrationDto;
|
||||
import com.alist.api.core.modules.migration.eltown.user.service.EltownUserMigrationService;
|
||||
import com.alist.api.core.modules.migration.eltown.user.vo.EltownUserMigrationVo;
|
||||
import com.alist.api.core.modules.standard.testUser.dto.TestUserAddDto;
|
||||
import com.alist.api.core.modules.standard.testUser.dto.TestUserModifyDto;
|
||||
import com.alist.api.core.modules.standard.testUser.service.TestUserService;
|
||||
import com.alist.api.core.modules.standard.testUserToken.dto.TestUserTokenAddDto;
|
||||
import com.alist.api.core.modules.standard.testUserToken.service.TestUserTokenService;
|
||||
import com.alist.api.front.modules.user.form.*;
|
||||
import com.alist.api.front.modules.user.vo.*;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
private final TestUserService testUserService;
|
||||
private final TestUserTokenService testUserTokenService;
|
||||
private final TestUserUserService testUserUserService;
|
||||
private final AlistUserMigrationService alistUserMigrationService;
|
||||
private final EltownUserMigrationService eltownUserMigrationService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public UserService(TestUserService testUserService, TestUserTokenService testUserTokenService, TestUserUserService testUserUserService, AlistUserMigrationService alistUserMigrationService, EltownUserMigrationService eltownUserMigrationService, PasswordEncoder passwordEncoder) {
|
||||
this.testUserService = testUserService;
|
||||
this.testUserTokenService = testUserTokenService;
|
||||
this.testUserUserService = testUserUserService;
|
||||
this.alistUserMigrationService = alistUserMigrationService;
|
||||
this.eltownUserMigrationService = eltownUserMigrationService;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserAddVo insertUserAdd(UserAddForm userAddForm) {
|
||||
UserAddVo userAddVo = new UserAddVo();
|
||||
userAddVo.setId(userAddForm.getId().trim());
|
||||
userAddVo.setUserRole(userAddForm.getUserRole().trim());
|
||||
userAddVo.setUserType(userAddForm.getUserType().trim());
|
||||
|
||||
if (testUserService.selectTestUserDuplicateCount(userAddVo.getId()) > 0) {
|
||||
userAddVo.setResultCode(2004);
|
||||
return userAddVo;
|
||||
}
|
||||
|
||||
TestUserAddDto testUserAddDto = new TestUserAddDto();
|
||||
testUserAddDto.setId(userAddVo.getId());
|
||||
testUserAddDto.setPassword(userAddForm.getPassword());
|
||||
var testUserAddVo = testUserService.insertTestUserAdd(testUserAddDto);
|
||||
|
||||
if (!testUserAddVo.isAdded()) {
|
||||
userAddVo.setResultCode(testUserAddVo.getResultCode());
|
||||
return userAddVo;
|
||||
}
|
||||
|
||||
for (int retryCount = 0; retryCount < 5; retryCount++) {
|
||||
TestUserTokenAddDto testUserTokenAddDto = new TestUserTokenAddDto();
|
||||
testUserTokenAddDto.setUserIdx(testUserAddVo.getUserIdx());
|
||||
testUserTokenAddDto.setUserApiKey(ApiKeyGenerator.userApiKeyProc());
|
||||
testUserTokenAddDto.setUserRole(userAddVo.getUserRole());
|
||||
testUserTokenAddDto.setUserType(userAddVo.getUserType());
|
||||
|
||||
var testUserTokenAddVo = testUserTokenService.insertTestUserTokenAdd(testUserTokenAddDto);
|
||||
if (testUserTokenAddVo.isAdded()) {
|
||||
userAddVo.setUserIdx(testUserAddVo.getUserIdx());
|
||||
userAddVo.setResultCode(2002);
|
||||
return userAddVo;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("사용자 API Key 생성에 실패했습니다.");
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public UserProfileViewVo selectUserProfileView() {
|
||||
Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx();
|
||||
if (userTokenIdx == null) return null;
|
||||
|
||||
TestUserUserProfileViewDto testUserUserProfileViewDto = new TestUserUserProfileViewDto();
|
||||
testUserUserProfileViewDto.setUserTokenIdx(userTokenIdx);
|
||||
TestUserUserProfileViewVo testUserUserProfileViewVo = testUserUserService.testUserUserProfileView(testUserUserProfileViewDto);
|
||||
if (testUserUserProfileViewVo == null) return null;
|
||||
|
||||
UserProfileViewVo userProfileViewVo = new UserProfileViewVo();
|
||||
userProfileViewVo.setUserIdx(testUserUserProfileViewVo.getUserIdx());
|
||||
userProfileViewVo.setId(testUserUserProfileViewVo.getId());
|
||||
userProfileViewVo.setEmail(testUserUserProfileViewVo.getEmail());
|
||||
userProfileViewVo.setHp(testUserUserProfileViewVo.getHp());
|
||||
userProfileViewVo.setUserRole(testUserUserProfileViewVo.getUserRole());
|
||||
userProfileViewVo.setUserType(testUserUserProfileViewVo.getUserType());
|
||||
return userProfileViewVo;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public UserPasswordCheckVo selectUserPasswordCheck(UserPasswordCheckForm userPasswordCheckForm) {
|
||||
return passwordCheck(userPasswordCheckForm.getPassword());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserPasswordModifyVo updateUserPasswordModify(UserPasswordModifyForm userPasswordModifyForm) {
|
||||
UserPasswordCheckVo userPasswordCheckVo = passwordCheck(userPasswordModifyForm.getCurrentPassword());
|
||||
UserPasswordModifyVo userPasswordModifyVo = new UserPasswordModifyVo();
|
||||
if (!userPasswordCheckVo.isPasswordMatched()) { userPasswordModifyVo.setResultCode(userPasswordCheckVo.getResultCode()); return userPasswordModifyVo; }
|
||||
|
||||
TestUserUserPasswordViewDto testUserUserPasswordViewDto = new TestUserUserPasswordViewDto();
|
||||
testUserUserPasswordViewDto.setUserTokenIdx(SecurityUtil.getLoginUserTokenIdx());
|
||||
TestUserUserPasswordViewVo testUserUserPasswordViewVo = testUserUserService.testUserUserPasswordView(testUserUserPasswordViewDto);
|
||||
TestUserModifyDto testUserModifyDto = new TestUserModifyDto();
|
||||
testUserModifyDto.setUserIdx(testUserUserPasswordViewVo.getUserIdx());
|
||||
testUserModifyDto.setPassword(userPasswordModifyForm.getNewPassword());
|
||||
var testUserModifyVo = testUserService.updateTestUserModify(testUserModifyDto);
|
||||
userPasswordModifyVo.setPasswordChanged(testUserModifyVo.isUpdated()); userPasswordModifyVo.setResultCode(testUserModifyVo.getResultCode()); return userPasswordModifyVo;
|
||||
}
|
||||
|
||||
private UserPasswordCheckVo passwordCheck(String password) {
|
||||
UserPasswordCheckVo userPasswordCheckVo = new UserPasswordCheckVo(); Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx();
|
||||
if (userTokenIdx == null || password == null) { userPasswordCheckVo.setResultCode(4003); return userPasswordCheckVo; }
|
||||
TestUserUserPasswordViewDto testUserUserPasswordViewDto = new TestUserUserPasswordViewDto(); testUserUserPasswordViewDto.setUserTokenIdx(userTokenIdx);
|
||||
TestUserUserPasswordViewVo testUserUserPasswordViewVo = testUserUserService.testUserUserPasswordView(testUserUserPasswordViewDto);
|
||||
if (testUserUserPasswordViewVo == null) { userPasswordCheckVo.setResultCode(2003); return userPasswordCheckVo; }
|
||||
userPasswordCheckVo.setPasswordMatched(passwordEncoder.matches(password, testUserUserPasswordViewVo.getPassword())); userPasswordCheckVo.setResultCode(userPasswordCheckVo.isPasswordMatched() ? 200 : 4001); return userPasswordCheckVo;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public UserMigrationListVo selectUserMigrationList(UserMigrationListForm userMigrationListForm) {
|
||||
AlistUserMigrationDto alistUserMigrationDto = new AlistUserMigrationDto();
|
||||
alistUserMigrationDto.setId(userMigrationListForm.getId().trim());
|
||||
alistUserMigrationDto.setPassword(userMigrationListForm.getPassword());
|
||||
EltownUserMigrationDto eltownUserMigrationDto = new EltownUserMigrationDto();
|
||||
eltownUserMigrationDto.setId(userMigrationListForm.getId().trim());
|
||||
eltownUserMigrationDto.setPassword(userMigrationListForm.getPassword());
|
||||
UserMigrationListVo userMigrationListVo = new UserMigrationListVo();
|
||||
List<UserMigrationItemVo> alistUserList = new ArrayList<>();
|
||||
for (AlistUserMigrationVo sourceVo : alistUserMigrationService.selectAlistUserMigrationList(alistUserMigrationDto)) { UserMigrationItemVo itemVo = new UserMigrationItemVo(); itemVo.setId(sourceVo.getId()); itemVo.setName(sourceVo.getName()); itemVo.setHp(sourceVo.getHp()); itemVo.setEmail(sourceVo.getEmail()); itemVo.setType(sourceVo.getType()); itemVo.setJoinDate(sourceVo.getJoinDate()); alistUserList.add(itemVo); }
|
||||
List<UserMigrationItemVo> eltownUserList = new ArrayList<>();
|
||||
for (EltownUserMigrationVo sourceVo : eltownUserMigrationService.selectEltownUserMigrationList(eltownUserMigrationDto)) { UserMigrationItemVo itemVo = new UserMigrationItemVo(); itemVo.setId(sourceVo.getId()); itemVo.setName(sourceVo.getName()); itemVo.setHp(sourceVo.getHp()); itemVo.setEmail(sourceVo.getEmail()); itemVo.setType(sourceVo.getType()); itemVo.setJoinDate(sourceVo.getJoinDate()); eltownUserList.add(itemVo); }
|
||||
userMigrationListVo.setAlistUserList(alistUserList); userMigrationListVo.setEltownUserList(eltownUserList); return userMigrationListVo;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserProfileModifyVo updateUserProfileModify(UserProfileModifyForm userProfileModifyForm) {
|
||||
UserPasswordCheckVo userPasswordCheckVo = passwordCheck(userProfileModifyForm.getCurrentPassword()); UserProfileModifyVo userProfileModifyVo = new UserProfileModifyVo();
|
||||
if (!userPasswordCheckVo.isPasswordMatched()) { userProfileModifyVo.setResultCode(userPasswordCheckVo.getResultCode()); return userProfileModifyVo; }
|
||||
if (userProfileModifyForm.getEmail() == null && userProfileModifyForm.getHp() == null) { userProfileModifyVo.setResultCode(4001); return userProfileModifyVo; }
|
||||
TestUserUserPasswordViewDto dto = new TestUserUserPasswordViewDto(); dto.setUserTokenIdx(SecurityUtil.getLoginUserTokenIdx()); TestUserUserPasswordViewVo passwordVo = testUserUserService.testUserUserPasswordView(dto);
|
||||
TestUserModifyDto testUserModifyDto = new TestUserModifyDto(); testUserModifyDto.setUserIdx(passwordVo.getUserIdx()); testUserModifyDto.setEmail(userProfileModifyForm.getEmail()); testUserModifyDto.setHp(userProfileModifyForm.getHp());
|
||||
var coreVo = testUserService.updateTestUserModify(testUserModifyDto); userProfileModifyVo.setProfileUpdated(coreVo.isUpdated()); userProfileModifyVo.setResultCode(coreVo.getResultCode()); return userProfileModifyVo;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserWithdrawModifyVo updateUserWithdrawModify(UserWithdrawModifyForm userWithdrawModifyForm) {
|
||||
UserWithdrawModifyVo userWithdrawModifyVo = new UserWithdrawModifyVo(); Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx(); if (userTokenIdx == null) { userWithdrawModifyVo.setResultCode(4003); return userWithdrawModifyVo; }
|
||||
TestUserUserPasswordViewDto dto = new TestUserUserPasswordViewDto(); dto.setUserTokenIdx(userTokenIdx); TestUserUserPasswordViewVo passwordVo = testUserUserService.testUserUserPasswordView(dto); if (passwordVo == null) { userWithdrawModifyVo.setResultCode(2003); return userWithdrawModifyVo; }
|
||||
TestUserModifyDto testUserModifyDto = new TestUserModifyDto(); testUserModifyDto.setUserIdx(passwordVo.getUserIdx()); testUserModifyDto.setWithdrawStatus(userWithdrawModifyForm.getWithdrawStatus()); var coreVo = testUserService.updateTestUserModify(testUserModifyDto); userWithdrawModifyVo.setWithdrawStatusUpdated(coreVo.isUpdated()); userWithdrawModifyVo.setResultCode(coreVo.getResultCode()); return userWithdrawModifyVo;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserDormantModifyVo updateUserDormantModify(UserDormantModifyForm userDormantModifyForm) {
|
||||
UserDormantModifyVo userDormantModifyVo = new UserDormantModifyVo(); Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx(); if (userTokenIdx == null) { userDormantModifyVo.setResultCode(4003); return userDormantModifyVo; }
|
||||
TestUserUserPasswordViewDto dto = new TestUserUserPasswordViewDto(); dto.setUserTokenIdx(userTokenIdx); TestUserUserPasswordViewVo passwordVo = testUserUserService.testUserUserPasswordView(dto); if (passwordVo == null) { userDormantModifyVo.setResultCode(2003); return userDormantModifyVo; }
|
||||
TestUserModifyDto testUserModifyDto = new TestUserModifyDto(); testUserModifyDto.setUserIdx(passwordVo.getUserIdx()); testUserModifyDto.setDormantYn(userDormantModifyForm.getDormantYn()); var coreVo = testUserService.updateTestUserModify(testUserModifyDto); userDormantModifyVo.setDormantStatusUpdated(coreVo.isUpdated()); userDormantModifyVo.setResultCode(coreVo.getResultCode()); return userDormantModifyVo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.alist.api.front.modules.user.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 회원가입 응답") @Getter @Setter
|
||||
public class UserAddVo {
|
||||
@Schema(description = "회원 PK", example = "2") private Integer userIdx;
|
||||
@Schema(description = "회원 아이디", example = "teacher02") private String id;
|
||||
@Schema(description = "회원 권한", example = "I") private String userRole;
|
||||
@Schema(description = "회원 유형", example = "T") private String userType;
|
||||
@JsonIgnore @Schema(hidden = true) private int resultCode;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.alist.api.front.modules.user.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 휴면 상태 변경 응답") @Getter @Setter
|
||||
public class UserDormantModifyVo {
|
||||
@Schema(description = "휴면 상태 변경 여부", example = "true") private boolean dormantStatusUpdated;
|
||||
@JsonIgnore @Schema(hidden = true) private int resultCode;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.alist.api.front.modules.user.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "이전 회원 항목") @Getter @Setter
|
||||
public class UserMigrationItemVo {
|
||||
@Schema(description = "회원 아이디", example = "test") private String id;
|
||||
@Schema(description = "회원 이름", example = "홍길동") private String name;
|
||||
@Schema(description = "휴대폰 번호", example = "01012345678") private String hp;
|
||||
@Schema(description = "이메일", example = "user@example.com") private String email;
|
||||
@Schema(description = "회원 구분", example = "교사") private String type;
|
||||
@Schema(description = "가입일", example = "2026-05-19") private String joinDate;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.alist.api.front.modules.user.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "이전 회원 조회 응답") @Getter @Setter
|
||||
public class UserMigrationListVo {
|
||||
@Schema(description = "ALIST 이전 회원 목록") private List<UserMigrationItemVo> alistUserList;
|
||||
@Schema(description = "ELTOWN 이전 회원 목록") private List<UserMigrationItemVo> eltownUserList;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.alist.api.front.modules.user.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 비밀번호 확인 응답") @Getter @Setter
|
||||
public class UserPasswordCheckVo {
|
||||
@Schema(description = "비밀번호 일치 여부", example = "true") private boolean passwordMatched;
|
||||
@JsonIgnore @Schema(hidden = true) private int resultCode;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.alist.api.front.modules.user.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 비밀번호 수정 응답") @Getter @Setter
|
||||
public class UserPasswordModifyVo {
|
||||
@Schema(description = "비밀번호 수정 여부", example = "true") private boolean passwordChanged;
|
||||
@JsonIgnore @Schema(hidden = true) private int resultCode;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.alist.api.front.modules.user.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 개인정보 수정 응답") @Getter @Setter
|
||||
public class UserProfileModifyVo {
|
||||
@Schema(description = "개인정보 수정 여부", example = "true") private boolean profileUpdated;
|
||||
@JsonIgnore @Schema(hidden = true) private int resultCode;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.alist.api.front.modules.user.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 개인정보 조회 응답") @Getter @Setter
|
||||
public class UserProfileViewVo {
|
||||
@Schema(description = "회원 PK", example = "1") private Integer userIdx;
|
||||
@Schema(description = "회원 아이디", example = "test") private String id;
|
||||
@Schema(description = "이메일", example = "user@example.com") private String email;
|
||||
@Schema(description = "휴대폰 번호", example = "01012345678") private String hp;
|
||||
@Schema(description = "회원 권한", example = "CCM") private String userRole;
|
||||
@Schema(description = "회원 유형", example = "T") private String userType;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.alist.api.front.modules.user.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Schema(description = "사용자 탈퇴 상태 변경 응답") @Getter @Setter
|
||||
public class UserWithdrawModifyVo {
|
||||
@Schema(description = "탈퇴 상태 변경 여부", example = "true") private boolean withdrawStatusUpdated;
|
||||
@JsonIgnore @Schema(hidden = true) private int resultCode;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
spring:
|
||||
application:
|
||||
name: api
|
||||
datasource:
|
||||
url: jdbc:log4jdbc:mariadb://121.160.234.222:3000/ALISTLMS?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul&connectionCollation=utf8mb4_uca1400_ai_ci
|
||||
username: alist_dev
|
||||
password: 1qaz2wsx!@
|
||||
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
minimum-idle: 5
|
||||
connection-timeout: 10000
|
||||
idle-timeout: 600000
|
||||
data:
|
||||
redis:
|
||||
host: 121.160.234.222
|
||||
port: 3001
|
||||
password: 1qaz2wsx!@
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: -1
|
||||
max-request-size: -1
|
||||
|
||||
migration:
|
||||
datasource:
|
||||
alist:
|
||||
jdbc-url: jdbc:log4jdbc:sqlserver://bigfuntnp.co.kr:51433;databaseName=alist_dev;encrypt=false;trustServerCertificate=true
|
||||
username: bigfuntnp
|
||||
password: password12!@
|
||||
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
|
||||
maximum-pool-size: 5
|
||||
minimum-idle: 1
|
||||
connection-timeout: 10000
|
||||
eltown:
|
||||
jdbc-url: jdbc:log4jdbc:sqlserver://bigfuntnp.co.kr:51433;databaseName=eltown;encrypt=false;trustServerCertificate=true
|
||||
username: bigfuntnp
|
||||
password: password12!@
|
||||
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
|
||||
maximum-pool-size: 5
|
||||
minimum-idle: 1
|
||||
connection-timeout: 10000
|
||||
|
||||
logging:
|
||||
config: classpath:logback-local.xml
|
||||
|
||||
jwt:
|
||||
secret: "F9k3s!29dkF#1lP0X9QZx8eW!2m@0AbD"
|
||||
access-token-validity-seconds: 3600
|
||||
refresh-token-validity-seconds: 2592000
|
||||
|
||||
cookie:
|
||||
secure: false # (HTTP) -> false
|
||||
domain: # (HTTP) -> 비워두세요
|
||||
name: ALIST_SSO
|
||||
same-site: Lax # (HTTP) -> Lax
|
||||
|
||||
swagger:
|
||||
login:
|
||||
id: alist
|
||||
password: "1qaz2wsx!@"
|
||||
|
||||
tus-file:
|
||||
upload:
|
||||
tus-endpoint: https://file-alist.pjt.kr/tus/file/
|
||||
public-base-url: https://file-alist.pjt.kr
|
||||
tmp-root: /srv/project/alist/uploads/tmp
|
||||
final-root: /srv/project/alist/uploads
|
||||
interrupt-seconds: 30
|
||||
auth-cache:
|
||||
ttl-seconds: 20
|
||||
|
||||
# types:
|
||||
# notice:
|
||||
# folder: notice 저장폴더
|
||||
# max-size: 20MB 최대 용량
|
||||
# image-only: false
|
||||
# resize:
|
||||
# enabled: true
|
||||
# width: 800 고정넓이
|
||||
# height: 600 고정높이
|
||||
# max-width: 1200 최대 넓이
|
||||
|
||||
file:
|
||||
upload:
|
||||
view:
|
||||
file-domain: http://localhost:8110
|
||||
root-path: ${tus-file.upload.final-root}
|
||||
max-size: 10MB
|
||||
allowed-extensions: [jpg, jpeg, png, gif, pdf, hwp, hwpx, doc, docx, xls, xlsx, ppt, pptx, txt, csv, zip]
|
||||
types:
|
||||
profile:
|
||||
folder: profile
|
||||
max-size: 5MB
|
||||
image-only: true
|
||||
resize:
|
||||
enabled: true
|
||||
width: 400
|
||||
height: 400
|
||||
notice:
|
||||
folder: notice
|
||||
max-size: 20MB
|
||||
image-only: false
|
||||
resize:
|
||||
enabled: true
|
||||
max-width: 1200
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
|
||||
sso:
|
||||
session:
|
||||
ttl-seconds: 7200
|
||||
code:
|
||||
ttl-seconds: 300
|
||||
cookie:
|
||||
name: ALIST_SSO
|
||||
@@ -0,0 +1,121 @@
|
||||
spring:
|
||||
application:
|
||||
name: api
|
||||
main:
|
||||
banner-mode: log
|
||||
datasource:
|
||||
url: jdbc:log4jdbc:mariadb://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul&connectionCollation=utf8mb4_uca1400_ai_ci
|
||||
username: ${DB_USERNAME}
|
||||
password: ${DB_PASSWORD}
|
||||
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
minimum-idle: 5
|
||||
connection-timeout: 10000
|
||||
idle-timeout: 600000
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST}
|
||||
port: ${REDIS_PORT}
|
||||
password: ${REDIS_PASSWORD}
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: -1
|
||||
max-request-size: -1
|
||||
|
||||
migration:
|
||||
datasource:
|
||||
alist:
|
||||
jdbc-url: jdbc:log4jdbc:sqlserver://${ALIST_DB_HOST}:${ALIST_DB_PORT};databaseName=${ALIST_DB_NAME};encrypt=false;trustServerCertificate=true
|
||||
username: ${ALIST_DB_USERNAME}
|
||||
password: ${ALIST_DB_PASSWORD}
|
||||
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
|
||||
maximum-pool-size: 5
|
||||
minimum-idle: 1
|
||||
connection-timeout: 10000
|
||||
eltown:
|
||||
jdbc-url: jdbc:log4jdbc:sqlserver://${ELTOWN_DB_HOST}:${ELTOWN_DB_PORT};databaseName=${ELTOWN_DB_NAME};encrypt=false;trustServerCertificate=true
|
||||
username: ${ELTOWN_DB_USERNAME}
|
||||
password: ${ELTOWN_DB_PASSWORD}
|
||||
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
|
||||
maximum-pool-size: 5
|
||||
minimum-idle: 1
|
||||
connection-timeout: 10000
|
||||
|
||||
logging:
|
||||
config: classpath:logback-pjt.xml
|
||||
|
||||
jwt:
|
||||
secret: ${JWT_SECRET}
|
||||
access-token-validity-seconds: 3600
|
||||
refresh-token-validity-seconds: 2592000
|
||||
|
||||
cookie:
|
||||
secure: true # 프로젝트 환경 (HTTPS)
|
||||
domain: api-alist.pjt.kr
|
||||
name: ALIST_SSO
|
||||
same-site: None
|
||||
|
||||
swagger:
|
||||
login:
|
||||
id: ${SWAGGER_ID}
|
||||
password: ${SWAGGER_PASSWORD}
|
||||
|
||||
tus-file:
|
||||
upload:
|
||||
tus-endpoint: https://file-alist.pjt.kr/tus/file/
|
||||
public-base-url: https://file-alist.pjt.kr
|
||||
tmp-root: /srv/project/alist/uploads/tmp
|
||||
final-root: /srv/project/alist/uploads
|
||||
interrupt-seconds: 30
|
||||
auth-cache:
|
||||
ttl-seconds: 20
|
||||
|
||||
# types:
|
||||
# notice:
|
||||
# folder: notice 저장폴더
|
||||
# max-size: 20MB 최대 용량
|
||||
# image-only: false
|
||||
# resize:
|
||||
# enabled: true
|
||||
# width: 800 고정넓이
|
||||
# height: 600 고정높이
|
||||
# max-width: 1200 최대 넓이
|
||||
|
||||
file:
|
||||
upload:
|
||||
view:
|
||||
file-domain: https://file-alist.pjt.kr
|
||||
root-path: ${tus-file.upload.final-root}
|
||||
max-size: 10MB
|
||||
allowed-extensions: [jpg, jpeg, png, gif, pdf, hwp, hwpx, doc, docx, xls, xlsx, ppt, pptx, txt, csv, zip]
|
||||
types:
|
||||
profile:
|
||||
folder: profile
|
||||
max-size: 5MB
|
||||
image-only: true
|
||||
resize:
|
||||
enabled: true
|
||||
width: 400
|
||||
height: 400
|
||||
notice:
|
||||
folder: notice
|
||||
max-size: 20MB
|
||||
image-only: false
|
||||
resize:
|
||||
enabled: true
|
||||
max-width: 1200
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
|
||||
sso:
|
||||
session:
|
||||
ttl-seconds: 7200
|
||||
code:
|
||||
ttl-seconds: 300
|
||||
cookie:
|
||||
name: ALIST_SSO
|
||||
@@ -0,0 +1,47 @@
|
||||
server:
|
||||
port: 8112
|
||||
forward-headers-strategy: framework
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: api-front
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
tags-sorter: alpha # 태그 알파벳 순 정렬
|
||||
operations-sorter: alpha # API 알파벳 순 정렬
|
||||
api-docs:
|
||||
path: /v3/api-docs
|
||||
|
||||
# API version rule: MAJOR.MINOR.PATCH
|
||||
# MAJOR: 기존 API와 호환되지 않는 변경
|
||||
# - 요청/응답 필드명 변경 또는 삭제
|
||||
# - URL, HTTP Method, 인증 방식 변경
|
||||
# - 기존 클라이언트가 수정 없이는 사용할 수 없는 변경
|
||||
#
|
||||
# MINOR: 기존 API와 호환되는 기능 추가
|
||||
# - 신규 API 추가
|
||||
# - 응답 필드 추가
|
||||
# - 선택 요청 파라미터 추가
|
||||
# - 기존 동작을 깨지 않는 기능 확장
|
||||
#
|
||||
# PATCH: 호환성에 영향 없는 수정
|
||||
# - 버그 수정
|
||||
# - 문서/Swagger 설명 수정
|
||||
# - 내부 로직 개선
|
||||
# - 성능 개선
|
||||
api:
|
||||
version: v1.0.1
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
health:
|
||||
db:
|
||||
enabled: false
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
q p ,,
|
||||
db \ / `7MMF' db mm
|
||||
;MM:o=--*--=o MM MM
|
||||
,V^MM. / \ MM `7MM ,pP"Ybd mmMMmm
|
||||
,M `MM d b MM MM 8I `" MM
|
||||
AbmmmqMA MM , MM `YMMMa. MM
|
||||
A' VML MM ,M MM L. I8 MM
|
||||
.AMA. .AMMA. .JMMmmmmMMM .JMML.M9mmmP' `Mbmo
|
||||
|
||||
|
||||
:: ALIST API ::
|
||||
Application : ${spring.application.name}
|
||||
Profile : ${spring.profiles.active}
|
||||
Spring Boot : ${spring-boot.version}
|
||||
Java : ${java.version}
|
||||
Port : ${server.port}
|
||||
PID : ${PID}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<!-- 로그 디렉토리 -->
|
||||
<property name="LOG_PATH" value="/logs" />
|
||||
|
||||
<!-- Spring Boot가 제공하는 %clr / 색상 변환기 등록 -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
|
||||
<!-- 콘솔 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%clr(%d{HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 콘솔 appender: Boot 기본 제공 -->
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
|
||||
<!-- ==== 로컬(local) ==== -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
<!-- springframework -->
|
||||
<logger name="org.springframework" level="INFO"/>
|
||||
|
||||
<!-- project -->
|
||||
<logger name="com.alist.api" level="DEBUG"/>
|
||||
|
||||
<!-- MyBatis SQL/파라미터 보고 싶으면 DEBUG -->
|
||||
<logger name="org.mybatis" level="DEBUG"/>
|
||||
<logger name="org.apache.ibatis" level="DEBUG"/>
|
||||
|
||||
<!-- log4jdbc -->
|
||||
<logger name="jdbc.sqltiming" level="DEBUG"/>
|
||||
<logger name="jdbc.resultsettable" level="DEBUG"/>
|
||||
<logger name="jdbc.audit" level="OFF"/>
|
||||
<logger name="jdbc.resultset" level="OFF"/>
|
||||
<logger name="jdbc.sqlonly" level="OFF"/>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<!-- 로그 디렉토리 -->
|
||||
<property name="LOG_PATH" value="/logs" />
|
||||
<property name="LOG_FILE_NAME" value="api-front" />
|
||||
|
||||
<!-- 파일 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
|
||||
<!-- 현재 로그는 고정 위치 (tail 편함) -->
|
||||
<file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
|
||||
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
|
||||
<!-- 롤링 파일은 날짜 폴더(yyyy/MM) 하위로 저장 -->
|
||||
<fileNamePattern>
|
||||
${LOG_PATH}/%d{yyyy/MM}/${LOG_FILE_NAME}.log-%d{yyyy-MM-dd}.%i
|
||||
</fileNamePattern>
|
||||
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
|
||||
<maxHistory>180</maxHistory>
|
||||
<cleanHistoryOnStart>true</cleanHistoryOnStart>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} | %-5level | %-15thread | %-40logger{40} | %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- ==== 빅펀개발(pjt) ==== -->
|
||||
<!-- 기본 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
|
||||
<!-- springframework -->
|
||||
<logger name="org.springframework" level="WARN"/>
|
||||
|
||||
<!-- project -->
|
||||
<logger name="com.alist.api.front" level="INFO"/>
|
||||
|
||||
<!-- MyBatis -->
|
||||
<logger name="org.mybatis" level="WARN"/>
|
||||
<logger name="org.apache.ibatis" level="WARN"/>
|
||||
|
||||
<!-- log4jdbc -->
|
||||
<logger name="jdbc.sqltiming" level="INFO"/>
|
||||
<logger name="jdbc.resultsettable" level="OFF"/>
|
||||
<logger name="jdbc.audit" level="OFF"/>
|
||||
<logger name="jdbc.resultset" level="OFF"/>
|
||||
<logger name="jdbc.sqlonly" level="OFF"/>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
Reference in New Issue
Block a user