본문 바로가기
Web/Spring

[Spring Security] @AuthenticationPrincipal에 null값 들어오는 문제 해결 방법

by 수짱수짱 2023. 11. 16.

https://devjem.tistory.com/70

이 글 보고 참고했다


이 문제가 발생한 경우는 “회원 정보 수정” 기능에 User를 @AuthenticationPrincipal로 가져와야 하는건데 자꾸 나의 User가 null으로 받아와지지 않았다 ㅠㅠ

글을 쭉 읽으면서 security 코드를 천천히 비교하며 보는데 !

내 경우의 문제는 CustomUserDetailsService의 loadByUserName 함수가 email으로 되어 있어서 그런 것이라고 처음에 접근했다

근데 사실 유저의 바뀌지 않는 값은 email이 맞으니 해당 함수는 문제가 없었다.

그래서 loadUserByUserName의 내부 findByEmail 메소드를 findByName 메소드로 변경했다

근데 findByName으로 바꾸니까 자꾸 존재하지 않는 계정으로 오히려 login에서 터지기 시작했다..

이유는 로그인할 때 자동으로 권한을 얻기 위해 override된 loadUserByUsername을 자동으로 호출하기 때문에 당연히 findByEmail부분을 findByName으로 바꾸니까 이름으로 유저를 식별하지 못하는 것이었다 ㅇㅅㅇ → 그래서 로그인이 안 되는 것이었음 email으로 식별할 땐 가능했음

그리고 JwtTokenProvider에서도 문제가 있었다

위의 코드를 보면 맨 처음 문제가 있던 코든데 User를 새로 만들어서 반환값에 넣어 반환하기 때문에 기존의 유저를 찾지 못하는 상황이다.
(new User(claims.getSubject(), “”, authorities))

문제는 @AuthenticationPrincipal에서 User를 찾을때 CustomerUserDetailsServiceloadByUserName을 호출해서 찾는다는 것이었다.

그래서 나는 다시 CustomerUserDetailsService를 해당 메소드에 넣어야 이 문제가 해결이 되겠다고 생각했다

그래서 그냥 아무 생각없이 JwtTokenProvider에 CustomerUserDetailsService를 넣고 loadByUserName를 사용하면 되겠지 라고 생각했다

ㅋㅋ 근데 자꾸 사용자를 식별할 수 없다는 문제가 뜨는것이었다~

그래서 뭐가 문제지 하고 게속 참고글을 읽고 읽고 디버그를 찍었다

디버그를 찍는 과정에서 @AuthenticationPrincipal의 user가 자꾸 null인게 보였다 그래서 과정을 따라갔다

JwtFilter에서 호출하는 JwtTokenProvider의 getAuthentication(인증 얻기) 함수를보자

혹시나..? 하고 username을 디버깅 찍었을 때 계속 email이 아닌 name으로 사용자를 식별하고 있는 것이었다… username = “장수연”

그래서 처음에는 name도 중복이 안되니까 email이 아니라 name으로 찾아버리자~ 하고

이렇게 정의를 해줬었다.

참고로 아래와 같이 loadUserByName은 @Override가 아니라 내가 정의한 메소드다.

package com.devcourse.ReviewRanger.user.application;

import static com.devcourse.ReviewRanger.common.exception.ErrorCode.*;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import com.devcourse.ReviewRanger.common.exception.RangerException;
import com.devcourse.ReviewRanger.user.domain.UserPrincipal;
import com.devcourse.ReviewRanger.user.repository.UserRepository;

@Component
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        return userRepository.findByEmail(email)
            .map(UserPrincipal::new)
            .orElseThrow(() -> new RangerException(FAIL_USER_LOGIN));
    }

    public UserDetails loadUserByName(String name) throws UsernameNotFoundException {
        return userRepository.findByName(name)
            .map(UserPrincipal::new)
            .orElseThrow(() -> new RangerException(FAIL_USER_LOGIN));
    }
}

당연히 처음에는 문제가 없겠지

근데 그 이후가 문제였다. 사용자의 “이름”은 회원정보 수정을 통해 바뀔 수 있는 값이었다.

그래서 식별값으로서 적절하지 않아 그 다음에 변경을 하려고 할 때는 이런 오류가 났다

처음에는 장수연으로 가입하고 유저정보를 바꾸려고 하면 username이 장수연으로 식별되어 정보가 바뀔 수 있다

근데 한번 더 요청을 하면?

db에 있는 이름이 바뀌어서 auth에 있는 이름과 일치하지 않게되어 이름으로 유저를 식별할 수 없는 것이다

그러니까 무조건 바뀌지 않는 email 을 String username = claims.getSubject(); 의 결과로 받아와야 한다

어떻게 바꿀 수 있을까..? 하고 계속 보다가

UserDetails user = userDetailService.loadUserByUsername(username); 의 반환 타입이 UserDetails인걸 보고 UserDetails를 구현한 UserPrincipal에 정답이 있나..? 하고 쓱 쳐다봤다

ㅋㅋ 찾았다 똑같은 Username… 이거를 user.getEmail로 바꿔주었더니

바로 email로 식별이 가능해졌다. claims.getSubject의 반환값이 유저 email로 잘 넘어왔다 ㅎㅎㅎ

그래서 내가 정의한 바로 밑에 줄의 loadUserByName 메소드도 지우고 loadUserByUsername 변경했다

(어차피 안에 findByEmail으로 user를 찾기 때문에 email 넘겨주면 찾을 수 있음)

결론적으로 계~속 이름과 비밀번호를 바꿔도 문제없이 변경되는 것을 확인할 수 있게 되었다!

User가 아주 잘 받아와지는 모습을 확인할 수 있다!!! 만세 ㅠㅠ