스프링 입문

[스프링 입문] 6.4 Filter - Intercepter 활용 (2)

코딩펭귄 2024. 1. 10. 11:57

Interceptor

- Filter와 유사한 형태로 존재

- Spring Context에 등록됨 (Filter - web application에 등록)

- AOP와 유사한 기능 제공

- 인증단계 처리, Logging 하는 데 사용

- 이를 선/후처리 하며 Service business logic과 분리시킴 -> 서비스 인증안된것들 걸러내는 용도

 

 

예제

 

public controller : 아무런 권한이 없는 사용자, 즉 모두가 들어올 수 있도록 = openAPI 형태

private controller : 내부사용자, 즉 세션이 인증된 사용자만 넘김

 

이 둘(public / private)의 권한차이를 주는 방법 : Interceptor에서 메소드/컨트롤러에 @Auth 어노테이션이 붙어있으면 세션을 검사해서, 있을때만 통과시키고 없을때는 통과시키지 않음

 

특정권한을 Interceotor에서 검사를 함

 

 

annotation 패키지

package com.example.interceptor.annotation;


import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Auth {
}

 

config 패키지

package com.example.interceptor.config;

import com.example.interceptor.interceptor.AuthInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@RequiredArgsConstructor // 생성자에서 주입받도록 함
public class MvcConfig implements WebMvcConfigurer {

    private final AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // registry 한 순서대로 진행, depth두고 활용할 수 있음
        registry.addInterceptor(authInterceptor).addPathPatterns("/api/private/*"); //검사하고싶은 패턴 넣을수있음
    }
}

 

controller 패키지

package com.example.interceptor.controller;

import com.example.interceptor.annotation.Auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api/private")
@Auth
@Slf4j //log를 작성위해
public class PrivateController {

    @GetMapping("/hello")
    public String hello(){
        log.info("private hello controller");
        return "private hello";
    }

}
package com.example.interceptor.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/public")
public class PublicController {

    @GetMapping("/hello")
    public String hello(){
        return "public hello";
    }

}

 

exception 패키지

package com.example.interceptor.exception;

public class AuthException extends RuntimeException{
}

 

 

handler 패키지

package com.example.interceptor.handler;

import com.example.interceptor.exception.AuthException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice // AuthException 받아서
public class GlobalExceptionHandler {

    @ExceptionHandler(AuthException.class)
    public ResponseEntity authException(){ //UNAUTHORIZED라는 http status (401에러 내림)
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
}

 

 

interceptor 패키지

package com.example.interceptor.interceptor;

import com.example.interceptor.annotation.Auth;
import com.example.interceptor.exception.AuthException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

@Slf4j
@Component //Spring에 의해 관리돼야하기 때문에 붙이는 어노테이션
public class AuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI();

        URI uri = UriComponentsBuilder.fromUriString(request.getRequestURI())
                .query(request.getQueryString())
                .build()
                .toUri();

        log.info("request url : {}", url);

        //권한체크
        boolean hasAnnotation = checkAnnotation(handler, Auth.class);
        log.info("has annotation : {}", hasAnnotation);

        // 나의 서버는 모두 public으로 동작을 함.
        // 단, Auth권한을 가진 요청에대해서는 세션, 쿠키
        if(hasAnnotation){
            // 권한체크
            String query = uri.getQuery();
            if(query.equals("name = steve")){ //steve인 경우에만 들어옴
                return true;
            }
            throw new AuthException(); //권한이 없으면 exception 던짐 -> handler가 받음
        }

        return true; // 이면 Interceptor를 넘어서서 안의 로직을 탐
    }

    //어노테이션이 달려있는지 보는 용도
    private boolean checkAnnotation(Object handler, Class clazz){

        // resource javascript, html, 에 대한 요청일때는 무조건 통과시킴
        if( handler instanceof ResourceHttpRequestHandler){
            return true; //통과시킴
        }

        //annotation check
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        if(null != handlerMethod.getMethodAnnotation(clazz) || null != handlerMethod.getBeanType().getAnnotation(clazz)){
            return true; // Auth annotation가지고있는경우 무조건 true
        }
        return false;
    }
}