Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
002df0e
feat: post error enum class 추가
gongsua Nov 8, 2025
ea9027c
feat: post 클래스 작성
gongsua Nov 8, 2025
b6d240f
feat: post관련 엔티티정리 수정
gongsua Nov 8, 2025
ced2612
feat: flyway sql생성
gongsua Nov 8, 2025
459dfc8
feat: 게시물 등록 구현
gongsua Nov 9, 2025
0aee6fe
fix: detail table 에 수량 컬럼 추가
gongsua Nov 9, 2025
62f6d8d
feat:JwtTokenFilter 에 맞춰 principal이 MemberDetails 로 수정 / postman 테스트 완료
gongsua Nov 9, 2025
7f504eb
feat: 상품 목록 가져오기 기능 구현
gongsua Nov 10, 2025
96336da
feat: 게시글 조회 비회원 접근 허용 및 인증 정책 분리
gongsua Nov 10, 2025
d54b666
feat: 게시글 조회시 상태 값 가져오도록 수정
gongsua Nov 10, 2025
e4d6137
feat: 게시물 상세 내용 조회 구현
gongsua Nov 10, 2025
9e48fe4
feat: pr템플릿
gongsua Nov 10, 2025
1a331b0
refactor: post detail 삭제 post 엔티티로 통일
gongsua Nov 12, 2025
6e96029
refactor: 예외처리 post 전용으로 추가 수정
gongsua Nov 13, 2025
60888ae
refactor: Integer -> int 로 수정
gongsua Nov 13, 2025
cd1f405
refactor: jwt 관련 예외 처리 추가
gongsua Nov 13, 2025
77695b0
refacor: post 관련 예외 핸들러 구성, 적용
gongsua Nov 15, 2025
62d7398
refacor: post 에러 enum post 패키지 하위로 이동
gongsua Nov 15, 2025
0fec8b7
refacor: MemberErrorCode 생성
gongsua Nov 15, 2025
76ef195
refacor: MemberNotFoundException 생성
gongsua Nov 15, 2025
28bd957
refacor: InvalidPasswordException 생성
gongsua Nov 15, 2025
b68a46e
refacor:DuplicateEmailException todtjd
gongsua Nov 15, 2025
4ab5e80
refacor:MissingAuthHeaderException 생성
gongsua Nov 15, 2025
3baa979
refacor: post, member service , 엔터티 수정
gongsua Nov 15, 2025
1135da7
refacor: 인증관련 예외처리 수정, 글로벌 예외처리 수정
gongsua Nov 16, 2025
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
7 changes: 7 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## 📌 Related Issue

## 🚀 Description

## 📸 Screenshot

## 📢 Notes
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.example.buyingserver.common.auth;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.buyingserver.common.dto.ApiResponse;
import org.example.buyingserver.common.exception.GlobalErrorCode;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@Component
@RequiredArgsConstructor
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

private final ObjectMapper objectMapper;

@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException
) throws IOException {

log.error("Access Denied: {}", request.getRequestURI());

ApiResponse<?> apiResponse = ApiResponse.error(GlobalErrorCode.ACCESS_DENIED);

response.setStatus(GlobalErrorCode.ACCESS_DENIED.getCode());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(apiResponse));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.example.buyingserver.common.auth;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.buyingserver.common.dto.ApiResponse;
import org.example.buyingserver.common.dto.ErrorCode;
import org.example.buyingserver.common.exception.GlobalErrorCode;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@Component
@RequiredArgsConstructor
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

private final ObjectMapper objectMapper;

