@Auth @IpAddress동작 방식

허준기·2024년 3월 9일
2

BCSD

목록 보기
2/7

이번에 동아리에서 진행하는 프로젝트인 코인Spring 3에서 Springboot로 마이그레이션하기로 했다.

레귤러로 전환이 된지 얼마 되지 않은 나는 마이그레이션을 진행하기에 앞서 어떤방식으로 동작하는지에 대해 기존 레귤러들에게 온보딩을 받게 되었다.

온보딩을 진행하면서 몇가지 조사 과제를 받아서 어떤 방식으로 동작하는지 한 번 알아봐야겠다.

@Auth

코인에서는 @Auth를 통해 인증?절차를 거친다

자바에서 어노테이션은 주석이다
실제로 뜻이 주석이다

그런데 어떻게 인증을 하는걸까?

@Override
    public ResponseEntity<OwnerResponse> getOwner(
        @Auth(permit = {OWNER}) Long ownerId
    ) {
        OwnerResponse ownerInfo = ownerService.getOwner(ownerId);
        return ResponseEntity.ok().body(ownerInfo);
    }

이렇게 @Auth를 달아놓으면 정해놓은 로직을 거치게 된다.

@Auth 인터페이스는 별 내용이 없다

@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Auth {

    UserType[] permit() default {};

    boolean anonymous() default false;
}

이렇게 되어 있는데 @Target은 어노테이션이 어느곳에 선언될지 지정해주는 것인데 여기서에서는 PARAMETER에만 적용되도록 설정해주었다.

그리고 @Retention은 어노테이션의 지속 시간을 정해주는데 여기서는 RUNTIME까지만 지속되도록 설정한 것을 알 수있다.

이제 그 과정을 한 번 알아보자

Argument Resolver

그 과정에서는 Argument Resolver가 사용된다.

HandlerMethodArgumentResolver라는 인터페이스가 있는데 이를 상속받아 구현해준다

코인에 있는 AuthArgumentResolver 클래스를 가져와서 보자

public class AuthArgumentResolver implements HandlerMethodArgumentResolver {

    private final UserRepository userRepository;
    private final AuthContext authContext;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(Auth.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

        Auth authAt = parameter.getParameterAnnotation(Auth.class);
        requireNonNull(authAt);
        List<UserType> permitStatus = Arrays.asList(authAt.permit());
        Long userId = authContext.getUserId();
        if (isAnonymous(userId, authAt)) {
            return null;
        }
        User user = userRepository.getById(userId);
        if (permitStatus.contains(user.getUserType())) {
            return user.getId();
        }
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        throw AuthException.withDetail("header: " + request);
    }

    private static boolean isAnonymous(Long userId, Auth authAt) {
        if (userId == null) {
            if (authAt.anonymous()) {
                return true;
            }
            throw AuthException.withDetail("userId is null");
        }
        return false;
    }
}

이렇게 되어 있는데 하나씩 뜯어가며 살펴보자

@Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(Auth.class);
    }

우선 supportsParameter라는 메서드를 통해서 파라미터가 @Auth라는 어노테이션을 가지고 있는지 확인해준다.

@Auth 어노테이션을 가지고 있는게 확인이 된다면 resolveArgument 라는 메서드를 실행시키게 된다.

@Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

        Auth authAt = parameter.getParameterAnnotation(Auth.class);
        requireNonNull(authAt);
        List<UserType> permitStatus = Arrays.asList(authAt.permit());
        Long userId = authContext.getUserId();
        if (isAnonymous(userId, authAt)) {
            return null;
        }
        User user = userRepository.getById(userId);
        if (permitStatus.contains(user.getUserType())) {
            return user.getId();
        }
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        throw AuthException.withDetail("header: " + request);
    }

@Auth 어노테이션에서는 보통 사용자의 id가 대상인데 순서대로 살펴보자

Auth authAt = parameter.getParameterAnnotation(Auth.class);
        requireNonNull(authAt);

이 부분에서는 어노테이션에 달려있는 파라미터가 null인지 확인하고 해당 id를 가져와서 익명인지 확인한다.

그 후에는 해당하는 idUser 객체로 만들어서 해당 아이디를 반환해주는 것을 볼 수 있다.

@IpAddress

@IpAddressip를 가져오는 어노테이션도 존재한다.

@Hidden // Swagger 문서에 표시하지 않음
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface IpAddress {

}

이 어노테이션도 인터페이스에는 아무것도 없다

public class IpAddressArgumentResolver implements HandlerMethodArgumentResolver {

    private final NetworkContext networkContext;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(IpAddress.class) && parameter.getParameterType().equals(String.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

        return networkContext.getIpAddress();
    }
}

Argument Resolver를 봐도 그냥 ip만 가져오는 것을 볼 수 있다
@Auth의 동작방식에서 로직을 뺀 것 같다

정리

정리를 해보자면 @Auth 어노테이션이 달려있는 파라미터를 가져와서 null 검사를 거쳐서 (일종의 인증 로직) 다시 id를 반환해주는 것을 볼 수 있다
@IpAddress는 어노테이션이 달려있는 파라미터의 ip를 가져오는 것으로 볼 수 있다!

profile
나는 허준기

0개의 댓글