Today I Learnd📌
현재 로그인 한 유저에 대한 정보를 가져오는 코드는 아래와 같다.
public String main(@CookieValue(required = false, name = "access_token")
String token, Model model) {
if (token != null) {
String userEmail = tokenProvider.getUserEmailByToken(token);
User user = userService.getUserByEmail(userEmail);
model.addAttribute("user", user);
token에서 userEmail정보를 빼온 이후, userEmail 정보를 이용하여 해당하는 유저의 정보를 넘겨받는 형식인데, 우리 프로젝트의 특성상 유저의 정보가 어디서든 필요하고 + 프로젝트의 볼륨이 작지 않다보니 이 대략 5줄의 코드가 모든 컨트롤러에 존재하는 게 상당히 보기 좋지 않은 요인이라고 생각했고 모듈화 시킬 수 있는 방법을 고민했다.
튜터님의 도움을 받아 userArgumentResolver
라는 개념에 대해 알게되었다. 컨트롤러 메서드에서 특정 조건에 맞는 파라미터가 있을 때 원하는 값을 바인딩해주는 인터페이스인 HandlerMethodArgumentResolver
를 상속받아 만드는, 로그인 한 유저를 판별하는 Resolver이다.
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return User.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!(authentication instanceof AnonymousAuthenticationToken)) {
return authentication.getPrincipal();
}
return null;
}
}
return User.class.isAssignableFrom(methodParameter.getParameterType()); 에서 User는 SpringSecurity에 principal로 등록한 객체를 의미한다.
원래 코드에서는 Java에서 제공하는 User라이브러리를 사용해 유저아이디와 비밀번호, 권한만 넘겨서 인증받는 객체를 principal에 등록했었는데, 로그인 한 유저의 정보 전체를 받아오고 싶었기 때문에 코드를 수정해주었다.
if (authToken.validate()) {
Claims claims = authToken.getTokenClaims();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(new String[]{claims.get(AUTHORITIES_KEY).toString()})
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
log.debug("claims subject := [{}]", claims.getSubject());
//수정전
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, authToken, authorities);
//수정후
User principal = userRepository.findByUserEmail(claims.getSubject()).orElseThrow();
return new UsernamePasswordAuthenticationToken(principal, authToken, authorities);
(+)넘겨준 객체는 어디까지는 원하는 정보를 어디까지 담을지를 스스로 지정하는 것도 충분히 가능.
모든 코드에서 user객체 자체를 필요로 하는 경우가 많아서 user객체를 전부 넘기도록 세팅했지만, 초반에 로그인 한 유저의 정보를 받아, 로그인하면 닉네임을 표시할 수 있는 정도만 고려하고 코드를 짰을 땐, userPrincipalForResolver라는 원하는 User정보만 받을 클래스를 따로 생성하기도 했었다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class UserPrincipalForResolver {
private String userEmail;
private String userNickname;
private RoleType authorities;
}
//자체 생성한 클래스에 원하는 정보를 토큰에서 빼와 담는 식으로 구성
if (authToken.validate()) {
Claims claims = authToken.getTokenClaims();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(new String[]{claims.get(AUTHORITIES_KEY).toString()})
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
String userNickname = claims.get(NICKNAME_KEY, String.class);
RoleType roleType = RoleType.of(claims.get(AUTHORITIES_KEY).toString());
User principal = userRepository.findByUserEmail(claims.getSubject()).orElseThrow();
log.debug("claims subject := [{}]", claims.getSubject());
return new UsernamePasswordAuthenticationToken(principal, authToken, authorities);
(+)resolver를 사용하기 위해선 webConfig로 resolver를 꼭 등록해줘야한다.
처음에 등록하지 않고 무턱대고 사용했어서 계속 null값만 리턴받는 오류를 마주했었다. webConfig 클래스를 만들어 꼭 등록해주도록하자
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final UserArgumentResolver userArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(userArgumentResolver);
}
}
너무너무 잘하고있어 이 복잡한 코드랑 열심히 싸우는거 나한테 하라하면 못해 ㅋㅋㅋ 너니까 하는거야 우리팀 정신적 지주 힘내라우