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 @@ -2,11 +2,19 @@

import java.io.IOException;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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 com.dnd.moddo.common.exception.ErrorResponse;
import com.dnd.moddo.common.exception.ModdoException;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -19,21 +27,43 @@ public class JwtFilter extends OncePerRequestFilter {

private final JwtAuth jwtAuth;
private final JwtUtil jwtUtil;
private final ObjectMapper objectMapper;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String token = jwtUtil.resolveToken(request);

if (token != null) {
Authentication authentication = jwtAuth.getAuthentication(token, JwtConstants.ACCESS_KEY.message);
SecurityContextHolder.getContext().setAuthentication(authentication);
try {
if (token != null) {
Authentication authentication = jwtAuth.getAuthentication(token, JwtConstants.ACCESS_KEY.message);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (ExpiredJwtException e) {
SecurityContextHolder.clearContext();
writeErrorResponse(response, HttpStatus.UNAUTHORIZED, "토큰이 만료되었습니다.");
return;
} catch (JwtException e) {
SecurityContextHolder.clearContext();
writeErrorResponse(response, HttpStatus.UNAUTHORIZED, "토큰이 유효하지 않습니다.");
return;
} catch (ModdoException e) {
SecurityContextHolder.clearContext();
writeErrorResponse(response, e.getStatus(), e.getMessage());
return;
}

filterChain.doFilter(request, response);
}

private void writeErrorResponse(HttpServletResponse response, HttpStatus status, String message) throws IOException {
response.setStatus(status.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
objectMapper.writeValue(response.getWriter(), new ErrorResponse(status.value(), message));
}

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getRequestURI().startsWith("/api/v1/user/reissue/token");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.dnd.moddo.auth.infrastructure.security.JwtAuth;
import com.dnd.moddo.auth.infrastructure.security.JwtFilter;
import com.dnd.moddo.auth.infrastructure.security.JwtUtil;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.RequiredArgsConstructor;

Expand All @@ -25,6 +26,7 @@ public class SecurityConfig {

private final JwtUtil jwtUtil;
private final JwtAuth jwtAuth;
private final ObjectMapper objectMapper;

@Bean
public BCryptPasswordEncoder passwordEncoder() {
Expand All @@ -43,7 +45,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.anyRequest().permitAll()
)
.addFilterBefore(new JwtFilter(jwtAuth, jwtUtil), UsernamePasswordAuthenticationFilter.class);
.addFilterBefore(new JwtFilter(jwtAuth, jwtUtil, objectMapper), UsernamePasswordAuthenticationFilter.class);

return http.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ public List<SettlementListResponse> findByUserAndStatus(
int limit
) {
QSettlement settlement = QSettlement.settlement;
QMember member = QMember.member;
QMember myMember = new QMember("myMember");
QMember settlementMember = new QMember("settlementMember");
QExpense expense = QExpense.expense;

BooleanExpression userCondition =
member.user.id.eq(userId);
myMember.user.id.eq(userId);

BooleanExpression statusCondition = null;

Expand All @@ -56,13 +57,13 @@ public List<SettlementListResponse> findByUserAndStatus(
? userCondition.and(statusCondition)
: userCondition;

NumberExpression<Long> memberCount = member.id.count();
NumberExpression<Long> memberCount = settlementMember.id.count();

NumberExpression<Long> completedCount =
Expressions.numberTemplate(
Long.class,
"sum(case when {0} = true then 1 else 0 end)",
member.isPaid
settlementMember.isPaid
);

JPQLQuery<Long> totalAmount =
Expand All @@ -86,8 +87,9 @@ public List<SettlementListResponse> findByUserAndStatus(
settlement.createdAt,
settlement.completedAt
))
.from(member)
.join(member.settlement, settlement)
.from(myMember)
.join(myMember.settlement, settlement)
.join(settlementMember).on(settlementMember.settlement.id.eq(settlement.id))
.where(finalCondition)
.groupBy(
settlement.id,
Expand Down
64 changes: 64 additions & 0 deletions src/test/java/com/dnd/moddo/domain/auth/service/JwtFilterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.dnd.moddo.domain.auth.service;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.*;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;

import com.dnd.moddo.auth.infrastructure.security.JwtAuth;
import com.dnd.moddo.auth.infrastructure.security.JwtFilter;
import com.dnd.moddo.auth.infrastructure.security.JwtUtil;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.FilterChain;

@ExtendWith(MockitoExtension.class)
class JwtFilterTest {

@Mock
JwtAuth jwtAuth;

@Mock
JwtUtil jwtUtil;

@Mock
FilterChain filterChain;

private final ObjectMapper objectMapper = new ObjectMapper();

@AfterEach
void tearDown() {
SecurityContextHolder.clearContext();
}

@Test
void givenExpiredToken_thenReturnTokenExpiredResponse() throws Exception {
// given
String token = "expired-token";
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
JwtFilter jwtFilter = new JwtFilter(jwtAuth, jwtUtil, objectMapper);

given(jwtUtil.resolveToken(any(MockHttpServletRequest.class))).willReturn(token);
given(jwtAuth.getAuthentication(token, "access_token"))
.willThrow(new ExpiredJwtException(null, null, "expired"));

// when
jwtFilter.doFilter(request, response, filterChain);

// then
assertThat(response.getStatus()).isEqualTo(401);
assertThat(response.getContentType()).startsWith("application/json");
assertThat(response.getContentAsString()).contains("\"message\":\"토큰이 만료되었습니다.\"");
then(filterChain).should(never()).doFilter(request, response);
}
}
Loading