Skip to content

Commit

Permalink
fix: Security Exception Handling에서 응답이 제대로 나오도록 처리
Browse files Browse the repository at this point in the history
  • Loading branch information
xGreenNarae committed Nov 7, 2023
1 parent 33f9921 commit 8355c59
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

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.*;
Expand Down Expand Up @@ -107,6 +108,14 @@ public ResponseEntity<Response<Void>> handleShelterAlreadyExistException(final E
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) {
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
@@ -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 @@ -28,9 +28,9 @@
class PetTest extends AcceptanceTest {

final MockMultipartFile image = new MockMultipartFile("profileImage", "image.jpg", "image/jpeg",
"image" .getBytes(StandardCharsets.UTF_8));
"image".getBytes(StandardCharsets.UTF_8));
final MockMultipartFile video = new MockMultipartFile("profileVideo", "video.mp4", "video/mp4",
"video" .getBytes(StandardCharsets.UTF_8));
"video".getBytes(StandardCharsets.UTF_8));

@Nested
class 강아지_등록 {
Expand Down

0 comments on commit 8355c59

Please sign in to comment.