Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public SecurityFilterChain defaultSecurity(HttpSecurity http) throws Exception {
// @PreAuthorized, @PostAuthorized의 경우 메서드 호출 직전에 차단되어 GlobalExceptionHandler에 의해 예외가 처리됩니다.
.authorizeHttpRequests(req -> req
.requestMatchers("/api/v1/admin/**").hasAnyRole(MemberType.ADMIN.name())
.requestMatchers("/api/v1/members/sign-in").permitAll()
.requestMatchers("/api/v1/members/sign-up/**").permitAll()
.requestMatchers("/api/v1/members/refresh").permitAll()
.requestMatchers("/api/v1/email/**").permitAll()
.anyRequest().permitAll())

.exceptionHandling(ex -> ex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;

@Slf4j
Expand All @@ -29,8 +30,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String[] excludePath = {"api/v1/members/sign-in", "api/v1/members/sign-up/**", "api/v1/members/refresh", "api/v1/email/**"};

String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
return authorizationHeader == null;
return authorizationHeader == null
|| Arrays.stream(excludePath).anyMatch(request.getRequestURI()::startsWith);
Comment on lines +36 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

startsWith 사용으로 인한 보안 취약점을 수정하세요.

현재 startsWith 방식은 의도하지 않은 경로까지 매치할 수 있는 보안 문제가 있습니다. 예를 들어 "/api/v1/members/sign-in-test" 같은 경로도 매치됩니다.

정확한 패턴 매칭을 위해 다음과 같이 수정하세요:

-        return authorizationHeader == null
-                || Arrays.stream(excludePath).anyMatch(request.getRequestURI()::startsWith);
+        String requestURI = request.getRequestURI();
+        return authorizationHeader == null
+                || requestURI.equals("/api/v1/members/sign-in")
+                || requestURI.startsWith("/api/v1/members/sign-up/")
+                || requestURI.equals("/api/v1/members/refresh")
+                || requestURI.startsWith("/api/v1/email/");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return authorizationHeader == null
|| Arrays.stream(excludePath).anyMatch(request.getRequestURI()::startsWith);
String requestURI = request.getRequestURI();
return authorizationHeader == null
|| requestURI.equals("/api/v1/members/sign-in")
|| requestURI.startsWith("/api/v1/members/sign-up/")
|| requestURI.equals("/api/v1/members/refresh")
|| requestURI.startsWith("/api/v1/email/");
🤖 Prompt for AI Agents
In
src/main/java/kr/ac/kumoh/d138/JobForeigner/global/jwt/filter/JwtAuthenticationFilter.java
around lines 36 to 37, the use of startsWith for path matching causes security
issues by matching unintended paths. Replace the startsWith check with an exact
match or a more precise pattern matching method to ensure only the intended
paths in excludePath are matched, preventing accidental matches like
"/api/v1/members/sign-in-test".

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,14 @@ public ResumeResponse updateResume(ResumeRequest request, Long memberId, Long re
Resume resume = resumeRepository.findById(resumeId)
.orElseThrow(()-> new BusinessException(ExceptionType.RESUME_NOT_FOUND));

String imageUrl = resumeImageService.getResumeImagePresignedUrl(memberId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

권한 체크 후에 이미지 URL을 생성하세요.

권한이 확인되지 않은 상태에서 resumeImageService.getResumeImagePresignedUrl(memberId)를 호출하는 것은 보안상 좋지 않습니다. 해당 사용자가 실제로 이력서의 소유자인지 확인한 후에 이미지 URL을 생성해야 합니다.

다음과 같이 순서를 변경하세요:

     @Transactional
     public ResumeResponse updateResume(ResumeRequest request, Long memberId, Long resumeId) {
         Resume resume = resumeRepository.findById(resumeId)
                 .orElseThrow(()-> new BusinessException(ExceptionType.RESUME_NOT_FOUND));

-        String imageUrl = resumeImageService.getResumeImagePresignedUrl(memberId);
         if(!resume.getMember().getId().equals(memberId)) {
             throw new BusinessException(ExceptionType.RESUME_FORBIDDEN);
         }
+        
+        String imageUrl = resumeImageService.getResumeImagePresignedUrl(memberId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String imageUrl = resumeImageService.getResumeImagePresignedUrl(memberId);
@Transactional
public ResumeResponse updateResume(ResumeRequest request, Long memberId, Long resumeId) {
Resume resume = resumeRepository.findById(resumeId)
.orElseThrow(() -> new BusinessException(ExceptionType.RESUME_NOT_FOUND));
if (!resume.getMember().getId().equals(memberId)) {
throw new BusinessException(ExceptionType.RESUME_FORBIDDEN);
}
String imageUrl = resumeImageService.getResumeImagePresignedUrl(memberId);
// ... the rest of the method ...
}
🤖 Prompt for AI Agents
In src/main/java/kr/ac/kumoh/d138/JobForeigner/resume/service/ResumeService.java
at line 112, the code calls
resumeImageService.getResumeImagePresignedUrl(memberId) before verifying the
user's authorization. To fix this, first perform a permission check to confirm
that the user is the owner of the resume, and only after successful
authorization, call getResumeImagePresignedUrl to generate the image URL. This
ensures secure access control before generating sensitive URLs.

if(!resume.getMember().getId().equals(memberId)) {
throw new BusinessException(ExceptionType.RESUME_FORBIDDEN);
}

resume.updateResume(
request.resumeTitle(),
request.resumeImageUrl() != null ? request.resumeImageUrl() : resume.getResumeImageUrl(),
imageUrl,
request.desiredJobs() != null
? request.desiredJobs().stream().map(DesiredJobRequest::toDesiredJob).collect(Collectors.toList())
: null,
Expand Down Expand Up @@ -148,8 +149,6 @@ public ResumeResponse updateResume(ResumeRequest request, Long memberId, Long re
: null
);

String imageUrl = resumeImageService.getResumeImagePresignedUrl(memberId);

return ResumeResponse.toResumeResponse(resume, imageUrl, MemberProfileResponse.toMemberProfileResponse(resume.getMember(), null));

}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/properties