코딩과 결혼합니다

230705 - Spring 필터 적용 코드 분석 본문

2세/Spring

230705 - Spring 필터 적용 코드 분석

코딩러버 2023. 7. 5. 22:18
728x90
@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("비즈니스 로직 완료");
    }
}

@Slf4j 어노테이션을 사용하여 LoggingFilter 클래스에 대한 로깅 기능을 추가한다. 이 어노테이션은 lombok 프레임워크를 통해 코드에서 로깅을 쉽게 처리할 수 있도록 해준다.

@Order(1) 필터의 실행 순서를 지정.

doFilter 에서는 ServletRequset / ServletResponse를 받아올 수 있고, FilterChain 을 이용해 필터간의 이동이 가능하다.

//전처리 에서는 ServeltRequest를 HttpServletRequset로 캐스팅하여 요청의 URI를 얻고, log.info()를 사용하여 해당 URL을 로깅한다.

 

 

로깅? : 애플리케이션의 동작과 관련된 정보를 기록하는 것. 주로 디버깅, 모니터링, 오류 추적 등을 위해 사용된다.

-일반적으로 로그 메시지는 로그 레벨과 함께 기록되며, 로그 레벨에는 다음과 같은 종류가 있다.

  1. DEBUG: 디버깅 목적으로 사용되는 세부 수준의 로그입니다. 애플리케이션의 내부 상태와 변수 값을 추적하고 분석하는 데 사용됩니다.
  2. INFO: 정보성 로그로서, 애플리케이션의 주요 이벤트 및 상태 정보를 기록합니다. 예를 들어, 애플리케이션의 시작 또는 중지, 요청 처리 결과 등을 기록할 수 있습니다.
  3. WARN: 경고성 로그로서, 애플리케이션이 예상치 않은 동작을 했거나 잠재적인 문제 상황에 진입했을 때 기록합니다. 애플리케이션은 계속 작동하지만 주의해야 할 상황임을 알려줍니다.
  4. ERROR: 에러가 발생했을 때 기록하는 로그입니다. 예외 상황, 처리 불가능한 오류 또는 중대한 문제를 나타내며, 애플리케이션이 오작동하거나 중단될 수 있습니다.

LoggingFilter 클래스의 예제서는 log.info()를 사용하여 로그 메시지를 기록하고 있다. 이를 통해 애플리케이션의 요청

URL과 "비즈니스 로직 완료" 라는 메시지를 로깅한다. 이러한 로그는 애플리케이션의 동작을 추적하고, 요청 처리의 진행 상황 및 완료 여부를 확인하는데 도움을 준다.

 

전처리, 후처리 로깅을 수행하고 chain.doFilter() 메서드를 호출하여 다음 필터로 제어를 넘긴다.

 

 

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String url = httpServletRequest.getRequestURI();

받아온 메시지를 HttpServletRequest 형태로 만들어 httpServletRequest 안에 값을 저장한다.

다음, url 에 .getRequestURI() 메서드를 이용해 URI를 저장한다.

 if (StringUtils.hasText(url) &&
                (url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
        ) {
            // 회원가입, 로그인 관련 API 는 인증 필요없이 요청 진행
            chain.doFilter(request, response); // 다음 Filter 로 이동

.hasText 로 url이 존재하는지 확인 후 url이 아래의 조건들을 만족한다면 바로 다음필터로 이동한다.

회원가입, 로그인 관련 API는 인증이 필요하지 않기 때문이다.

} else {
            // 나머지 API 요청은 인증 처리 진행
            // 토큰 확인
            String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);

그 외의 것들은 인증 처리를 진행 하는데 먼저 토큰을 확인한다.

 


jwt > 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;
    }

필터는 DispatcherSvervlet보다 더 앞쪽에 위치하기 때문에 @CookieValue라는 어노테이션 사용할 수 없다. 그렇기 때문에 직 Cookie를 뽑아오는 메서드 구현하였다.

 

req.getCookies 로 여러 개가 담긴 Cooke들을 전부 배열로 가져온다.

다음 Cookies가 존재하면 그 쿠키들 중에서 이름이 AUTHORIZATION_HEADER 인지 아닌지 체크 후 

맞다면 다지고 온다. + decode 까지 해서 return

 

만약 오류가 발생하거나 Cookie 가 없다면 null 반환


            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");
            }

토큰이 존재하면 .substringToken 을 하여 순수한 Token만 뽑아낸다.

토큰 검증 후에 Token에서 사용자 정보를 가져온다.

Token 이 넘어올 때에 해당유저가 있다는 보장이 없으므로 데이터베이스에 접근해서 해당유저가 있는지 체크한다.

 

request 객체를 Controller로 넘길 것이기 때문에 .setAttribute로 유저 객체를 넣어 사용가능하게 하였다. 

끝나면 다음 필터로 이동한다.