From 0dc32f2b39e2e9e7a0c73a6e19435ee947c5d5d5 Mon Sep 17 00:00:00 2001 From: sohot8653 Date: Wed, 20 Aug 2025 16:22:11 +0900 Subject: [PATCH] =?UTF-8?q?[=ED=9A=8C=EC=9B=90=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80]=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20API=20=EA=B5=AC=ED=98=84,=20JWT=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80.=20Member=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20DTO,=20Entity,=20Mapper,=20Repository,=20Service=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=9C=BC=EB=A1=9C=20=ED=9A=8C=EC=9B=90=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EA=B0=95=ED=99=94?= =?UTF-8?q?=ED=95=98=EA=B3=A0,=20Swagger=20=EC=84=A4=EC=A0=95=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20API=20=EB=AC=B8=EC=84=9C=ED=99=94=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../member/controller/MemberController.java | 36 ++++++++--- .../member/dto/CreateMemberRequestDto.java | 2 +- .../member/dto/CreateMemberResponseDto.java | 4 +- .../member/dto/LoginRequestDto.java | 2 +- .../member/dto/LoginResponseDto.java | 2 +- .../{user => base}/member/dto/MemberDto.java | 4 +- .../{user => base}/member/entity/Member.java | 4 +- .../member/enums/MemberRole.java | 2 +- .../member/mapper/MemberMapper.java | 13 ++-- .../member/repository/MemberRepository.java | 4 +- .../repository/MemberRepositoryCustom.java | 4 +- .../repository/MemberRepositoryImpl.java | 9 ++- .../member/service/MemberService.java | 4 +- .../member/service/MemberServiceImpl.java | 12 ++-- .../bio_backend/global/config/AppConfig.java | 24 ++++++++ .../CustomAuthenticationFailureHandler.java | 3 - ...ilter.java => JwtTokenIssuanceFilter.java} | 20 +++--- ...ter.java => JwtTokenValidationFilter.java} | 61 ++++++++++--------- .../global/security/WebSecurity.java | 48 ++++++--------- .../bio_backend/global/utils/JwtUtils.java | 2 +- src/main/resources/application.properties | 3 +- 22 files changed, 151 insertions(+), 115 deletions(-) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/controller/MemberController.java (74%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/dto/CreateMemberRequestDto.java (89%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/dto/CreateMemberResponseDto.java (78%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/dto/LoginRequestDto.java (89%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/dto/LoginResponseDto.java (86%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/dto/MemberDto.java (92%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/entity/Member.java (89%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/enums/MemberRole.java (94%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/mapper/MemberMapper.java (76%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/repository/MemberRepository.java (77%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/repository/MemberRepositoryCustom.java (94%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/repository/MemberRepositoryImpl.java (94%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/service/MemberService.java (85%) rename src/main/java/com/bio/bio_backend/domain/{user => base}/member/service/MemberServiceImpl.java (93%) rename src/main/java/com/bio/bio_backend/global/security/{JwtAuthenticationFilter.java => JwtTokenIssuanceFilter.java} (89%) rename src/main/java/com/bio/bio_backend/global/security/{JwtTokenFilter.java => JwtTokenValidationFilter.java} (61%) diff --git a/build.gradle b/build.gradle index ccc0e45..f3ad22e 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ repositories { dependencies { developmentOnly 'org.springframework.boot:spring-boot-devtools' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + // PostgreSQL JDBC runtimeOnly 'org.postgresql:postgresql' implementation 'org.springframework.boot:spring-boot-starter-web' @@ -45,6 +46,8 @@ dependencies { // jwt implementation 'io.jsonwebtoken:jjwt-api:0.12.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' // lombok compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java b/src/main/java/com/bio/bio_backend/domain/base/member/controller/MemberController.java similarity index 74% rename from src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java rename to src/main/java/com/bio/bio_backend/domain/base/member/controller/MemberController.java index 284b9fe..4353e2b 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/controller/MemberController.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/controller/MemberController.java @@ -1,18 +1,17 @@ -package com.bio.bio_backend.domain.user.member.controller; +package com.bio.bio_backend.domain.base.member.controller; import com.bio.bio_backend.global.dto.ApiResponseDto; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.*; import jakarta.validation.Valid; -import com.bio.bio_backend.domain.user.member.dto.MemberDto; -import com.bio.bio_backend.domain.user.member.dto.CreateMemberRequestDto; -import com.bio.bio_backend.domain.user.member.dto.CreateMemberResponseDto; -import com.bio.bio_backend.domain.user.member.service.MemberService; -import com.bio.bio_backend.domain.user.member.mapper.MemberMapper; +import com.bio.bio_backend.domain.base.member.dto.MemberDto; +import com.bio.bio_backend.domain.base.member.dto.CreateMemberRequestDto; +import com.bio.bio_backend.domain.base.member.dto.CreateMemberResponseDto; +import com.bio.bio_backend.domain.base.member.service.MemberService; +import com.bio.bio_backend.domain.base.member.mapper.MemberMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import io.swagger.v3.oas.annotations.Operation; @@ -32,7 +31,6 @@ public class MemberController { private final MemberService memberService; private final MemberMapper memberMapper; - private final BCryptPasswordEncoder bCryptPasswordEncoder; @LogExecution("회원 등록") @Operation(summary = "회원 등록", description = "새로운 회원을 등록합니다.") @@ -51,6 +49,28 @@ public class MemberController { return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } + @LogExecution("로그아웃") + @Operation(summary = "로그아웃", description = "사용자 로그아웃을 처리합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "로그아웃 성공"), + @ApiResponse(responseCode = "401", description = "인증 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))) + }) + @PostMapping("/logout") + public ResponseEntity> logout(@RequestHeader("Authorization") String authorization) { + try { + // Authorization 헤더에서 토큰 추출 + String token = authorization.replace("Bearer ", ""); + // Refresh Token 삭제 (실제로는 JWT 블랙리스트나 DB에서 삭제) + // memberService.deleteRefreshToken(userId); + + return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS)); + } catch (Exception e) { + log.error("로그아웃 처리 중 오류 발생: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponseDto.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR)); + } + } + // @PostMapping("/member/list") // public ResponseEntity> getMemberList(@RequestBody(required = false) Map params) { diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDto.java b/src/main/java/com/bio/bio_backend/domain/base/member/dto/CreateMemberRequestDto.java similarity index 89% rename from src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDto.java rename to src/main/java/com/bio/bio_backend/domain/base/member/dto/CreateMemberRequestDto.java index f424b00..f3bafc6 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberRequestDto.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/dto/CreateMemberRequestDto.java @@ -1,4 +1,4 @@ -package com.bio.bio_backend.domain.user.member.dto; +package com.bio.bio_backend.domain.base.member.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java b/src/main/java/com/bio/bio_backend/domain/base/member/dto/CreateMemberResponseDto.java similarity index 78% rename from src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java rename to src/main/java/com/bio/bio_backend/domain/base/member/dto/CreateMemberResponseDto.java index ac45060..04b061d 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/CreateMemberResponseDto.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/dto/CreateMemberResponseDto.java @@ -1,6 +1,6 @@ -package com.bio.bio_backend.domain.user.member.dto; +package com.bio.bio_backend.domain.base.member.dto; -import com.bio.bio_backend.domain.user.member.enums.MemberRole; +import com.bio.bio_backend.domain.base.member.enums.MemberRole; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginRequestDto.java b/src/main/java/com/bio/bio_backend/domain/base/member/dto/LoginRequestDto.java similarity index 89% rename from src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginRequestDto.java rename to src/main/java/com/bio/bio_backend/domain/base/member/dto/LoginRequestDto.java index b5017d0..8f3ead7 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginRequestDto.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/dto/LoginRequestDto.java @@ -1,4 +1,4 @@ -package com.bio.bio_backend.domain.user.member.dto; +package com.bio.bio_backend.domain.base.member.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginResponseDto.java b/src/main/java/com/bio/bio_backend/domain/base/member/dto/LoginResponseDto.java similarity index 86% rename from src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginResponseDto.java rename to src/main/java/com/bio/bio_backend/domain/base/member/dto/LoginResponseDto.java index e3a242c..95c821e 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/LoginResponseDto.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/dto/LoginResponseDto.java @@ -1,4 +1,4 @@ -package com.bio.bio_backend.domain.user.member.dto; +package com.bio.bio_backend.domain.base.member.dto; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDto.java b/src/main/java/com/bio/bio_backend/domain/base/member/dto/MemberDto.java similarity index 92% rename from src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDto.java rename to src/main/java/com/bio/bio_backend/domain/base/member/dto/MemberDto.java index 74151dc..04b689f 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/dto/MemberDto.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/dto/MemberDto.java @@ -1,6 +1,6 @@ -package com.bio.bio_backend.domain.user.member.dto; +package com.bio.bio_backend.domain.base.member.dto; -import com.bio.bio_backend.domain.user.member.enums.MemberRole; +import com.bio.bio_backend.domain.base.member.enums.MemberRole; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java b/src/main/java/com/bio/bio_backend/domain/base/member/entity/Member.java similarity index 89% rename from src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java rename to src/main/java/com/bio/bio_backend/domain/base/member/entity/Member.java index e1ee717..cc544a0 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/entity/Member.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/entity/Member.java @@ -1,6 +1,6 @@ -package com.bio.bio_backend.domain.user.member.entity; +package com.bio.bio_backend.domain.base.member.entity; -import com.bio.bio_backend.domain.user.member.enums.MemberRole; +import com.bio.bio_backend.domain.base.member.enums.MemberRole; import com.bio.bio_backend.global.entity.BaseEntity; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/enums/MemberRole.java b/src/main/java/com/bio/bio_backend/domain/base/member/enums/MemberRole.java similarity index 94% rename from src/main/java/com/bio/bio_backend/domain/user/member/enums/MemberRole.java rename to src/main/java/com/bio/bio_backend/domain/base/member/enums/MemberRole.java index 53014a7..bfce8bd 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/enums/MemberRole.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/enums/MemberRole.java @@ -1,4 +1,4 @@ -package com.bio.bio_backend.domain.user.member.enums; +package com.bio.bio_backend.domain.base.member.enums; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java b/src/main/java/com/bio/bio_backend/domain/base/member/mapper/MemberMapper.java similarity index 76% rename from src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java rename to src/main/java/com/bio/bio_backend/domain/base/member/mapper/MemberMapper.java index 7b90c00..5a04987 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/mapper/MemberMapper.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/mapper/MemberMapper.java @@ -1,10 +1,9 @@ -package com.bio.bio_backend.domain.user.member.mapper; +package com.bio.bio_backend.domain.base.member.mapper; -import com.bio.bio_backend.domain.user.member.dto.CreateMemberRequestDto; -import com.bio.bio_backend.domain.user.member.dto.CreateMemberResponseDto; -import com.bio.bio_backend.domain.user.member.dto.MemberDto; -import com.bio.bio_backend.domain.user.member.entity.Member; -import com.bio.bio_backend.domain.user.member.enums.MemberRole; +import com.bio.bio_backend.domain.base.member.dto.CreateMemberRequestDto; +import com.bio.bio_backend.domain.base.member.dto.CreateMemberResponseDto; +import com.bio.bio_backend.domain.base.member.dto.MemberDto; +import com.bio.bio_backend.domain.base.member.entity.Member; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; @@ -20,7 +19,7 @@ public interface MemberMapper { * 기본값 설정: role = MemberRole.MEMBER, useFlag = true */ @Mapping(target = "oid", ignore = true) - @Mapping(target = "role", expression = "java(com.bio.bio_backend.domain.user.member.enums.MemberRole.getDefault())") + @Mapping(target = "role", expression = "java(com.bio.bio_backend.domain.base.member.enums.MemberRole.getDefault())") @Mapping(target = "useFlag", constant = "true") @Mapping(target = "refreshToken", ignore = true) @Mapping(target = "lastLoginAt", ignore = true) diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java b/src/main/java/com/bio/bio_backend/domain/base/member/repository/MemberRepository.java similarity index 77% rename from src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java rename to src/main/java/com/bio/bio_backend/domain/base/member/repository/MemberRepository.java index c7081eb..f83d09a 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepository.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/repository/MemberRepository.java @@ -1,9 +1,9 @@ -package com.bio.bio_backend.domain.user.member.repository; +package com.bio.bio_backend.domain.base.member.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.bio.bio_backend.domain.user.member.entity.Member; +import com.bio.bio_backend.domain.base.member.entity.Member; import java.util.Optional; @Repository diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java b/src/main/java/com/bio/bio_backend/domain/base/member/repository/MemberRepositoryCustom.java similarity index 94% rename from src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java rename to src/main/java/com/bio/bio_backend/domain/base/member/repository/MemberRepositoryCustom.java index 52f5d57..49d4ff1 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryCustom.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/repository/MemberRepositoryCustom.java @@ -1,6 +1,6 @@ -package com.bio.bio_backend.domain.user.member.repository; +package com.bio.bio_backend.domain.base.member.repository; -import com.bio.bio_backend.domain.user.member.entity.Member; +import com.bio.bio_backend.domain.base.member.entity.Member; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java b/src/main/java/com/bio/bio_backend/domain/base/member/repository/MemberRepositoryImpl.java similarity index 94% rename from src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java rename to src/main/java/com/bio/bio_backend/domain/base/member/repository/MemberRepositoryImpl.java index 58e706a..ad918cd 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/repository/MemberRepositoryImpl.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/repository/MemberRepositoryImpl.java @@ -1,10 +1,9 @@ -package com.bio.bio_backend.domain.user.member.repository; +package com.bio.bio_backend.domain.base.member.repository; -import com.bio.bio_backend.domain.user.member.entity.Member; -import com.bio.bio_backend.domain.user.member.entity.QMember; -import com.bio.bio_backend.domain.user.member.enums.MemberRole; +import com.bio.bio_backend.domain.base.member.entity.Member; +import com.bio.bio_backend.domain.base.member.entity.QMember; +import com.bio.bio_backend.domain.base.member.enums.MemberRole; import com.querydsl.core.BooleanBuilder; -import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java b/src/main/java/com/bio/bio_backend/domain/base/member/service/MemberService.java similarity index 85% rename from src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java rename to src/main/java/com/bio/bio_backend/domain/base/member/service/MemberService.java index 7377ba7..e8beb19 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberService.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/service/MemberService.java @@ -1,4 +1,4 @@ -package com.bio.bio_backend.domain.user.member.service; +package com.bio.bio_backend.domain.base.member.service; import java.util.List; import java.util.Map; @@ -6,7 +6,7 @@ import java.util.Map; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import com.bio.bio_backend.domain.user.member.dto.MemberDto; +import com.bio.bio_backend.domain.base.member.dto.MemberDto; public interface MemberService extends UserDetailsService { diff --git a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java b/src/main/java/com/bio/bio_backend/domain/base/member/service/MemberServiceImpl.java similarity index 93% rename from src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java rename to src/main/java/com/bio/bio_backend/domain/base/member/service/MemberServiceImpl.java index 673566f..d78fd35 100644 --- a/src/main/java/com/bio/bio_backend/domain/user/member/service/MemberServiceImpl.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/service/MemberServiceImpl.java @@ -1,10 +1,10 @@ -package com.bio.bio_backend.domain.user.member.service; +package com.bio.bio_backend.domain.base.member.service; -import com.bio.bio_backend.domain.user.member.dto.MemberDto; -import com.bio.bio_backend.domain.user.member.entity.Member; -import com.bio.bio_backend.domain.user.member.enums.MemberRole; -import com.bio.bio_backend.domain.user.member.mapper.MemberMapper; -import com.bio.bio_backend.domain.user.member.repository.MemberRepository; +import com.bio.bio_backend.domain.base.member.dto.MemberDto; +import com.bio.bio_backend.domain.base.member.entity.Member; +import com.bio.bio_backend.domain.base.member.enums.MemberRole; +import com.bio.bio_backend.domain.base.member.mapper.MemberMapper; +import com.bio.bio_backend.domain.base.member.repository.MemberRepository; import com.bio.bio_backend.global.exception.ApiException; import com.bio.bio_backend.global.constants.ApiResponseCode; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/bio/bio_backend/global/config/AppConfig.java b/src/main/java/com/bio/bio_backend/global/config/AppConfig.java index 0b22fb7..920e068 100644 --- a/src/main/java/com/bio/bio_backend/global/config/AppConfig.java +++ b/src/main/java/com/bio/bio_backend/global/config/AppConfig.java @@ -1,9 +1,16 @@ package com.bio.bio_backend.global.config; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + @Configuration public class AppConfig { @@ -11,4 +18,21 @@ public class AppConfig { public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + + // JavaTimeModule 등록 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + + // LocalDateTime 직렬화/역직렬화 설정 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); + + mapper.registerModule(javaTimeModule); + + return mapper; + } } \ No newline at end of file diff --git a/src/main/java/com/bio/bio_backend/global/exception/CustomAuthenticationFailureHandler.java b/src/main/java/com/bio/bio_backend/global/exception/CustomAuthenticationFailureHandler.java index 624de5b..7cc2539 100644 --- a/src/main/java/com/bio/bio_backend/global/exception/CustomAuthenticationFailureHandler.java +++ b/src/main/java/com/bio/bio_backend/global/exception/CustomAuthenticationFailureHandler.java @@ -29,9 +29,6 @@ public class CustomAuthenticationFailureHandler implements AuthenticationFailure @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - - log.info("exception : " + exception.toString()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding("UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); diff --git a/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java b/src/main/java/com/bio/bio_backend/global/security/JwtTokenIssuanceFilter.java similarity index 89% rename from src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java rename to src/main/java/com/bio/bio_backend/global/security/JwtTokenIssuanceFilter.java index 631bf5c..9585b3b 100644 --- a/src/main/java/com/bio/bio_backend/global/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/bio/bio_backend/global/security/JwtTokenIssuanceFilter.java @@ -7,10 +7,10 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import com.bio.bio_backend.global.dto.ApiResponseDto; -import com.bio.bio_backend.domain.user.member.dto.LoginRequestDto; -import com.bio.bio_backend.domain.user.member.dto.LoginResponseDto; -import com.bio.bio_backend.domain.user.member.dto.MemberDto; -import com.bio.bio_backend.domain.user.member.service.MemberService; +import com.bio.bio_backend.domain.base.member.dto.LoginRequestDto; +import com.bio.bio_backend.domain.base.member.dto.LoginResponseDto; +import com.bio.bio_backend.domain.base.member.dto.MemberDto; +import com.bio.bio_backend.domain.base.member.service.MemberService; import com.bio.bio_backend.global.constants.ApiResponseCode; import com.bio.bio_backend.global.utils.JwtUtils; import lombok.RequiredArgsConstructor; @@ -35,12 +35,13 @@ import java.util.Objects; @RequiredArgsConstructor @Slf4j -public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { +public class JwtTokenIssuanceFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; private final MemberService memberService; private final JwtUtils jwtUtils; private final Environment env; + private final ObjectMapper objectMapper; // 사용자 login 인증 처리 @SneakyThrows @@ -93,7 +94,6 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte // JWT 토큰 전달 response.setHeader("Authorization", "Bearer " + accessToken); -// response.addCookie(refreshTokenCookie); response.addHeader("Set-Cookie", String.format("%s=%s; HttpOnly; Secure; Path=/; Max-Age=%d; SameSite=None", refreshTokenCookie.getName(), @@ -112,8 +112,10 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte // login 성공 메시지 전송 response.setStatus(HttpStatus.OK.value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - new ObjectMapper().writeValue(response.getWriter(), - ApiResponseDto.success(ApiResponseCode.LOGIN_SUCCESSFUL, memberData)); + response.setContentType(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"); + objectMapper.writeValue( + response.getWriter(), + ApiResponseDto.success(ApiResponseCode.LOGIN_SUCCESSFUL, memberData) + ); } } diff --git a/src/main/java/com/bio/bio_backend/global/security/JwtTokenFilter.java b/src/main/java/com/bio/bio_backend/global/security/JwtTokenValidationFilter.java similarity index 61% rename from src/main/java/com/bio/bio_backend/global/security/JwtTokenFilter.java rename to src/main/java/com/bio/bio_backend/global/security/JwtTokenValidationFilter.java index 5680d36..76f86a7 100644 --- a/src/main/java/com/bio/bio_backend/global/security/JwtTokenFilter.java +++ b/src/main/java/com/bio/bio_backend/global/security/JwtTokenValidationFilter.java @@ -13,13 +13,12 @@ import org.springframework.web.filter.OncePerRequestFilter; import com.fasterxml.jackson.databind.ObjectMapper; -import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import com.bio.bio_backend.global.dto.ApiResponseDto; -import com.bio.bio_backend.domain.user.member.service.MemberService; +import com.bio.bio_backend.domain.base.member.service.MemberService; import com.bio.bio_backend.global.constants.ApiResponseCode; import com.bio.bio_backend.global.utils.JwtUtils; import lombok.RequiredArgsConstructor; @@ -27,18 +26,12 @@ import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor @Slf4j -public class JwtTokenFilter extends OncePerRequestFilter { +public class JwtTokenValidationFilter extends OncePerRequestFilter { private final JwtUtils jwtUtils; private final MemberService memberService; private final Environment env; - private final UriAllowFilter uriAllowFilter; - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { - return uriAllowFilter.authExceptionAllow(request); - } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, @@ -53,41 +46,53 @@ public class JwtTokenFilter extends OncePerRequestFilter { } try { - // 토큰 유효성 검사 - try { - if (jwtUtils.validateAccessToken(accessToken)) { - String username = jwtUtils.extractUsername(accessToken); - UserDetails userDetails = memberService.loadUserByUsername(username); + // Access Token 유효성 검사 + if (jwtUtils.validateAccessToken(accessToken)) { + String username = jwtUtils.extractUsername(accessToken); + UserDetails userDetails = memberService.loadUserByUsername(username); - if (userDetails != null) { - UsernamePasswordAuthenticationToken authentication = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); - filterChain.doFilter(request, response); - return; - } + if (userDetails != null) { + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + return; } - } catch (ExpiredJwtException ignored) { - // Access Token이 만료된 경우에만 ignored >> Refresh Token 검증 수행 } - // Refresh Token 유효성 검사 + + // Access Token이 유효하지 않거나 만료된 경우 Refresh Token 검증 if (refreshToken != null && jwtUtils.validateRefreshToken(refreshToken)) { String username = jwtUtils.extractUsername(refreshToken); String role = (String) jwtUtils.extractAllClaims(refreshToken).get("role"); + + // 새로운 Access Token 생성 String newAccessToken = jwtUtils.generateToken(username, role, Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access")))); + // 새로운 Access Token을 응답 헤더에 설정 response.setHeader("Authorization", "Bearer " + newAccessToken); + + // 인증 정보 설정 + UserDetails userDetails = memberService.loadUserByUsername(username); + if (userDetails != null) { + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(request, response); + return; } else { - sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.ALL_TOKEN_INVALID, null)); + sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.ALL_TOKEN_INVALID)); + return; } } catch (Exception e) { request.setAttribute("exception", e); + sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR)); + return; } - - filterChain.doFilter(request, response); } private void sendJsonResponse(HttpServletResponse response, ApiResponseDto apiResponse) throws IOException { diff --git a/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java b/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java index 44c7a09..477b2c5 100644 --- a/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java +++ b/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java @@ -8,21 +8,18 @@ import org.springframework.security.config.annotation.authentication.builders.Au import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.filter.CorsFilter; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.Filter; -//import com.bio.bio_backend.domain.common.service.AccessLogService; -import com.bio.bio_backend.domain.user.member.service.MemberService; +import com.bio.bio_backend.domain.base.member.service.MemberService; import com.bio.bio_backend.global.exception.CustomAuthenticationFailureHandler; import com.bio.bio_backend.global.exception.JwtAccessDeniedHandler; import com.bio.bio_backend.global.exception.JwtAuthenticationEntryPoint; -//import com.bio.bio_backend.global.filter.HttpLoggingFilter; -import com.bio.bio_backend.global.utils.HttpUtils; import com.bio.bio_backend.global.utils.JwtUtils; import lombok.RequiredArgsConstructor; @@ -34,26 +31,20 @@ public class WebSecurity { private final MemberService memberService; private final BCryptPasswordEncoder bCryptPasswordEncoder; private final JwtUtils jwtUtils; -// private final AccessLogService accessLogService; - private final CorsFilter corsFilter; private final ObjectMapper objectMapper; - private final HttpUtils httpUtils; private final Environment env; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private final JwtAccessDeniedHandler jwtAccessDeniedHandler; - - private final UriAllowFilter uriAllowFilter; - - private JwtAuthenticationFilter getJwtAuthenticationFilter(AuthenticationManager authenticationManager) throws Exception { - JwtAuthenticationFilter filter = new JwtAuthenticationFilter(authenticationManager, memberService, jwtUtils, env); + private JwtTokenIssuanceFilter getJwtTokenIssuanceFilter(AuthenticationManager authenticationManager) throws Exception { + JwtTokenIssuanceFilter filter = new JwtTokenIssuanceFilter(authenticationManager, memberService, jwtUtils, env, objectMapper); filter.setFilterProcessesUrl("/login"); // 로그인 EndPoint filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper)); return filter; } - private Filter getJwtTokenFilter() { - return new JwtTokenFilter(jwtUtils, memberService, env, uriAllowFilter); + private Filter getJwtTokenValidationFilter() { + return new JwtTokenValidationFilter(jwtUtils, memberService, env); } @Bean @@ -69,13 +60,15 @@ public class WebSecurity { http.csrf(AbstractHttpConfigurer::disable) //csrf 비활성화 .authorizeHttpRequests(request -> //request 허용 설정 request - .anyRequest().permitAll() // 모든 요청 허용 -// .requestMatchers("/ws/**").permitAll() -// .requestMatchers("/admin/**", "/join").hasAnyAuthority(MemberConstants.ROLE_ADMIN) -// .requestMatchers("/member/**").hasAnyAuthority(MemberConstants.ROLE_MEMBER) -// .anyRequest().authenticated() + .requestMatchers("/api/auth/**").permitAll() // 인증 관련 엔드포인트 허용 + .requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll() // Swagger 허용 + .requestMatchers("/ws/**").permitAll() // WebSocket 허용 + .anyRequest().authenticated() // 나머지 요청은 인증 필요 ) .authenticationManager(authenticationManager) + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용 안함 + ) .logout(AbstractHttpConfigurer::disable); // 예외 처리 핸들링 @@ -85,16 +78,11 @@ public class WebSecurity { .accessDeniedHandler(jwtAccessDeniedHandler) ); -// http -// .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) -// .addFilterBefore(new HttpLoggingFilter(accessLogService, httpUtils), UsernamePasswordAuthenticationFilter.class) -// .addFilterBefore(getJwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class) -// .addFilterBefore(getJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); - - -// .sessionManagement(httpSecuritySessionManagementConfigurer -> //Session 사용 X -// httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - + http + // 1단계: JWT 토큰 발급 필터 (로그인 요청 처리 및 토큰 발급) + .addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class) + // 2단계: JWT 토큰 검증 필터 (모든 요청에 대해 토큰 유효성 검증) + .addFilterBefore(getJwtTokenValidationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java b/src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java index 8ac5e88..cec8caa 100644 --- a/src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java +++ b/src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java @@ -6,7 +6,7 @@ import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; -import com.bio.bio_backend.domain.user.member.service.MemberService; +import com.bio.bio_backend.domain.base.member.service.MemberService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 667b6cf..541e6a8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -94,5 +94,4 @@ springdoc.swagger-ui.tagsSorter=alpha springdoc.swagger-ui.doc-expansion=none springdoc.swagger-ui.disable-swagger-default-url=true springdoc.default-produces-media-type=application/json -springdoc.default-consumes-media-type=application/json - +springdoc.default-consumes-media-type=application/json \ No newline at end of file