Spring 로직이 실행되기 전에 사용자의 요청을 한번 걸러주는 역할을 합니다.
Spring에서는 Filter를 직접 구현할 필요 없이 많이 사용되는 목적에 따라 이미 구현이 되어 있습니다. 우리는 그 중 하나를 선택하여 사용하면되는데 OncePerRequestFilter 를 제외하고는 거의 사용할 일이 별로 없습니다.
OncePerRequestFilter
CharacterEncodingFilter
HiddenHttpMethodFilter
GET, POST 만을 지원 → _method 파라미터로 PUT, DELETE 등 매핑 가능하게 함FormContentFilter
application/x-www-form-urlencoded 데이터를 PUT, PATCH, DELETE 요청에서도 읽을 수 있게 함RequestContextFilter
RequestContextHolder 에 바인딩RequestScope Bean 사용 시 필요ETC...
💡 옛날에는 서버에 Front, Back이 공존했는데 이때 리다이렉트가 발생해서 Filter가 두 번 호출되는 경우가 있었습니다.
OncePerRequestFilter의 한 번의 필터 실행 보장 기능이 존재하는 이유는 이 때문입니다. 현재는 REST API를 사용하고 있어 리다이렉트가 발생하는 경우는 거의 존재하지 않습니다.
@Component
public class Filter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 요청이 들어갈 때 실행되는 부분
System.out.println("Filter로 들어간다 ");
// 필터 계속 진행
filterChain.doFilter(request, response);
// 요청이 나갈 때 실행되는 부분
System.out.println("Filter로 나간다 ");
}
}
filterChain.doFilter(request, response); 를 기준으로 필터를 통과해서 들어올 때와 나갈 때를 구분 짓습니다.
FilterChain이라는 Filter의 묶음이 있고 필요하면 Filter를 하나씩 추가하여 사용할 수 있습니다.
@Component
@Order(1)
public class Filter1 extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 요청이 들어갈 때 실행되는 부분
System.out.println("Filter1로 들어간다 ");
// 필터 계속 진행
filterChain.doFilter(request, response);
// 요청이 나갈 때 실행되는 부분
System.out.println("Filter1로 나간다 ");
}
}
@Component
@Order(2)
public class Filter2 extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 요청이 들어갈 때 실행되는 부분
System.out.println("Filter2로 들어간다 ");
// 필터 계속 진행
filterChain.doFilter(request, response);
// 요청이 나갈 때 실행되는 부분
System.out.println("Filter2로 나간다 ");
}
}
@Order 어노테이션을 사용하여 Filter의 순서를 지정할 수 있습니다.
JWT는 출입증과 같습니다.
HS256)name, age, email, role)Secret Key)implementation "io.jsonwebtoken:jjwt-api:0.12.5"
runtimeOnly "io.jsonwebtoken:jjwt-impl:0.12.5"
runtimeOnly "io.jsonwebtoken:jjwt-jackson:0.12.5" // JSON 직렬화
// 터미널에 openssl rand -base64 64 입력 시 비밀 키 생성
jwt.secret.key = 0ydHRc8EG9P/RJDjjPbE8dezJ0IXc8z61Y7hDZRJKkkrDW81g9FPKb0XdU5Knun3kIBEabqtzGnsNSqbufv79g==
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import java.util.Date;
import javax.crypto.SecretKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class JwtUtil {
public static final String BEARER_PREFIX = "Bearer ";
private static final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}") // application 에 있는 key 가져오기
private String secretKeyString;
private SecretKey key;
private JwtParser parser;
// @PostConstruct 어플리케이션 실행 될 때 가장 먼저 실행 되게 하는 어노테이션
@PostConstruct
public void init() {
byte[] bytes = Decoders.BASE64.decode(secretKeyString);
this.key = Keys.hmacShaKeyFor(bytes);
this.parser = Jwts.parser()
.verifyWith(this.key)
.build();
}
// 토큰 생성
public String generateToken(String username) {
Date now = new Date();
return BEARER_PREFIX + Jwts.builder()
.claim("username", username)
.issuedAt(now)
.expiration(new Date(now.getTime() + TOKEN_TIME))
.signWith(key, Jwts.SIG.HS256)
.compact();
}
// 토큰 검증
public boolean validateToken(String token) {
if (token == null || token.isBlank()) return false;
try {
parser.parseSignedClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
// 개별 예외 분리 없음: 서명/형식/만료 등 모든 실패를 한 번에 처리
log.debug("Invalid JWT: {}", e.toString());
return false;
}
}
// 토큰 복호화
private Claims extractAllClaims(String token) {
return parser.parseSignedClaims(token).getPayload();
}
public String extractUsername(String token) {
return extractAllClaims(token).get("username", String.class);
}
}