diff --git a/src/main/java/briefing/briefing/api/BriefingApi.java b/src/main/java/briefing/briefing/api/BriefingApi.java index d38da5c..6a82606 100644 --- a/src/main/java/briefing/briefing/api/BriefingApi.java +++ b/src/main/java/briefing/briefing/api/BriefingApi.java @@ -5,8 +5,14 @@ import briefing.briefing.application.dto.*; import briefing.briefing.domain.BriefingType; import java.time.LocalDate; +import java.util.Optional; import briefing.common.response.CommonResponse; +import briefing.member.domain.Member; +import briefing.scrap.application.ScrapQueryService; +import briefing.security.handler.annotation.AuthMember; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -27,6 +33,7 @@ public class BriefingApi { private final BriefingQueryService briefingQueryService; private final BriefingCommandService briefingCommandService; + private final ScrapQueryService scrapQueryService; @GetMapping public CommonResponse findBriefings( @@ -37,8 +44,14 @@ public CommonResponse findBriefings( } @GetMapping("/{id}") - public CommonResponse findBriefing(@PathVariable final Long id) { - return CommonResponse.onSuccess(BriefingConverter.toBriefingDetailDTO(briefingQueryService.findBriefing(id))); + @Parameter(name = "member", hidden = true) + public CommonResponse findBriefing(@PathVariable final Long id, @AuthMember Member member) { + + Boolean isScrap = Optional.ofNullable(member) + .map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id)) + .orElseGet(() -> Boolean.FALSE); + + return CommonResponse.onSuccess(BriefingConverter.toBriefingDetailDTO(briefingQueryService.findBriefing(id), isScrap)); } @PostMapping diff --git a/src/main/java/briefing/briefing/api/BriefingConverter.java b/src/main/java/briefing/briefing/api/BriefingConverter.java index d40e921..3d44c25 100644 --- a/src/main/java/briefing/briefing/api/BriefingConverter.java +++ b/src/main/java/briefing/briefing/api/BriefingConverter.java @@ -41,7 +41,7 @@ public static BriefingResponseDTO.ArticleResponseDTO toArticleResponseDTO(final .build(); } - public static BriefingResponseDTO.BriefingDetailDTO toBriefingDetailDTO(Briefing briefing){ + public static BriefingResponseDTO.BriefingDetailDTO toBriefingDetailDTO(Briefing briefing, Boolean isScrap){ List articleResponseDTOList = briefing.getBriefingArticles().stream() .map(article -> toArticleResponseDTO(article.getArticle())).toList(); @@ -54,6 +54,7 @@ public static BriefingResponseDTO.BriefingDetailDTO toBriefingDetailDTO(Briefing .content(briefing.getContent()) .date(briefing.getCreatedAt().toLocalDate()) .articles(articleResponseDTOList) + .isScrap(isScrap) .build(); } diff --git a/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java b/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java index 0697235..5e9c9c2 100644 --- a/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java +++ b/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java @@ -45,6 +45,7 @@ public static class BriefingDetailDTO{ String content; LocalDate date; List articles; + Boolean isScrap; } @Builder diff --git a/src/main/java/briefing/member/api/MemberApi.java b/src/main/java/briefing/member/api/MemberApi.java index 630349a..40add5c 100644 --- a/src/main/java/briefing/member/api/MemberApi.java +++ b/src/main/java/briefing/member/api/MemberApi.java @@ -22,10 +22,8 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.sql.Ref; import java.util.Arrays; import java.util.List; -import java.util.UUID; @Tag(name = "02-Member \uD83D\uDC64",description = "사용자 관련 API") @RestController diff --git a/src/main/java/briefing/scrap/application/ScrapQueryService.java b/src/main/java/briefing/scrap/application/ScrapQueryService.java index 7b35120..859c1d5 100644 --- a/src/main/java/briefing/scrap/application/ScrapQueryService.java +++ b/src/main/java/briefing/scrap/application/ScrapQueryService.java @@ -18,4 +18,9 @@ public class ScrapQueryService { public List getScrapsByMemberId(Long memberId) { return scrapRepository.findByMember_Id(memberId); } + + + public Boolean existsByMemberIdAndBriefingId(Long memberId, Long briefingId) { + return scrapRepository.existsByMember_IdAndBriefing_Id(memberId, briefingId); + } } diff --git a/src/main/java/briefing/security/config/SecurityConfig.java b/src/main/java/briefing/security/config/SecurityConfig.java index ecc2b0c..e4bc6d1 100644 --- a/src/main/java/briefing/security/config/SecurityConfig.java +++ b/src/main/java/briefing/security/config/SecurityConfig.java @@ -69,10 +69,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement(manage -> manage.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // Session 사용 안함 .formLogin(AbstractHttpConfigurer::disable) // form login 사용 안함 .httpBasic(AbstractHttpConfigurer::disable) // http basic 방식 사용 안함 -// .authorizeHttpRequests(authorize -> authorize // lambda 방식 -// //.requestMatchers(WHITE_LIST).permitAll() -// .anyRequest().authenticated() -// ) + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/briefings/**").permitAll() // 모두 접근 가능합니다. + ) .exceptionHandling(exceptionHandling -> exceptionHandling .authenticationEntryPoint(jwtAuthenticationEntryPoint) .accessDeniedHandler(jwtAccessDeniedHandler) diff --git a/src/main/java/briefing/security/filter/JwtRequestFilter.java b/src/main/java/briefing/security/filter/JwtRequestFilter.java index 3cc8746..84efb17 100644 --- a/src/main/java/briefing/security/filter/JwtRequestFilter.java +++ b/src/main/java/briefing/security/filter/JwtRequestFilter.java @@ -26,6 +26,7 @@ public class JwtRequestFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest httpServletRequest = request; String jwt = tokenProvider.resolveToken(httpServletRequest); + if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt, TokenProvider.TokenType.ACCESS)){ Authentication authentication = tokenProvider.getAuthentication(jwt); diff --git a/src/main/java/briefing/security/handler/annotation/AuthUserArgumentResolver.java b/src/main/java/briefing/security/handler/annotation/AuthUserArgumentResolver.java index b69ee75..fa20489 100644 --- a/src/main/java/briefing/security/handler/annotation/AuthUserArgumentResolver.java +++ b/src/main/java/briefing/security/handler/annotation/AuthUserArgumentResolver.java @@ -5,12 +5,15 @@ import briefing.member.api.MemberConverter; import briefing.member.application.MemberQueryService; import briefing.member.domain.Member; +import briefing.security.provider.TokenProvider; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -22,6 +25,7 @@ public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver { private final MemberQueryService memberQueryService; + private final TokenProvider tokenProvider; @Override public boolean supportsParameter(MethodParameter parameter) { @@ -33,20 +37,24 @@ public boolean supportsParameter(MethodParameter parameter) { return true; } + + /* + NOTE - 사용자 추출 방법 변경 + 기존) 스프링 시큐리티 컨텍스트에서 유저정보를 가져와 세팅해주었습니다. + 현재) httpRequest의 authorization header에서 액세스 토큰을 가져와 사용자 정보를 반환합니다. + 변경 배경) + permitAll()을 통해 인증 유무에 상관없이 제공되는 API에서 JwtRequestFilter까지 도달하지 않는 문제인해 + authentication 객체가 세팅이 되지 않기 때문에 기존 방법으로는 토큰을 물고 있는 사용자라도 AuthMember를 통해 추출하면 null이 반환되었습니다. + */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - Object principal = null; - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - - if (authentication != null) { - principal = authentication.getPrincipal(); + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + String jwt = tokenProvider.resolveToken(request); + if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt, TokenProvider.TokenType.ACCESS)) { + // 토큰에서 사용자 ID (subject) 추출 + String userId = tokenProvider.getAuthentication(jwt).getName(); + return memberQueryService.findById(Long.valueOf(userId)); } - if (principal == null || principal.getClass() == String.class) { - throw new MemberException(ErrorCode.MEMBER_NOT_FOUND); - } - - UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication; - Member member = memberQueryService.findById(Long.valueOf(authenticationToken.getName())); - return member; + return null; // 토큰이 없거나 유효하지 않은 경우 } }