Skip to content

Commit

Permalink
Week 10 Release
Browse files Browse the repository at this point in the history
Week 10 Release
  • Loading branch information
xGreenNarae authored Nov 10, 2023
2 parents 7dac441 + 82ab94f commit 1eee90a
Show file tree
Hide file tree
Showing 20 changed files with 191 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@

import com.daggle.animory.common.Response;
import com.daggle.animory.common.security.exception.ForbiddenException;
import com.daggle.animory.common.security.exception.UnAuthorizedException;
import com.daggle.animory.domain.account.exception.AlreadyExistEmailException;
import com.daggle.animory.domain.account.exception.CheckEmailOrPasswordException;
import com.daggle.animory.domain.fileserver.exception.*;
import com.daggle.animory.domain.pet.exception.InvalidPetAgeFormatException;
import com.daggle.animory.domain.pet.exception.InvalidPetMonthRangeException;
import com.daggle.animory.domain.pet.exception.InvalidPetYearRangeException;
import com.daggle.animory.domain.pet.exception.PetNotFoundException;
import com.daggle.animory.domain.pet.exception.PetPermissionDeniedException;
import com.daggle.animory.domain.shelter.exception.ShelterAlreadyExistException;
import com.daggle.animory.domain.shelter.exception.ShelterNotFoundException;
import com.daggle.animory.domain.shelter.exception.ShelterPermissionDeniedException;
import com.daggle.animory.domain.shortform.exception.AlreadyLikedPetVideoException;
import com.daggle.animory.domain.shortform.exception.NotLikedPetVideoException;
import com.daggle.animory.domain.shortform.exception.ShortFormNotFoundException;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -107,12 +112,37 @@ public ResponseEntity<Response<Void>> handleShelterAlreadyExistException(final E
HttpStatus.BAD_REQUEST));
}

@ExceptionHandler({AlreadyLikedPetVideoException.class})
public ResponseEntity<Response<Void>> handleAlreadyLikedPetVideoException(final Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Response.error(e.getMessage(),
HttpStatus.BAD_REQUEST));
}

@ExceptionHandler({NotLikedPetVideoException.class})
public ResponseEntity<Response<Void>> handleNotLikedPetVideoException(final Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Response.error(e.getMessage(),
HttpStatus.BAD_REQUEST));
}

// 401
@ExceptionHandler({UnAuthorizedException.class})
public ResponseEntity<Response<Void>> handleUnAuthorizedException(final Exception e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Response.error(e.getMessage(),
HttpStatus.UNAUTHORIZED));
}


// 403
@ExceptionHandler({ShelterPermissionDeniedException.class})
public ResponseEntity<Response<Void>> handleShelterPermissionDeniedException(final Exception e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Response.error(e.getMessage(), HttpStatus.FORBIDDEN));
}

@ExceptionHandler({PetPermissionDeniedException.class})
public ResponseEntity<Response<Void>> handlePetPermissionDeniedException(final Exception e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Response.error(e.getMessage(), HttpStatus.FORBIDDEN));
}

@ExceptionHandler({ForbiddenException.class})
public ResponseEntity<Response<Void>> handleForbiddenExceptionn(final Exception e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Response.error(e.getMessage(), HttpStatus.FORBIDDEN));
Expand All @@ -128,6 +158,10 @@ public ResponseEntity<Response<Void>> handlePetNotFoundException(final Exception
public ResponseEntity<Response<Void>> handleShelterNotFoundException(final Exception e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Response.error(e.getMessage(), HttpStatus.NOT_FOUND));
}
@ExceptionHandler({ShortFormNotFoundException.class})
public ResponseEntity<Response<Void>> handleShortFormNotFoundException(final Exception e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Response.error(e.getMessage(), HttpStatus.NOT_FOUND));
}

