LUMI_dev
Filter 필터 본문

Filter
: Web 애플리케이션에서 관리되는 영역
: Client로부터 오는 요청과 응답에 대해 최초/최종 단계의 위치
: 요청과 응답의 정보를 변경하거나 부가적인 기능 추가 가능
: @CookieValue라는 어노테이션을 사용하지 못함
: 주로 범용적으로 처리해야 하는 작업에 활용 (ex. 로깅, 보안 처리)
: 인증과 인가에 관련된 로직들 처리
장점)
인증, 인가와 관련된 로직을 비즈니스 로직과 분리하여 관리할 수 있음
Filter Chain

Filter는 여러 개가 Chain 형식으로 묶여서 처리될 수 있음
실습) 요청 URL의 인가 처리 및 인증 처리를 진행할 수 있는 Filter와 요청 URL을 로깅해주는 Filter의 구현

LoggingFilter.java
package com.sparta.springauth.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j(topic = "LoggingFilter") //여기 topic을 작성한 이름대로 로그가 찍힘
@Component
@Order(1) //필터 체인 순서
public class LoggingFilter implements Filter { //필터 역할 수행하게 하기 위해 Filter 인터페이스 구현
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//FilterChain은 filter 이동할 때 사용하려고 쓰는 것임
// filter(전처리) > dispatchServlet > Controller > View >dispatchServlet>filter (최종 결과물) (후처리)
// 전처리 (HttpServlet에 요청이 들어가기 전에 Filter 먼저 수행)
//request에서 URL 정보를 가져올 거임
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI(); //url 정보 가져오기
log.info(url);
//log.error(); 에러 로그
chain.doFilter(request, response); // 다음 Filter 로 이동
// 후처리
log.info("비즈니스 로직 완료");
}
}
AuthFilter.java

AuthController

