Spring

[시큐리티] Jwt토큰 null 예외처리

juhwan 2023. 9. 5. 19:14

프론트에서 테스트시 jwt토큰이 없으면 500에러가 나는 현상을 잡으려고 했는데 자꾸 안잡혔다 계속 찾아보다

보니 jwt는 필터단에서 걸러지는 부분이라 내가 작성했던 커스텀 에러가 못잡는 거였다.

 

그래서
throw new JwtException("토큰이 없습니다.");

 

이런 JwtException을 적용하니 에러를 잡아서 토큰Exception에 잘 넣어줬다

 

이렇게 토큰 예외처리도 했으니 커밋해야지~~ 하면서 스웨거 테스트를 했다

 

어머? 왠걸 로그인하려는데 이것도 시큐리티 필터가 막아버렸잔아?

{
  "path": "/api/users/login",
  "error": "Unauthorized",
  "message": "토큰이 없습니다.",
  "status": 401
}

아 어떻게 해야하지

일단 급하게 null 체크하는 부분을 지우고 토큰 파싱할 때 잡는 토큰 유효성을 검사해서 잡는 방법으로 바꿔놓았다. 

 

 

토큰이 없으면 null 오류가 아직 뜨긴한다

 


또 삽질하다 왔다.
팀장의 도움을 받아 디버깅을 바쁘게 하여 찾았다 ㅠㅠ
이걸 왜 생각 못 했지....

 

오류가 난 이유를 설명하자면 시큐리티에서 보내준 @AuthenticationPrincipal 어노테이션으로 보내준 유저의 값이 없어서 난 오류였다.

 @GetMapping("/users/my-info")
    @ApiOperation(value = "사용자 마이페이지 API", nickname = "사용자 마이페이지 API")
    public Response<MyInfoResponse> buyerUserMyInfo(@AuthenticationPrincipal CustomUserDetail userDetail) {
        return ApiUtils.success(HttpStatus.OK, "유저 마이페이지 응답 성공", myInfoService.getUserInfoForMyPage(findUserByUserId(userDetail)));
    }

하 난 왜 시큐리티에서 난 오류라 생각하고 시큐리티에서만 디버깅하고 있었을까
팀장이 컨트롤러 단에 중단점을 걸어준 덕분에 잡을 수 있었다.
위 코드 보면  getUserInfoForMyPage(); 매개변수로
findUserByUserId(userDetail)  이렇게 받고 있다.

  private User findUserByUserId(CustomUserDetail userDetail) {
        CustomUserDetail validUserDetail = Optional.ofNullable(userDetail).orElseThrow(() -> new CustomException(LoginErrorCode.INVALID_LOGIN));
        return userRepository.findById(validUserDetail.getUserId()).orElseThrow(() -> new CustomException(UserErrorCode.INVALID_MEMBER_ID));
    }

여기서 보면 findById로 getUserId를 찾는데 validUserDetail이 없어서 null 값이 반환되었다.

 

 

 

여기서 또 문제가 생겼다. 

findUserByUserId()를 보면 2번째 줄에 userDetail이 null인지 비교하는 코드가 있다.

나는 이렇게 알아서 하나씩 적으면 되지만 같이 협업하는 분들은 userDetail이 null일거라는 생각을 못하고 나처럼 getUserId같은 메소드를 실행하여 유저의 번호를 찾으려고 할 것이다 

나처럼 null포인트 오류가 나겠지?? 그럼 나처럼 어느 부분이 오류인지 모른채 삽질을 계속 할것이다.

 

이제 나는 @AuthenticationPrincipal CustomUserDetail userDetail 값에 null 여부를 미리 체크하는 코드를 만들어

꼭필요한 중복코드를 줄이러 떠나려 한다.

 

 

컨트롤러단마다 시큐리티 유저의 여부를 검사하는데 자꾸 null이 나와야할 것이 anonymousUser으로 나오는거다

 if (authentication == null) {
            throw new CustomException(TokenErrorCode.ACCESS_DENIED);
        }

이렇게 예외처리를 하려했지만

authentication이라는 객체에는  anonymousUser을 포함한 익명유저의 객체가 들어가있는 것 같다
그래서 예외에걸리지 않고 

 

토큰을 불러오기위해 시큐리티에 스레드로 데이터 요청하는거 실패 

 

 

하루가 지나고~~

@Getter
@NoArgsConstructor
@Configuration
public class JwtToken {
    private static UserRepository userRepository;
    @Autowired
    public JwtToken(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    public static User user() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication != null && authentication.isAuthenticated()) {
            Object principal = authentication.getPrincipal();
            // 게스트 권한이면 예외처리
            if (principal == "anonymousUser") throw new CustomException(TokenErrorCode.ACCESS_DENIED);

            // 토큰데이터 확인 유저객체 return
            if (principal instanceof UserDetails) {
                CustomUserDetail userDetails = (CustomUserDetail) principal;
                return userRepository.findById(userDetails.getUserId()).orElseThrow(() -> new CustomException(TokenErrorCode.ACCESS_DENIED));
            }
        }
        return null;
    }
}

결국 해결 했다 힝힝힝

 

상단 @Configuration이 있는데

@Component랑 기능이 비슷하다고해서 적용해봤는데 디버깅시 @Component를 설정하면 2번실행되었다.

 

시큐리티 Context를 사용하여 데이터를 가져와 if문으로 토큰 여부 확인하고 게스트와 로그인 유저를 구분해서 User객체로 만들어주었다.

이제 협업하는 분들에게 JwtToken.user()를 넣으면 어디서든 유저객체를 불러올 수 있다고 말해줘야겠다.

 

시큐리티에 anonymousUser

.anonymous().disable() // "anonymousUser" 비활성화

 

 

 

 

 

나중에 권한 URL별 권한 설정할 때 사용할 거

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Spring' 카테고리의 다른 글

[시큐리티]Spring Security 주요 기능  (0) 2023.09.20
스프링 시큐리티  (0) 2023.08.31
[Spring] @ModelAttribute("regions")  (0) 2023.05.22
[Spring] PRG Post/Redirect/Get  (0) 2023.05.16
[Spring] @PathVariable  (0) 2023.05.08