Spring-Lv3 / spring-lv3

0 stars 0 forks source link

annotation/MyEnum에 있는 파일들과 jwt 디렉토리 안에 있는 jwtRequired의 역할이 무엇인가요?? #15

Open alaneelee opened 10 months ago

alaneelee commented 10 months ago
package com.sparta.adminserver.annotation.MyEnum;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "MyEnum annotation : ")
public class EnumValidator implements ConstraintValidator<MyEnum, String> {
    private Class<? extends Enum<?>> enumClass;

    @Override
    public void initialize(MyEnum constraintAnnotation){
        this.enumClass = constraintAnnotation.enumClass();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        Enum<?>[] enumConstants = enumClass.getEnumConstants();
        if (enumConstants == null) {
            log.error("enum Constants is null");
            return false;
        }
        for(Enum<?> enumValue : enumConstants){
            if(enumValue.name().equals(value)){
                return true;
            }
        }
        return false;
    }
}
package com.sparta.adminserver.annotation.MyEnum;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

// Custom Contraint Annotation 생성시 message, groups, payload 정의
@Documented // 문서
@Target(ElementType.FIELD) // 여기를 타겟으로
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 유지
@Constraint(validatedBy = {EnumValidator.class}) //
public @interface MyEnum {
    // 오류 메세지 관리
    String message() default "선택할 수 없는 값 입니다.";
    // 상황별 validattion 제어
    Class<?>[] groups() default{};
    Class<? extends Payload>[] payload() default{};
    // 제약할 Enum
    Class<? extends Enum<?>> enumClass();
}
package com.sparta.adminserver.jwt;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

위 3가지 파일의 역할과 각 어떤 기능을 하는 것 인지가 궁금합니다,,,, :)

BYEONGRYEOL commented 10 months ago
  1. @interface MyEnum 제가 JPA에서 Enum형식을 편리하게 DB로 저장할 수 있는 지 모른채로 구글링이 시작되서 한건데, Client -> Server 전달시 서비스단에서는 Enum으로 처리해야하지만 String 형식으로 입력 및 전달받아야한다고 오해해서

먼저 Entity에는 String 형식으로 저장되어있고, LectureRequestDto 에서도 String 형식으로 전달받지만, @Valid 어노테이션으로 유효성 검증을 Controller 단에서 하고있습니다. 아래는 LectureRequestDto 클래스의 내용인데,

@Getter
@Setter
public class LectureRequestDto {
    private String name;
    private Long price;
    private String comment;
    @MyEnum(enumClass = LectureCategoryEnum.class)
    private String category;
    private Long tutorId;
}

category를 String 형식으로 받되 제가 정의한 MyEnum어노테이션을 붙여서 시각적으로 LectureCategoryEnum안에 정의된 값만 입력받기를 원한다고 알 수 있죠,

어노테이션의 enumclass() option으로 전달한 LectureCategoryEnum이 이 ConstraintValidator<MyEnum, String> 를 상속받은 EnumValidator 안의 enumclass로 전달될 수 있도록 코드를 짰고, initialzie와 isValid 함수는 ConstraintValidator<MyEnum, String> 상속받으면서 override했고, ConstraintValidator<MyEnum, String> 는 @Vaild어노테이션 내부에 정의되어있는 유효한 값을 평가하는 클래스입니다.

  1. JwtRequired
@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        if (method.isAnnotationPresent(JwtRequired.class) || handlerMethod.getBeanType().isAnnotationPresent(JwtRequired.class)) {
            String token = jwtUtil.getJwtFromHeader(request); // bearer 떼진 token
            if(!jwtUtil.validateToken(token)){
                return false;
            }
            // claim 담기
            Claims claims = jwtUtil.getUserInfoFromToken(token);
            request.setAttribute("user", claims);
        }
        return true;
    }

JwtInterceptor에서 override한 함수인데, security filter chain을 사용하지 않고(swagger의 접속을 허용하는 것이 어려워서), Interceptor를 이용하게되었고, 그 과정에서, 위 코드에서 if문만 말씀 드리면, 모든 클라이언트에 요청을 받을때마다 이 Interceptor가 가로채서 현재 서블릿이 클라이언트의 요청에 의해 찾아준 실행할 메소드 위에 @JwtRequired 어노테이션이 달려 있는 경우에만 토큰 검증을 시작하겠다는 로직입니다.

BYEONGRYEOL commented 10 months ago

@alaneelee 궁금하신거있으시면 더 물어봐주세용

alaneelee commented 10 months ago

답변 감사합니다,,! swagger의 접속을 허용하는 것이 어려워서 Interceptor를 이용하셨다는 말씀이 잘 이해가 안됩니다,, :( Intercepter를 이용해도 @JwtRequired 어노테이션이 붙은 곳은 swagger가 접근할 수 없지 않나요,,?

BYEONGRYEOL commented 10 months ago

이제 이해를 좀 더 한것 같아서 답변을 좀 늦게 드려요ㅠㅠ Interceptor나 Filter나 결국엔 Spring에 등록하는 과정에서, Filter나 Interceptor나 결국에는 아래 코드처럼 특정 Path에 대해서는 Interceptor가 HttpReqeust를 가져가지않고, Filter가 작업을 수행하지 않는 다는 것을 명시해야하는 것은 마찬가진데,

저의 경우는 이를 이해하지 못한채

Filter에서 Swagger 허용 시도 -> 실패 구글링 -> Interceptor를 사용하세요! Interceptor 에서 Swagger 허용 시도 -> 성공 의 과정을 거쳐서 잘못 이해하고 답변드린 것 같습니다.

아래 코드 보시면 inteceptorRegistry에 Interceptor 추가하는 과정에 url exclude 과정이 Filter와 똑같이 존재합니다.

@RequiredArgsConstructor
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private final JwtUtil jwtUtil;
    @Value("${spring.local.url}")
    private static String URI;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtTokenInterceptor())
                .excludePathPatterns(URI + "/swagger-resources/**", URI + "/swagger-ui/**", URI + "/v3/api-docs", URI + "/api-docs/**")
                .excludePathPatterns("/swagger-resources/**", "/swagger-ui/**", "/v3/api-docs", "/api-docs/**")
                .excludePathPatterns("/signUp", "/signIn", "/error/**", "/reissue")
                .addPathPatterns("/**");
    }

    @Bean
    public JwtInterceptor jwtTokenInterceptor(){
        return new JwtInterceptor(jwtUtil);
    }
}