근데 filter은 HTTP 요청이 DispatcherServlet에 도달하기 전에 실행 ( DispatcherServlet보다 앞단임 )
→ 그래서 @CookieValue라는 어노테이션을 사용하지 못함
package com.sparta.springauth.filter;
import com.sparta.springauth.entity.User;
import com.sparta.springauth.jwt.JwtUtil;
import com.sparta.springauth.repository.UserRepository;
import io.jsonwebtoken.Claims;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
@Slf4j(topic = "AuthFilter")
@Component
@Order(2) //loggingfilter 탄 후에 doFilter로 이 filter 탐
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(); //url을 한번 더 가져오는 이유 : 제어문을 사용해서 한번 제어를 할 것임 => StringUtils.hasText
//제어문 -> StringUtils.hasText로 null인지 공백인지 확인(공백 아니면) && .startsWith ()안의 내용으로 시작하나 안하나? -> 해당 url이 ()안의 내용으로 시작하나 안하나
// 회원가입, 로그인 관련 API 는 인증 필요없이 요청 진행 (회원가입은 당연히 인증할 필요 없고, 로그인은 인증하려고 로그인하는 거니까 필요 x /그 외 css, js도 인증하면 안됨)
//그래서 인증을 하지 않기 위한 url들은 제어문으로 제어
if (StringUtils.hasText(url) &&
(url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
) {
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
// 나머지 API 요청은 인증 처리 진행
//여기서 AuthController 다녀오기
// 토큰 확인
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");
}
}
}
}
jwtUtil - getTokenFromRequest
// HttpServletRequest 에서 Cookie Value : JWT 가져오기
public String getTokenFromRequest(HttpServletRequest req) {
//Cookies를 배열로 가져옴
Cookie[] cookies = req.getCookies();
if(cookies != null) {
for (Cookie cookie : cookies) {
//쿠키 이름이 우리가 가지고 오려고 하는 AUTHORIZATION인지 체크 + 맞으면 가져오기
if (cookie.getName().equals(AUTHORIZATION_HEADER)) {
try {
return URLDecoder.decode(cookie.getValue(), "UTF-8"); //우리는 넘길 때 Encode해서 넘기기 때문에 넘어간 Value를 뽑아올 때는 다시 Decode
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
}
return null; //에러가 나거나 해당되는 쿠키가 없으면 null
}
다시 AuthFilter에서 토큰 확인 이후
package com.sparta.springauth.filter;
import com.sparta.springauth.entity.User;
import com.sparta.springauth.jwt.JwtUtil;
import com.sparta.springauth.repository.UserRepository;
import io.jsonwebtoken.Claims;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
@Slf4j(topic = "AuthFilter")
@Component
@Order(2) //loggingfilter 탄 후에 doFilter로 이 filter 탐
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(); //url을 한번 더 가져오는 이유 : 제어문을 사용해서 한번 제어를 할 것임 => StringUtils.hasText
//제어문 -> StringUtils.hasText로 null인지 공백인지 확인(공백 아니면) && .startsWith ()안의 내용으로 시작하나 안하나? -> 해당 url이 ()안의 내용으로 시작하나 안하나
// 회원가입, 로그인 관련 API 는 인증 필요없이 요청 진행 (회원가입은 당연히 인증할 필요 없고, 로그인은 인증하려고 로그인하는 거니까 필요 x /그 외 css, js도 인증하면 안됨)
//그래서 인증을 하지 않기 위한 url들은 제어문으로 제어
if (StringUtils.hasText(url) &&
(url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
) {
log.info("인증 처리를 하지 않은 URL :"+url);
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
// 나머지 API 요청은 인증 처리 진행
//여기서 AuthController 다녀오기
//JwtUtil > getTokenFromRequest
// 토큰 확인
String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest); //우리가 원하는 Authorization이름의 토큰을 가져오는 메서드
//토큰이 있는지/없는지 봄
if (StringUtils.hasText(tokenValue)) { // 토큰이 존재하면 검증 시작
// JWT 토큰 substring (Bearer 없앰)
String token = jwtUtil.substringToken(tokenValue);
// 토큰 검증
if (!jwtUtil.validateToken(token)) {
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 사용자 정보 가져오기
Claims info = jwtUtil.getUserInfoFromToken(token);
//Token이 넘어올 때는 해당 유저가 있는지 모름 -> 진짜로 해당 유저가 있는지 체크
User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
new NullPointerException("Not Found User")
);
request.setAttribute("user", user); //Controller에서 사용하게 하기 위해
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
throw new IllegalArgumentException("Not Found Token");
}
}
}
}
ProductController
package com.sparta.springauth.controller;
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;
//Filter에서 user Request 객체에 넣은 거 받아와볼 것임
@Controller
@RequestMapping("/api")
public class ProductController {
@GetMapping("/products")
public String getProducts(HttpServletRequest req) {
System.out.println("ProductController.getProducts : 인증 완료");
//가져올 때는 .getAttribute
User user = (User) req.getAttribute("user");
System.out.println("user.getUsername() = " + user.getUsername());
return "redirect:/";
}
}
실행시켰을 때 다음과 같은 결과

만약 url이 js/ css/ 등이 아니면..
2025-02-06T15:18:08.399+09:00 INFO 35296 --- [spring-auth] [nio-8080-exec-8] LoggingFilter : 비즈니스 로직 완료
2025-02-06T15:18:08.407+09:00 INFO 35296 --- [spring-auth] [nio-8080-exec-9] LoggingFilter : /
Hibernate:
/* <criteria> */ select
u1_0.id,
u1_0.email,
u1_0.password,
u1_0.role,
u1_0.username
from
users u1_0
where
u1_0.username=?
'스파르타 코딩 클럽 | 자바 심화 과정 > Spring Master (숙련 주차)' 카테고리의 다른 글
Spring Security - #2. 로그인 (세션) // @AuthenticationPrinciple (0) | 2025.02.07 |
---|---|
Spring Security - #1.Spring Security 프레임워크 - Filter 대신 인가,인증 편리하게 해줌 (0) | 2025.02.06 |
JWT 다루기 (1) | 2025.02.06 |
사용자 관리하기 - #2. 로그인 구현 JWT (1) | 2025.02.05 |
사용자 관리하기 - #1. 회원가입 구현 (구현 편) / 양방향, 단방향 암호 알고리즘 (0) | 2025.02.05 |