@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException
) throws IOException {

log.error("Not Authenticated Request", authException);

ErrorCode errorCode =
(authException instanceof JwtAuthenticationException jwtEx)
? jwtEx.getErrorCode()
: GlobalErrorCode.UNAUTHORIZED;

ApiResponse<?> apiResponse = ApiResponse.error(errorCode);
String responseBody = objectMapper.writeValueAsString(apiResponse);

response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(errorCode.getCode());
response.setCharacterEncoding("UTF-8");
response.getWriter().write(responseBody);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.example.buyingserver.common.auth;

import org.example.buyingserver.common.dto.ErrorCode;
import org.springframework.security.core.AuthenticationException;


public class JwtAuthenticationException extends AuthenticationException {

private final ErrorCode errorCode;

public JwtAuthenticationException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}

public ErrorCode getErrorCode() {
return errorCode;
}
}
138 changes: 71 additions & 67 deletions src/main/java/org/example/buyingserver/common/auth/JwtTokenFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,106 +3,110 @@
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.SignatureException;
import jakarta.servlet.*;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.buyingserver.common.dto.ErrorCodeAndMessage;
import org.example.buyingserver.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import org.example.buyingserver.common.exception.GlobalErrorCode;
import org.example.buyingserver.member.domain.Member;
import org.example.buyingserver.member.exception.MemberErrorCode;
import org.example.buyingserver.member.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Value;
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.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class JwtTokenFilter extends GenericFilter {
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {

private final MemberRepository memberRepository;

@Value("${jwt.secret}")
private String secretKey;

private final MemberRepository memberRepository;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {

public JwtTokenFilter(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
String path = request.getRequestURI();

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (isPublicPath(path)) {
filterChain.doFilter(request, response);
return;
}

HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String path = httpRequest.getRequestURI();
String token = resolveToken(request);

try {
// JWT 토큰 검증이 필요 없는 경로들
if (path.equals("/member/login") ||
path.equals("/member/create") ||
path.startsWith("/oauth2/") ||
path.startsWith("/login/oauth2/") ||
path.startsWith("/swagger-ui/") ||
path.startsWith("/v3/api-docs") ||
path.equals("/favicon.ico") ||
path.equals("/error")) {
chain.doFilter(request, response);
return;
}

String bearerToken = httpRequest.getHeader("Authorization");
if (bearerToken == null || !bearerToken.startsWith("Bearer ")) {
throw new BusinessException(ErrorCodeAndMessage.MISSING_AUTHORIZATION_HEADER);
}

String token = bearerToken.substring(7).trim();

Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey.getBytes())
.build()
.parseClaimsJws(token)
.getBody();
Claims claims = parseClaims(token);

String email = claims.getSubject();
setAuthentication(claims, token);

Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new BusinessException(ErrorCodeAndMessage.MEMBER_NOT_FOUND));
filterChain.doFilter(request, response);
}

MemberDetails memberDetails = new MemberDetails(member);
private boolean isPublicPath(String path) {
return path.equals("/member/login") ||
path.equals("/member/create") ||
path.startsWith("/oauth2/") ||
path.startsWith("/login/oauth2/") ||
path.startsWith("/swagger-ui/") ||
path.startsWith("/v3/api-docs") ||
path.equals("/favicon.ico") ||
path.equals("/error") ||
path.startsWith("/posts/lists");
}

Authentication authentication = new UsernamePasswordAuthenticationToken(
memberDetails,
token,
memberDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");

chain.doFilter(request, response);
if (bearerToken == null || !bearerToken.startsWith("Bearer ")) {
throw new JwtAuthenticationException(GlobalErrorCode.MISSING_AUTHORIZATION_HEADER);
}

} catch (SignatureException e) {
sendErrorResponse(httpResponse, ErrorCodeAndMessage.TOKEN_INVALID);
return bearerToken.substring(7).trim();
}

private Claims parseClaims(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(secretKey.getBytes())
.build()
.parseClaimsJws(token)
.getBody();

} catch (io.jsonwebtoken.ExpiredJwtException e) {
sendErrorResponse(httpResponse, ErrorCodeAndMessage.TOKEN_EXPIRED);
throw new JwtAuthenticationException(GlobalErrorCode.TOKEN_EXPIRED);

} catch (BusinessException e) {
sendErrorResponse(httpResponse, e.getErrorCodeAndMessage());
} catch (SignatureException e) {
throw new JwtAuthenticationException(GlobalErrorCode.TOKEN_INVALID);

} catch (Exception e) {
e.printStackTrace();
sendErrorResponse(httpResponse, ErrorCodeAndMessage.INTERNAL_SERVER_ERROR);
throw new JwtAuthenticationException(GlobalErrorCode.TOKEN_INVALID);
}
}

private void sendErrorResponse(HttpServletResponse response, ErrorCodeAndMessage errorCode) throws IOException {
response.setStatus(errorCode.getCode());
response.setContentType("application/json; charset=UTF-8");
private void setAuthentication(Claims claims, String token) {
String email = claims.getSubject();

Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new JwtAuthenticationException(MemberErrorCode.MEMBER_NOT_FOUND));

MemberDetails memberDetails = new MemberDetails(member);

Authentication authentication = new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(
memberDetails,
token,
memberDetails.getAuthorities()
);

String json = String.format(
"{\"status\": %d, \"message\": \"%s\"}",
errorCode.getCode(),
errorCode.getMessage());
response.getWriter().write(json);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.example.buyingserver.common.dto.ErrorCodeAndMessage;
import org.example.buyingserver.common.exception.BusinessException;
import org.example.buyingserver.common.exception.GlobalErrorCode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Base64;
import java.util.Date;

@Component
Expand Down Expand Up @@ -53,7 +52,7 @@ public String getEmailFromToken(String token) {

return claims.getSubject(); // email
} catch (Exception e) {
throw new BusinessException(ErrorCodeAndMessage.TOKEN_INVALID);
throw new BusinessException(GlobalErrorCode.TOKEN_INVALID);
}
}
}
Loading
Loading