Step 4. Custom Annotation 을 통해 로그인 정보 가져오기
1. Custom Annotation 구현
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface LoginMember {
boolean required() default true;
}
annotation 은 기본 값을 true 로 두고 필요한 api 에서만 사용합니다.
@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
private static final String AUTHORIZATION_HEADER = "authorization";
private final JwtAuthTokenProvider jwtAuthTokenProvider;
private final MemberRepository memberRepository;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(LoginMember.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
LoginMember loginUserAnnotation = parameter.getParameterAnnotation(LoginMember.class);
if (!loginUserAnnotation.required()) {
return null;
}
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
return Optional.ofNullable(request.getHeader(AUTHORIZATION_HEADER))
.map(authorization -> authorization.split("bearer")[1])
.map(jwtAuthTokenProvider::convertAuthToken)
.map(JwtAuthToken::getEmail)
.map(email -> memberRepository.findByEmail(email)
.orElseThrow(() -> new CustomAuthrizationException(ErrorCode.NOT_EXIST_MEMBER)))
.orElseThrow(() -> new CustomAuthrizationException(ErrorCode.FORBIDDEN));
}
}
resolver 는 jwt 를 해석해서 이메일 정보를 가져오고, 회원이 존재하는지를 검증합니다.
2. Resolver 설정
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final LoginMemberArgumentResolver loginMemberArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginMemberArgumentResolver);
}
}
3. JWT 에서 로그인 정보 가져오기
@RestController
public class LoginController {
@GetMapping("/api/v1/my")
public void my(@LoginMember Member loginMember) {
log.info("### loginMember.getEmail()={}, loginMember.getRole()={}", loginMember.getEmail(), loginMember.getRole());
}
}
마치며
구현을 하면서 배운 것도 많았지만, 반대로 미숙한 부분도 많았습니다.
- filter 레이어에서의 에러 처리
- 인증/인가 예외 처리를 제대로 하고 있는가?
- AuthTokenProvider 에 Authentication 을 가져오는 메소드가 있는게 적절한가?
- UsernamePasswordAuthenticationToken 외를 이용해서 인증을 하려면 어떻게 해야할까?
- SecurityContextHolder 를 더 잘 이용할 수 있는 방법은 무엇일까?
특히 예제에서는 회원 정보를 가져올 때 SecurityContextHolder 를 통한 것이 아닌 별도의 Resolver 를 만들었습니다.
조금 구현해본 바로는 Authentication - User - principal 와 Adapter 를 이해해야되는데,
이 과정에서 Member entity 에 extends 를 해줘야하는 등 종속성이 생기는 것 같아 이상한 느낌이 들었습니다.
아마 공부가 부족해서 잘못 이해했거나 best practice 를 찾지 못한 것이라고 생각합니다.
하지만 security 는 워낙 양이 방대하고 사이드 프로젝트를 하는데는 이 정도의 구현으로 충분하기에
고도화는 좀 더 공부하고 해볼 생각입니다.
금방 할 수 있기를..
'Series > 내가 해본' 카테고리의 다른 글
표준 예외 처리에서 로깅까지 (2) (0) | 2021.07.03 |
---|---|
표준 예외 처리에서 로깅까지 (1) (0) | 2021.07.02 |
Spring Security 로 회원 가입을 구현해보자 (2) (0) | 2021.06.19 |
Spring Security 로 회원 가입을 구현해보자 (1) (0) | 2021.06.19 |
enum 을 조회하는 방법 (0) | 2021.01.13 |