@ExceptionHandler({MaxUploadSizeExceededException.class})
public ResponseEntity<Response<Void>> handleMaxUploadSizeExceededException(final RuntimeException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,22 @@ public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
private static final String AUTHORIZATION_HEADER = "Authorization";
private final TokenProvider tokenProvider;

public JwtAuthenticationFilter(final AuthenticationManager authenticationManager, final TokenProvider tokenProvider) {
public JwtAuthenticationFilter(final AuthenticationManager authenticationManager,
final TokenProvider tokenProvider) {
super(authenticationManager);
this.tokenProvider = tokenProvider;
}

// 권한 확인을 수행하는 로직
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException, ServletException {
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws IOException, ServletException {
log.debug("JwtAuthenticationFilter 동작");

final String jwt = request.getHeader(AUTHORIZATION_HEADER);

if (jwt == null) {
super.doFilterInternal(request, response, chain);
chain.doFilter(request, response);
return;
}

Expand All @@ -44,24 +48,25 @@ protected void doFilterInternal(final HttpServletRequest request, final HttpServ
final AccountRole role = tokenProvider.getRoleFromToken(claims);

final Account account = Account.builder()
.email(email)
.role(role)
.build();
.email(email)
.role(role)
.build();
final UserDetailsImpl userDetails = new UserDetailsImpl(account);

final Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails,
userDetails.getPassword(),
userDetails.getAuthorities());
userDetails,
userDetails.getPassword(),
userDetails.getAuthorities());

SecurityContextHolder.getContext().setAuthentication(authentication);

log.debug("디버그 : 인증 객체 만들어짐");
chain.doFilter(request, response);
} catch (SecurityException e) {
log.info("Invalid JWT signature.");
throw new JwtException("잘못된 JWT 시그니처");
} catch (MalformedJwtException e) {
log.info("Invalid JWT token.");
log.info("Invalid JWT token: {}.", e.getMessage());
throw new JwtException("유효하지 않은 JWT 토큰");
} catch (ExpiredJwtException e) {
log.info("Expired JWT token.");
Expand All @@ -72,8 +77,6 @@ protected void doFilterInternal(final HttpServletRequest request, final HttpServ
} catch (IllegalArgumentException e) {
log.info("JWT token compact of handler are invalid.");
throw new JwtException("JWT token compact of handler are invalid.");
} finally {
super.doFilterInternal(request, response, chain);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.daggle.animory.common.security.exception.ForbiddenException;
import com.daggle.animory.common.security.exception.JwtExceptionFilter;
import com.daggle.animory.common.security.exception.UnAuthorizedException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -21,8 +22,6 @@
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.http.HttpServletResponse;

@Slf4j
@Configuration
@EnableWebSecurity
Expand Down Expand Up @@ -64,12 +63,12 @@ public SecurityFilterChain securityFilterChain(final HttpSecurity http,
http.apply(new SecurityFilterManagerImpl());

http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
log.info(authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
log.info("{}, {}", request.getRemoteAddr(), authException.getMessage());
resolver.resolveException(request, response, null, new UnAuthorizedException());
});

http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
log.info(accessDeniedException.getMessage());
log.info("{}, {}", request.getRemoteAddr(), accessDeniedException.getMessage());
resolver.resolveException(request, response, null, new ForbiddenException());
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@
@Component
public class TokenProvider {

private final String key;
private static final String TOKEN_PREFIX = "Bearer ";
private static final String ROLES_CLAIM = "roles";

private static final int TOKEN_EXPIRATION_DATE_TO_PLUS = 1;
private static final int TOKEN_EXPIRATION_FIXED_HOUR = 3;
private final String key;

public TokenProvider(@Value("${jwt.secret}") final String key) {
this.key = Base64.getEncoder().encodeToString(key.getBytes());
Expand All @@ -31,13 +30,14 @@ public TokenProvider(@Value("${jwt.secret}") final String key) {

public String create(final String email, final AccountRole role) {
return TOKEN_PREFIX + Jwts.builder().setSubject(email) // 정보 저장
.claim(ROLES_CLAIM, role).setIssuedAt(new Date()) // 토큰 발행 시간
.setExpiration(calcExpirationDateTime()) // 토큰 만료 시간
.signWith(SignatureAlgorithm.HS256, key) // 암호화 알고리즘 및 secretKey
.compact();
.claim(ROLES_CLAIM, role).setIssuedAt(new Date()) // 토큰 발행 시간
.setExpiration(calcExpirationDateTime()) // 토큰 만료 시간
.signWith(SignatureAlgorithm.HS256, key) // 암호화 알고리즘 및 secretKey
.compact();
}

public TokenWithExpirationDateTimeDto createTokenWithExpirationDateTimeDto(final String email, final AccountRole role) {
public TokenWithExpirationDateTimeDto createTokenWithExpirationDateTimeDto(final String email,
final AccountRole role) {
final Date expirationDateTime = calcExpirationDateTime();
final String token = TOKEN_PREFIX + Jwts.builder().setSubject(email)
.claim(ROLES_CLAIM, role).setIssuedAt(new Date())
Expand Down Expand Up @@ -70,17 +70,17 @@ private String cutTokenPrefix(final String bearerToken) {

private Claims extractBody(final String token) {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
}

private Date calcExpirationDateTime() {
final LocalDateTime currentTime = LocalDateTime.now(); // 현재 시각으로 부터

final LocalDateTime expirationDateTime = currentTime
.plusDays(TOKEN_EXPIRATION_DATE_TO_PLUS) // day를 더하고
.withHour(TOKEN_EXPIRATION_FIXED_HOUR); // 고정된 시각
.plusDays(TOKEN_EXPIRATION_DATE_TO_PLUS) // day를 더하고
.withHour(TOKEN_EXPIRATION_FIXED_HOUR); // 고정된 시각

return convertLocalDateTimeToDate(expirationDateTime);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
package com.daggle.animory.common.security.exception;

import com.daggle.animory.common.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;
import com.daggle.animory.common.Response;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
public class JwtExceptionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {
log.debug("JwtExceptionFilter 동작");
try {
filterChain.doFilter(request, response);
} catch (final JwtException e) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(
new ObjectMapper()
.writeValueAsString(
Response.error(e.getMessage(), HttpStatus.UNAUTHORIZED)));
new ObjectMapper()
.writeValueAsString(
Response.error(e.getMessage(), HttpStatus.UNAUTHORIZED)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
public enum SecurityExceptionMessage {
UNAUTHORIZED("인증되지 않은 사용자입니다."),
FORBIDDEN("권한이 없습니다."),
INVALID_TOKEN("유효하지 않은 토큰입니다."),
INVALID_TOKEN_FORMAT("유효하지 않은 토큰 형식입니다."),;
;

private final String message;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.daggle.animory.common.security.exception;

public class UnAuthorizedException extends RuntimeException {
@Override
public String getMessage() {
return SecurityExceptionMessage.UNAUTHORIZED.getMessage();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

import javax.validation.Valid;

@Tag(name = "계정 API", description = "계정 관련 API 입니다.")
@Tag(name = "계정 API", description = """
최종수정일: 2023-11-06
""")
public interface AccountControllerApi {

@Operation(summary = "보호소 계정으로 회원가입 API", description = "보호소 계정으로 회원가입합니다.")
Expand All @@ -34,15 +36,14 @@ public interface AccountControllerApi {
@ApiResponse(responseCode = "400", description = """
- ID 또는 Password 불일치로 인한 로그인 실패
- Email 형식 오류, 비밀번호가 null
""", content = @Content),
@ApiResponse(responseCode = "404", description = "해당하는 보호소를 찾을 수 없는 경우", content = @Content)
""", content = @Content)
})
ResponseEntity<Response<AccountLoginSuccessDto>> login(@Valid @RequestBody AccountLoginDto request);

@Operation(summary = "이메일 중복 검증 API", description = "회원가입에 사용할 이메일이 중복되는지 검사합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "이메일 중복 검증 성공"),
@ApiResponse(responseCode = "400", description = "이미 존재하는 이메일인 경우"),
@ApiResponse(responseCode = "400", description = "이미 존재하는 이메일인 경우", content = @Content),
})
Response<Void> validateEmail(@Valid @RequestBody EmailValidateDto emailValidateDto);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

@Tag(name = "Pet", description = """
Pet 등록, 수정 및 조회 관련 API
최종수정시각: 2023-10-22 23:56
@Tag(name = "펫 API", description = """
최종수정일: 2023-11-06
""")
public interface PetControllerApi {

Expand All @@ -46,7 +45,7 @@ public interface PetControllerApi {
"\t\n 3. 빈 이미지 파일일 경우" +
"\t\n 4. 빈 비디오 파일일 경우" +
"\t\n 5. 잘못된 나이 형식일 경우", content = @Content),
@ApiResponse(responseCode = "404", description = "보호소를 찾을 수 없는 경우", content = @Content),
@ApiResponse(responseCode = "404", description = "로그인되어 있는 보호소의 권한 체크 중 해당 보호소를 DB에서 찾을 수 없는 경우", content = @Content),
@ApiResponse(responseCode = "500", description = "S3 저장 오류", content = @Content)
})
Response<RegisterPetSuccessDto> registerPet(
Expand All @@ -59,6 +58,8 @@ Response<RegisterPetSuccessDto> registerPet(
@Operation(summary = "Pet 수정 페이지 진입, 기존 펫 정보 확인",
description = "Pet 수정 페이지에서, 기존 등록된 정보를 확인하기 위해 호출하는 API 입니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "기존 펫 정보 조회 성공"),
@ApiResponse(responseCode = "403", description = "수정 권한이 없는 보호소 계정으로 해당 페이지를 조회 시도한 경우", content = @Content),
@ApiResponse(responseCode = "404", description = "존재하지 않는 펫인 경우", content = @Content)
})
Response<PetRegisterInfoDto> getPetRegisterInfo(UserDetailsImpl userDetails,
Expand All @@ -85,7 +86,8 @@ Response<PetRegisterInfoDto> getPetRegisterInfo(UserDetailsImpl userDetails,
"\t\n 3. 빈 이미지 파일일 경우" +
"\t\n 4. 빈 비디오 파일일 경우" +
"\t\n 5. 잘못된 나이 형식일 경우", content = @Content),
@ApiResponse(responseCode = "404", description = "존재하지 않는 펫 오류", content = @Content),
@ApiResponse(responseCode = "403", description = "해당 펫을 수정할 권한이 없는 경세", content = @Content),
@ApiResponse(responseCode = "404", description = "존재하지 않는 펫을 수정하려는 경우", content = @Content),
@ApiResponse(responseCode = "500", description = "S3 저장 오류", content = @Content)
})
Response<UpdatePetSuccessDto> updatePet(
Expand Down Expand Up @@ -149,11 +151,13 @@ Response<NewPetProfilesDto> getPetNewProfiles(
@Operation(summary = "Pet 상세 조회",
description = "")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "펫 상세 조회 성공"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 펫인 경우", content = @Content)
})
Response<PetDto> getPetDetail(@PathVariable int petId);



@Operation(summary = "[로그인 필요: 보호소] Pet 입양 완료 처리",
description = "입양 상태가 변경되고, 보호만료날짜가 삭제됩니다.",
parameters = {
Expand All @@ -167,8 +171,10 @@ Response<NewPetProfilesDto> getPetNewProfiles(
}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "404", description = "1. 존재하지 않는 펫인 경우" +
"\t\n 2. 보호소를 찾을 수 없는 경우", content = @Content)
@ApiResponse(responseCode = "200", description = "펫 입양 처리 성공"),
@ApiResponse(responseCode = "403", description = "해당 펫을 수정할 권한이 없는 경우", content = @Content),
@ApiResponse(responseCode = "404", description = "1. 존재하지 않는 펫인 경우" +
"\t\n 2. 로그인되어 있는 보호소의 권한 체크 중 해당 보호소를 DB에서 찾을 수 없는 경우", content = @Content)
})
Response<Void> updatePetAdopted(@AuthenticationPrincipal UserDetailsImpl userDetails,
@PathVariable int petId);
Expand Down
Loading

0 comments on commit 1eee90a

Please sign in to comment.