2024. 8. 14. 14:38ㆍ스프링
2024/08/14
※ Filter에 대해 알아보자.
▶ Filter란?
● Filter란 Web 애플리케이션에서 관리되는 영역을 말한다.
○ Client로부터 오는 요청과 응답에 대해 최초 / 최종 단계의 위치이며 이를 통해 요청과 응답의 정보를 변경하거나 부가적인 기능을 추가할 수 있다.
● 주로 범용적으로 처리해야 하는 작업들, 예를들어 로깅 및 보안 처리에 활용한다.
○ 또한 인증, 인가와 관련된 로직들을 처리할 수도 있다.
○ Filter를 사용하면 인증, 인가와 관련된 로직을 비즈니스 로직과 분리하여 관리할 수 있다는 장점이 있다.
▶ Filter Chain
● Filter는 한 개만 존재하는 것이 아니라 이렇게 여러 개가 Chain 형식으로 묶여서 처리될 수 있다.
▶ Filter 적용
📌 요청 URL의 인가 처리 및 인증 처리를 진행할 수 있는 Filter를 구현해보자.
추가로 요청 URL을 로깅해주는 Filter도 구현해보자.
1. Request URL Logging
@Slf4j(topic = "LoggingFilter")
@Component
@Order(1)
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 전처리
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
log.info(url);
chain.doFilter(request, response); // 다음 Filter 로 이동
// 후처리
log.info("비즈니스 로직 완료");
}
}
● @Order(1) 로 필터의 순서를 지정한다.
○ @Order(1)은 첫 번째 필터를 뜻한다.
● chain.doFilter(request, response); 다음 Filter로 이동시킨다.
● log.info("비즈니스 로직 완료");
○ 작업이 다 완료된 후 Client에 응답 전 로그가 작성된 것을 확인할 수 있다.
2. AuthFilter : 인증 및 인가 처리 필터
@Slf4j(topic = "AuthFilter")
@Component
@Order(2)
public class AuthFilter implements Filter {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
public AuthFilter(UserRepository userRepository, JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
if (StringUtils.hasText(url) &&
(url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
) {
// 회원가입, 로그인 관련 API 는 인증 필요없이 요청 진행
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
// 나머지 API 요청은 인증 처리 진행
// 토큰 확인
String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);
if (StringUtils.hasText(tokenValue)) { // 토큰이 존재하면 검증 시작
// JWT 토큰 substring
String token = jwtUtil.substringToken(tokenValue);
// 토큰 검증
if (!jwtUtil.validateToken(token)) {
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 사용자 정보 가져오기
Claims info = jwtUtil.getUserInfoFromToken(token);
User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
new NullPointerException("Not Found User")
);
request.setAttribute("user", user);
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
throw new IllegalArgumentException("Not Found Token");
}
}
}
}
● httpServletRequest.getRequestURI() 요청 URL을 가져와서 구분한다. (인가)
○ "/api/user" , "/css" , "/js" 로 시작하는 URL은 인증 처리에서 제외시킨다.
● 그 외 URL은 인증 처리를 진행한다.
○ jwtUtil.getTokenFromRequest(httpServletRequest);
✅ httpServletRequest 에서 Cookie 목록을 가져와 JWT가 저장된 Cookie를 찾는다.
✅ getTokenFromRequest 메서드를 JwtUtil에 구현한다.
// HttpServletRequest 에서 Cookie Value : JWT 가져오기
public String getTokenFromRequest(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if(cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(AUTHORIZATION_HEADER)) {
try {
return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode 되어 넘어간 Value 다시 Decode
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
}
return null;
}
○ tokenValue가 존재하면 토큰 파싱, 검증을 진행하고 사용자 정보를 가져온다.
○ 가져온 사용자 username을 사용해서 DB에 사용자가 존재하는지 확인하고 존재하면 인증이 완료된 것
○ 사용자 정보가 필요한 Controller API에 인증완료된 User 객체를 전달해다.
import com.sparta.springauth.entity.User;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/api")
public class ProductController {
@GetMapping("/products")
public String getProducts(HttpServletRequest req) {
System.out.println("ProductController.getProducts : 인증 완료");
User user = (User) req.getAttribute("user");
System.out.println("user.getUsername() = " + user.getUsername());
return "redirect:/";
}
}
○ 사용자 본인이 등록한 제품만 조회하는 기능의 API라 가정한다.
○ Filter에서 인증 처리되어 넘어온 User 객체를 사용하면 API 요청을 한 해당 사용자가 등록한 제품만 조회할 수 있다.
다음 포스트에서는 Spring Security에서
Filter가 어떻게 적용되는지 알아보도록 하겠습니다.
※ 위 이미지들은 스파르타코딩클럽에 저작권이 있으므로 무단 도용 금지 및 상업 목적으로 사용할 수 없습니다.
'스프링' 카테고리의 다른 글
스프링 - RESTful API이란 무엇일까? (0) | 2024.08.16 |
---|---|
스프링 - Spring Security 프레임워크 (0) | 2024.08.14 |
스프링 - 영속성 컨텍스트란? (0) | 2024.08.13 |
스프링 - Entity란? (0) | 2024.08.12 |
스프링 - Spring Data JPA란 무엇일까? (0) | 2024.08.09 |