[Spring Boot] Spring Security+JWT 맛보기 - trouble shooting

CNH·2024년 3월 3일

개발

목록 보기
16/17

맛보기 (1)~(2)

1. redis-server.exe 실행 안됨

처음에는 그냥 redis-server.exe를 누르고 redis-cli.exe를 눌렀는데 뭔가 안 됨. 그래서 Bash창을 켜서 redis-server.exe 명령어를 통해 오류 로그를 확인하고 해결함.

https://m.blog.naver.com/PostView.nhn?blogId=ambidext&logNo=220664344209&proxyReferer=https:%2F%2Fwww.google.co.kr%2F

2. Redis 비밀번호 설정 안됨

여기처럼 Redis 비밀번호를 설정했는데

(error) ERR Client sent AUTH, but no password is set

이런 오류가 뜸. 해결 못함. 결국 Redis에 비밀번호 등록이 잘 안된 것으로 판단, Spring Boot에서도 Redis에 setPassword 부분을 주석처리함.

다음 날 노트북을 다시 켜니까 redis-cli.exe에서 auth redispw 이런 느낌으로 접속가능함.. 근데 그러고 웃긴 건 Spring Boot 프로젝트에서 Redis에 setPassword를 안해도 접속가능하고 해도 접속가능함.. 뭐지?

3. @RequiredArgsConstructor를 사용했을 때 빈을 못찾음

아래처럼 롬복 애노테이션을 사용하려고 했는데

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
    private RedisRepository redisRepository;
	//(중략)

아래와 같은 오류 발생

java.lang.NullPointerException: 
Cannot invoke "com.example.securitytest.RedisRepository.getRefreshToken(String)"
because "this.redisRepository" is null

알고보니 @RequiredArgsConstructorfinal이 붙은 필드로만 생성자 주입을 해준다고 함. 아래처럼 final을 붙여서 해결.

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {

    private final RedisRepository redisRepository;
    //(중략)

4. 컨트롤러에서 /logout을 못찾음

정말정말 황당했던 오류였다. Controller에서 url을 /logout으로 설정했는데 포스트맨으로 쏴볼때는 자꾸 /login으로 연결되어 오류가 발생하는 것이었다.

"timestamp": "2024-03-04T14:48:36.592+00:00",
"status": 405,
"error": "Method Not Allowed",
"trace": "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' is not supported\r\n\tat 
...(중략)...
"message": "Method 'GET' is not supported.",
"path": "/login"

그냥 /asdf 이런 식으로 했을 때는 잘 되는데 /logout만 그랬다. 예전에도 처음 Spring Security를 설정했을 때 기본으로 /login으로 이동되어 이 설정을 끈 적이 있었기 때문에, 이것도 혹시나 그런 현상이 아닐까 추측하고 디버그 레벨을 DEBUG로 낮춰서 요청 url을 확인했다. 아니나다를까 /logout으로 요청하면 /login?logout 이런 식으로 리다이렉트가 되고 있었다. 그냥 웹페이지에서 실행해도 /login?logout로 연결되며 whilelabel 오류가 났다.

구글링을 통해 해결법을 찾기는 했다. WebSecurityConfig.javafilterChain메소드에 아래 내용을 추가하고,

        http
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .logoutSuccessHandler(customLogoutSuccessHandler)
                );

CustomLogoutSuccessHandler를 따로 정의하는 것이다.

@Slf4j
class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
   @Override
   public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
       // 로그아웃 성공 시 원하는 동작을 여기에 구현하세요.
       log.info("logout 여기");
       response.setStatus(HttpServletResponse.SC_OK);
   }
}

그런데 결국 이렇게 해도 컨트롤러에 /logout메소드로 이동되게는 못하는 것 같았다. 따라서 이 부분은 이정도로만 넘어가고 컨트롤러에서는 /logout 대신 /userlogout을 사용하는 것으로 스스로 타협봤다..


맛보기 (3)

5. Spring Security의 getAuthorities()

이번에는 Spring Security의 getAuthorities() 메소드에 대해서 좀 더 알게 되었다. 마지막에 DB를 추가하게 되면서, 매번 signup을 시키는게 귀찮아서 그냥 DB에 추가를 했더니, Role이 없어서 SecurityContextHolder.getContext().setAuthentication(authentication); 이 부분이 제대로 시행되지 않았다. 즉 여기서 정상적으로 authentication이 담겨야 @AuthenticationPrincipal 등을 사용할 수 있다. 이후 아래처럼 코드를 수정하고 signup 메소드를 통해 정상적으로 프로세스를 진행할 수 있었다.

@Service
@RequiredArgsConstructor
@Slf4j
public class CustomUserDetailService implements UserDetailsService {

    private final UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //DB 사용하지 않았을 때 임시방편
//        log.info("loadUserByUsername : {}",username);
//        HashSet hs = new HashSet<Role>();
//        hs.add(Role.USER);
//        return new User(1L, username, "", hs);


        return userRepository.findByEmail(username)
                .orElseThrow(() -> new RuntimeException("아이디나 비밀번호가 일치하지 않습니다."));
    }
}

6. User의 roles에서의 오류

위에서 살펴보았다 시피 User 클래스에는 roles을 설정해줘야 한다. 그런데 해당 필드 관련해서 아래와 같은 오류가 났다.

org.hibernate.LazyInitializationException: 
failed to lazily initialize a collection of role: 
com.example.securitytest.User.roles: could not initialize proxy - no Session at (중략)

JPA의 영속성 관련한 오류같다. roles가 다른 테이블로 있으면서 유저 테이블과 해당 테이블이 연관관계가 되어, roles를 읽기 전에 유저 테이블을 읽는게 끝나버린 오류 같다.. 자세한건 아직 JPA를 잘 모르니 패스..

어쨋든 해결방법은 두 가지가 있는 것 같다. 호출되는 메소드에 @Transactional을 부티던지, 해당 필드에 @ElementCollection(fetch = FetchType.EAGER)를 쓰는 것이다. @Transactional이 권장되는 방법 같던데 @Transactional 썼더니 안 돼서 User 클래스의 private Set<Role> roles = new HashSet<>();@ElementCollection(fetch = FetchType.EAGER)를 붙였다..

7. JpaRepository 생성 오류

처음에 JpaRepository를 상속받는 RefreshTokenRepository를 생성했는데, 내가 알기로는 따로 메소드를 재정의하지 않아도 .save() 같은 메소드를 사용할 수 있는데, 처음에는 다른 곳에서 의존성 주입 후 .save() 등 메소드들이 호출이 안 됐었다.. 그런데 내가 기존에 재정의한 Optional<RefreshToken> findByEmail(String email);를 주석처리했더니 정상호출됐다. 근데 그러고 다시 주석을 풀어도 정상호출이 됐다.. 무슨 오류인지, 왜 해결이 됐는지 모르겠다....

profile
끄적끄적....

0개의 댓글