jwt 수정중

This commit is contained in:
2025-12-04 12:26:18 +09:00
parent 899f86d4a1
commit 18fcc2361e
15 changed files with 128 additions and 73 deletions

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,29 +26,39 @@ 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());
// auth.requestMatchers(
// "/api/auth/signin",
// "/api/auth/refresh",
// "/swagger-ui/**",
// "/v3/api-docs/**") // 로그인 없이 접근가능 url
// .permitAll()
// .anyRequest()
// .authenticated())
// .addFilterBefore(
// jwtAuthenticationFilter,
// UsernamePasswordAuthenticationFilter
// .class) // 요청 들어오면 먼저 JWT 토큰 검사 후 security context 에 사용자 정보 저장.
// 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/**")
// .permitAll()
// .anyRequest()
// .authenticated())
// .addFilterBefore(
// 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

@@ -49,7 +49,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

@@ -182,7 +182,6 @@ public class MapSheetMngService {
}
}
return new FilesDto(dirPath, fileTotCnt, fileTotSize, files);
}
@@ -194,7 +193,7 @@ public class MapSheetMngService {
String extension = srchDto.getExtension();
String sortType = srchDto.getSortType();
int startPos = srchDto.getStartPos();
int startPos = srchDto.getStartPos();
int endPos = srchDto.getEndPos();
int limit = endPos - startPos + 1;
@@ -206,7 +205,6 @@ public class MapSheetMngService {
int fileTotCnt = 0;
long fileTotSize = 0;
try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {
fileDtoList =
@@ -243,8 +241,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

@@ -41,12 +41,12 @@ public class MembersCoreService {
public Long saveMembers(MembersDto.AddReq addReq) {
if (membersRepository.existsByEmployeeNo(addReq.getEmployeeNo())) {
throw new MemberException.DuplicateMemberException(
MemberException.DuplicateMemberException.Field.EMPLOYEE_NO, addReq.getEmployeeNo());
MemberException.DuplicateMemberException.Field.EMPLOYEE_NO, addReq.getEmployeeNo());
}
if (membersRepository.existsByEmail(addReq.getEmail())) {
throw new MemberException.DuplicateMemberException(
MemberException.DuplicateMemberException.Field.EMAIL, addReq.getEmail());
MemberException.DuplicateMemberException.Field.EMAIL, addReq.getEmail());
}
MemberEntity memberEntity = new MemberEntity();
@@ -66,7 +66,7 @@ public class MembersCoreService {
*/
public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) {
MemberEntity memberEntity =
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
if (StringUtils.isNotBlank(memberEntity.getEmployeeNo())) {
memberEntity.setEmployeeNo(updateReq.getEmployeeNo());
@@ -93,13 +93,13 @@ public class MembersCoreService {
public void saveRoles(MembersDto.RolesDto rolesDto) {
MemberEntity memberEntity =
membersRepository
.findByUUID(rolesDto.getUuid())
.orElseThrow(() -> new MemberNotFoundException());
membersRepository
.findByUUID(rolesDto.getUuid())
.orElseThrow(() -> new MemberNotFoundException());
if (memberRoleRepository.findByUuidAndRoleName(rolesDto)) {
throw new MemberException.DuplicateMemberException(
MemberException.DuplicateMemberException.Field.DEFAULT, "중복된 역할이 있습니다.");
MemberException.DuplicateMemberException.Field.DEFAULT, "중복된 역할이 있습니다.");
}
MemberRoleEntityId memberRoleEntityId = new MemberRoleEntityId();
@@ -120,9 +120,9 @@ public class MembersCoreService {
*/
public void deleteRoles(MembersDto.RolesDto rolesDto) {
MemberEntity memberEntity =
membersRepository
.findByUUID(rolesDto.getUuid())
.orElseThrow(() -> new MemberNotFoundException());
membersRepository
.findByUUID(rolesDto.getUuid())
.orElseThrow(() -> new MemberNotFoundException());
MemberRoleEntityId memberRoleEntityId = new MemberRoleEntityId();
memberRoleEntityId.setMemberUuid(rolesDto.getUuid());
@@ -142,9 +142,9 @@ public class MembersCoreService {
*/
public void updateStatus(MembersDto.StatusDto statusDto) {
MemberEntity memberEntity =
membersRepository
.findByUUID(statusDto.getUuid())
.orElseThrow(() -> new MemberNotFoundException());
membersRepository
.findByUUID(statusDto.getUuid())
.orElseThrow(() -> new MemberNotFoundException());
memberEntity.setStatus(statusDto.getStatus());
memberEntity.setUpdatedDttm(ZonedDateTime.now());
@@ -158,9 +158,9 @@ public class MembersCoreService {
*/
public void deleteAccount(MembersDto.StatusDto statusDto) {
MemberEntity memberEntity =
membersRepository
.findByUUID(statusDto.getUuid())
.orElseThrow(() -> new MemberNotFoundException());
membersRepository
.findByUUID(statusDto.getUuid())
.orElseThrow(() -> new MemberNotFoundException());
MemberArchivedEntityId memberArchivedEntityId = new MemberArchivedEntityId();
memberArchivedEntityId.setUserId(memberEntity.getId());
@@ -193,10 +193,10 @@ public class MembersCoreService {
*/
public void resetPassword(Long id) {
MemberEntity memberEntity =
membersRepository.findById(id).orElseThrow(() -> new MemberNotFoundException());
membersRepository.findById(id).orElseThrow(() -> new MemberNotFoundException());
String salt =
BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim());
BCryptSaltGenerator.generateSaltWithEmployeeNo(memberEntity.getEmployeeNo().trim());
// 패스워드 암호화, 초기 패스워드 고정
String hashedPassword = BCrypt.hashpw(password, salt);

View File

@@ -17,8 +17,7 @@ import org.hibernate.annotations.ColumnDefault;
@Table(name = "tb_member_archived")
public class MemberArchivedEntity {
@EmbeddedId
private MemberArchivedEntityId id;
@EmbeddedId private MemberArchivedEntityId id;
@Size(max = 50)
@Column(name = "employee_no", length = 50)
@@ -51,5 +50,4 @@ public class MemberArchivedEntity {
@ColumnDefault("now()")
@Column(name = "archived_dttm", nullable = false)
private ZonedDateTime archivedDttm;
}

View File

@@ -16,6 +16,7 @@ import org.hibernate.Hibernate;
public class MemberArchivedEntityId implements Serializable {
private static final long serialVersionUID = -7102800377481389036L;
@NotNull
@Column(name = "user_id", nullable = false)
private Long userId;
@@ -33,13 +34,11 @@ public class MemberArchivedEntityId implements Serializable {
return false;
}
MemberArchivedEntityId entity = (MemberArchivedEntityId) o;
return Objects.equals(this.userId, entity.userId) &&
Objects.equals(this.uuid, entity.uuid);
return Objects.equals(this.userId, entity.userId) && Objects.equals(this.uuid, entity.uuid);
}
@Override
public int hashCode() {
return Objects.hash(userId, uuid);
}
}