Filter
1. Filter
1) 스프링 흐름도

2) OncePerRequestFilter
- 요청 당 한 번만 실행되도록 보장
- ex) 인증/인가, 로깅, 트랜잭션 관리
- 가장 많이 사용됨
3) When
- 로깅(요청/응답 로깅): 로그를 필터에만 남겨둠
- 공통 인증/권한 체크(JWT 등): 컨트롤러 전 필터로 인증 실패 걸러냄
2. FilterChain 활용
1) 코드
@Component
public class NbcamFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 요청이 들어갈 때 실행되는 부분
System.out.println("✅ NbcamFilter로 들어간다 ");
// 필터 계속 진행
filterChain.doFilter(request, response);
// 요청이 나갈 때 실행되는 부분
System.out.println("✅ NbcamFilter로 나간다 ");
}
}
- OncePerRequestFilter 사용
2) FilterChain
// 요청이 들어갈 때 실행되는 부분
System.out.println("✅ NbcamFilter로 들어간다 ");
// 필터 계속 진행
filterChain.doFilter(request, response);
// 요청이 나갈 때 실행되는 부분
System.out.println("✅ NbcamFilter로 나간다 ");
- 요청 들어갈 때 실행되는 부분 > FilterChain 코드(구분기준) > 요청 나갈 때 실행되는 부분
- Controller 앞에 실행
- FilterChain: 필터 묶음 단위 / 필터들을 여러개 만들어 진행 가능
3) 필터 순서 지정
- 필터 순서 지정하는 방법: @Order 어노테이션 통해 지정
- 요청 들어올 때: @Order(1) -> @Order(2)
- 요청 나갈 때: @Order(2) -> Ordef(1)
- 순서: 요청 들어옴 > 필터1 > 필터2 > 컨트롤러 > 필터2 > 필터1
JWT 이해
1. JWT
1) 구성
- HEADER(암호화된 알고리즘, 정보)
- PAYLOAD(회원정보)
- SIGNATURE(암호화된 인증키)

2) 사용할 때 필요한 것
- 어떤 암호화 알고리즘을 사용할 것인지 ex) HS256
- 어떤 정보를 넣을 것인지 ex) name, role
- 어떤 암호화키로 암호화 할 것인지 ex) Secret Key
- 만료시간: 만료시간이 없으면 보안에 취약함
2. JWT 흐름
1) 토큰을 발행해주는 경우
- 사용자 입장: 인증/인가를 위해 로그인
- 서버 입장: 사용자가 보낸 정보 기반으로 로그인 시도, 로그인 성공하면 JWT 토큰 발행
2) 토큰을 검사하는 경우
- 사용자 입장: postman을 통해 요청 보냄, JWT 토큰 보내고 싶으면 헤더에 포함해서 보냄
- 서버 입장: JWT 토큰 있는지 검사, 유효한지 검사, 유효하면 JWT 토큰 안에 있는 값을 복호화
3. JWT 실습
- 기본 셋팅
더보기
더보기
더보기

- 프로젝트 구성

- build.gradle 추가
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 직렬화
- application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/nbcam
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: create-drop
properties:
hibernate:
format_sql: true
defer-datasource-initialization: true
## 암호화에 사용할 비밀키
jwt:
secret:
key: IfthisgetsstolenitsabigproblemIfthelengthistooshortthesecurityisnotsufficientsoanerroroccurs
- JWT
더보기
더보기
더보기
- JwtUtil
package org.example.nbcam_addvanced_1.common.utils;
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.yml 에 있는 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);
}
}
- UserController
package org.example.nbcam_addvanced_1.user.controller;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.nbcam_addvanced_1.common.utils.JwtUtil;
import org.example.nbcam_addvanced_1.user.model.response.LoginResponseDto;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
@Slf4j
public class UserController {
private final JwtUtil jwtUtil;
@GetMapping("/get")
public String getUserInfo() {
log.info("호출");
return "호출 되었습니다.";
}
// 토큰 생성 테스트
@PostMapping("/login")
public ResponseEntity<LoginResponseDto> login() {
String token = jwtUtil.generateToken("kim-dong-hyun");
return ResponseEntity.ok(new LoginResponseDto(token));
}
// 토큰 검증 테스트
@GetMapping("/validate")
public ResponseEntity<Boolean> checkValidate(HttpServletRequest request) {
String authorizationHeader = request.getHeader("Authorization");
String jwt = authorizationHeader.substring(7);
Boolean validate = jwtUtil.validateToken(jwt);
return ResponseEntity.ok(validate);
}
// 토큰 복호화 테스트
@GetMapping("/username")
public ResponseEntity<String> getUsername(HttpServletRequest request) {
String authorizationHeader = request.getHeader("Authorization");
String jwt = authorizationHeader.substring(7);
String username = jwtUtil.extractUsername(jwt);
return ResponseEntity.ok(username);
}
}
Filter와 같이 사용하는 JWT
1. JwtFilter
package org.example.nbcam_addvanced_1.common.filter;
import static org.example.nbcam_addvanced_1.common.utils.JwtUtil.BEARER_PREFIX;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.nbcam_addvanced_1.common.utils.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
@Slf4j
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// JWT 검증이 필요 없는 경우 조 ex) 로그인
String requestURI = request.getRequestURI();
if(requestURI.equals("/api/user/login")) {
filterChain.doFilter(request,response);
return;
}
// JWT 토큰이 있는지 없는지 검사
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader == null || !authorizationHeader.startsWith(BEARER_PREFIX)) {
log.info("JWT 토큰이 필요 합니다.");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT 토큰이 필요 합니다.");
return;
}
// JWT 토큰이 있다면 유효한 토큰인지 검사
String jwt = authorizationHeader.substring(7);
if (!jwtUtil.validateToken(jwt)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("{\"error\": \"Unauthorized\"}");
}
// JWT 토큰에서 복호화 한 데이터 저장하기
String username = jwtUtil.extractUsername(jwt);
request.setAttribute("username", username);
filterChain.doFilter(request, response);
}
}
2. HttpServletRequest
1) 흐름
- Postman 요청을 보낼 때 응답 부분은 비어 있고 API 통신이 완료되면 채워지는 것처럼 1번의 요청당 1개의 servlet이 위의 그림처럼의 흐름을 돌면서 빈칸을 채워 오는 것

2) attribute
- Postman 기준 윗쪽 request에 있는 모든 정보를 담고 있고 그것 이외로 Map 타입의 attribute 라는 저장공간이 있어서 attribute 에 JWT 에서 복호화한 정보를 담아서 Controller로 넘겨주는 것

'🔌 SPARTA > Courses' 카테고리의 다른 글
| 스탠다드반 8회차 : 로깅(AOP)와 인증/인가의 시작(JWT) (0) | 2026.03.04 |
|---|---|
| Spring Security와 SecurityContext (0) | 2026.03.03 |
| [숙련 Spring] Spring Data JPA (0) | 2026.02.18 |
| [숙련 Spring] Spring Data JPA (0) | 2026.02.11 |
| [숙련 Spring] Spring Boot 활용하기 (0) | 2026.02.10 |