Merge branch 'feat/dev_251201' of https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice into feat/dev_251201

This commit is contained in:
Harry M. You
2025-12-04 15:59:57 +09:00
16 changed files with 161 additions and 61 deletions

View File

@@ -1,4 +1,4 @@
package com.kamco.cd.kamcoback.config;
package com.kamco.cd.kamcoback.auth;
import java.security.SecureRandom;
import java.util.Base64;

View File

@@ -39,6 +39,15 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String path = request.getServletPath();
// 여기에 JWT 필터를 타지 않게 할 URL 패턴들 작성
return path.startsWith("/api/auth/signin") || path.startsWith("/api/auth/refresh");
// 필요하면 "/api/auth/logout" 도 추가
}
private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if (bearer != null && bearer.startsWith("Bearer ")) {

View File

@@ -3,6 +3,8 @@ package com.kamco.cd.kamcoback.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 io.swagger.v3.oas.models.servers.Server;
import java.util.List;
import org.springframework.context.annotation.Bean;
@@ -13,6 +15,21 @@ public class OpenApiConfig {
@Bean
public OpenAPI kamcoOpenAPI() {
// 🔹 1) SecurityScheme 정의 (Bearer JWT)
SecurityScheme bearerAuth =
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.in(SecurityScheme.In.HEADER)
.name("Authorization");
// 🔹 2) SecurityRequirement (기본으로 BearerAuth 사용)
SecurityRequirement securityRequirement = new SecurityRequirement().addList("BearerAuth");
// 🔹 3) Components 에 SecurityScheme 등록
Components components = new Components().addSecuritySchemes("BearerAuth", bearerAuth);
return new OpenAPI()
.info(
new Info()
@@ -32,6 +49,8 @@ public class OpenApiConfig {
new Server().url("https://kamco.dev-api.gs.dabeeo.com").description("개발 서버")
// , new Server().url("https://api.kamco.com").description("운영 서버")
))
.components(new Components());
.components(new Components())
// 🔥 여기 한 줄이 "모든 API 기본적으로 BearerAuth 요구" 의미
.addSecurityItem(securityRequirement);
}
}

View File

@@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.config;
import com.kamco.cd.kamcoback.auth.CustomAuthenticationProvider;
import com.kamco.cd.kamcoback.auth.JwtAuthenticationFilter;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -11,6 +12,9 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
@EnableWebSecurity
@@ -22,22 +26,23 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable()) // CSRF 보안 기능 비활성화
.sessionManagement(
sm ->
sm.sessionCreationPolicy(
SessionCreationPolicy.STATELESS)) // 서버 세션 만들지 않음 요청은 JWT 인증
.formLogin(form -> form.disable()) // react에서 로그인 요청 관리
.httpBasic(basic -> basic.disable()) // 기본 basic 인증 비활성화 JWT 인증사용
.logout(logout -> logout.disable()) // 기본 로그아웃 비활성화 JWT는 서버 상태가 없으므로 로그아웃 처리 필요 없음
.authenticationProvider(
customAuthenticationProvider) // 로그인 패스워드 비교방식 스프링 기본 Provider 사용안함 커스텀 사용
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
// http.csrf(csrf -> csrf.disable()) // CSRF 보안 기능 비활성화
// .sessionManagement(
// sm ->
// sm.sessionCreationPolicy(
// SessionCreationPolicy.STATELESS)) // 서버 세션 만들지 않음 요청은 JWT 인증
// .formLogin(form -> form.disable()) // react에서 로그인 요청 관리
// .httpBasic(basic -> basic.disable()) // 기본 basic 인증 비활성화 JWT 인증사용
// .logout(logout -> logout.disable()) // 기본 로그아웃 비활성화 JWT는 서버 상태가 없으므로 로그아웃 처리 필요 없음
// .authenticationProvider(
// customAuthenticationProvider) // 로그인 패스워드 비교방식 스프링 기본 Provider 사용안함 커스텀 사용
// .authorizeHttpRequests(
// auth ->
// auth.requestMatchers(
// "/api/auth/signin",
// "/api/auth/refresh",
// "/swagger-ui/**",
// "/v3/api-docs/**") // 로그인 없이 접근가능 url
// "/v3/api-docs/**")
// .permitAll()
// .anyRequest()
// .authenticated())
@@ -45,6 +50,15 @@ public class SecurityConfig {
// jwtAuthenticationFilter,
// UsernamePasswordAuthenticationFilter
// .class) // 요청 들어오면 먼저 JWT 토큰 검사 후 security context 에 사용자 정보 저장.
http.csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.formLogin(form -> form.disable())
.httpBasic(basic -> basic.disable())
.logout(logout -> logout.disable())
.authenticationProvider(customAuthenticationProvider)
.authorizeHttpRequests(
auth -> auth.anyRequest().permitAll() // 🔥 인증 필요 없음
);
;
return http.build();
@@ -55,4 +69,18 @@ public class SecurityConfig {
throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성
config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header
config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
/** "/**" → 모든 API 경로에 대해 이 CORS 규칙을 적용 /api/** 같이 특정 경로만 지정 가능. */
source.registerCorsConfiguration("/**", config); // CORS 정책을 등록
return source;
}
}

View File

@@ -0,0 +1,10 @@
package com.kamco.cd.kamcoback.config;
// @Configuration
// @SecurityScheme(
// name = "BearerAuth",
// type = SecuritySchemeType.HTTP,
// scheme = "bearer",
// bearerFormat = "JWT"
// )
public class SwaggerConfig {}

View File

@@ -10,22 +10,11 @@ import org.locationtech.jts.geom.Polygon;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**") // 모든 URL 허용
.allowedOriginPatterns("*")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true);
}
@Bean
public ObjectMapper objectMapper() {
SimpleModule module = new SimpleModule();

View File

@@ -9,6 +9,7 @@ import java.util.stream.Collectors;
import org.springframework.web.util.ContentCachingRequestWrapper;
public class ApiLogFunction {
// 클라이언트 IP 추출
public static String getClientIp(HttpServletRequest request) {
String[] headers = {
@@ -34,6 +35,7 @@ public class ApiLogFunction {
// 사용자 ID 추출 예시 (Spring Security 기준)
public static String getUserId(HttpServletRequest request) {
try {
Object userId = request.getUserPrincipal();
return request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : null;
} catch (Exception e) {
return null;
@@ -45,8 +47,12 @@ public class ApiLogFunction {
String uri = request.getRequestURI().toLowerCase();
// URL 기반 DOWNLOAD/PRINT 분류
if (uri.contains("/download") || uri.contains("/export")) return EventType.DOWNLOAD;
if (uri.contains("/print")) return EventType.PRINT;
if (uri.contains("/download") || uri.contains("/export")) {
return EventType.DOWNLOAD;
}
if (uri.contains("/print")) {
return EventType.PRINT;
}
// 일반 CRUD
return switch (method) {
@@ -97,7 +103,9 @@ public class ApiLogFunction {
// JSON Body 읽기
public static String getBodyData(ContentCachingRequestWrapper request) {
byte[] buf = request.getContentAsByteArray();
if (buf.length == 0) return null;
if (buf.length == 0) {
return null;
}
try {
return new String(buf, request.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {

View File

@@ -50,7 +50,6 @@ public class MapSheetMngApiController {
return ApiResponseDto.createOK(mapSheetMngService.getFolderAll(srchDto));
}
@Operation(summary = "지정폴더내 파일목록 조회", description = "지정폴더내 파일목록 조회")
@ApiResponses(
value = {

View File

@@ -1,6 +1,5 @@
package com.kamco.cd.kamcoback.mapsheet.dto;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.util.List;
@@ -43,7 +42,6 @@ public class FileDto {
@Schema(description = "파일종료위치", example = "100")
@NotNull
private Integer endPos;
}
@Getter

View File

@@ -183,7 +183,6 @@ public class MapSheetMngService {
}
}
return new FilesDto(dirPath, fileTotCnt, fileTotSize, files);
}
@@ -207,7 +206,6 @@ public class MapSheetMngService {
int fileTotCnt = 0;
long fileTotSize = 0;
try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {
fileDtoList =
@@ -244,8 +242,6 @@ public class MapSheetMngService {
fileTotCnt = fileDtoList.size();
fileTotSize = fileDtoList.stream().mapToLong(FileDto.Basic::getFileSize).sum();
} catch (IOException e) {
System.err.println("파일 I/O 오류 발생: " + e.getMessage());
}

View File

@@ -1,6 +1,6 @@
package com.kamco.cd.kamcoback.members.service;
import com.kamco.cd.kamcoback.config.BCryptSaltGenerator;
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
import com.kamco.cd.kamcoback.members.dto.MembersDto;
import com.kamco.cd.kamcoback.postgres.core.MembersCoreService;
import lombok.RequiredArgsConstructor;

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.members.service;
import com.kamco.cd.kamcoback.auth.CustomUserDetails;
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -17,9 +18,10 @@ public class MemberDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UUID uuid = UUID.fromString(username);
MemberEntity member =
membersRepository
.findByEmployeeNo(username)
.findByUUID(uuid)
.orElseThrow(() -> new UsernameNotFoundException("USER NOT FOUND"));
return new CustomUserDetails(member);

View File

@@ -1,7 +1,7 @@
package com.kamco.cd.kamcoback.members.service;
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.config.BCryptSaltGenerator;
import com.kamco.cd.kamcoback.members.dto.MembersDto;
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
import com.kamco.cd.kamcoback.postgres.core.MembersCoreService;

View File

@@ -1,10 +1,11 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.config.BCryptSaltGenerator;
import com.kamco.cd.kamcoback.auth.BCryptSaltGenerator;
import com.kamco.cd.kamcoback.members.dto.MembersDto;
import com.kamco.cd.kamcoback.members.exception.MemberException;
import com.kamco.cd.kamcoback.members.exception.MemberException.MemberNotFoundException;
import com.kamco.cd.kamcoback.postgres.entity.MemberArchivedEntity;
import com.kamco.cd.kamcoback.postgres.entity.MemberArchivedEntityId;
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
import com.kamco.cd.kamcoback.postgres.entity.MemberRoleEntity;
import com.kamco.cd.kamcoback.postgres.entity.MemberRoleEntityId;
@@ -161,9 +162,12 @@ public class MembersCoreService {
.findByUUID(statusDto.getUuid())
.orElseThrow(() -> new MemberNotFoundException());
MemberArchivedEntityId memberArchivedEntityId = new MemberArchivedEntityId();
memberArchivedEntityId.setUserId(memberEntity.getId());
memberArchivedEntityId.setUuid(memberEntity.getUuid());
MemberArchivedEntity memberArchivedEntity = new MemberArchivedEntity();
memberArchivedEntity.setUuid(memberEntity.getUuid());
memberArchivedEntity.setId(memberEntity.getId());
memberArchivedEntity.setId(memberArchivedEntityId);
memberArchivedEntity.setEmployeeNo(memberEntity.getEmployeeNo());
memberArchivedEntity.setName(memberEntity.getName());
memberArchivedEntity.setPassword(memberEntity.getPassword());

View File

@@ -1,13 +1,12 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.ZonedDateTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
@@ -18,12 +17,7 @@ import org.hibernate.annotations.ColumnDefault;
@Table(name = "tb_member_archived")
public class MemberArchivedEntity {
@Id
@Column(name = "uuid", nullable = false)
private UUID uuid;
@Column(name = "id", nullable = false)
private Long id;
@EmbeddedId private MemberArchivedEntityId id;
@Size(max = 50)
@Column(name = "employee_no", length = 50)

View File

@@ -0,0 +1,44 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.Hibernate;
@Getter
@Setter
@Embeddable
public class MemberArchivedEntityId implements Serializable {
private static final long serialVersionUID = -7102800377481389036L;
@NotNull
@Column(name = "user_id", nullable = false)
private Long userId;
@NotNull
@Column(name = "uuid", nullable = false)
private UUID uuid;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
return false;
}
MemberArchivedEntityId entity = (MemberArchivedEntityId) o;
return Objects.equals(this.userId, entity.userId) && Objects.equals(this.uuid, entity.uuid);
}
@Override
public int hashCode() {
return Objects.hash(userId, uuid);
}
}