본문 바로가기

Spring/Security

Spring Security 8: 데이터베이스를 이용해 인증하기(JPA) #2

JPA가 주는 강력한 기능들 때문에 우리는 ORM 기술로 jdbc가 아닌 JPA를 사용한다. 스프링 시큐리티도 이를 지원하는데 간단히 살펴보자. (앞서 스프링 시큐리티가 제공해주는 필터들에 대해 설명했으니 이를 이해했다면 구현은 어렵지 않다.)

 

JPA 엔티티 매핑

 

앞서 작성한 테이블을 Entity 객체로 만들고 매핑한다. JPA를 공부하는 시간이 아니기 때문에 객체 매핑하는 방법은 생략하고 넘어간다.

 

https://joyfulviper.tistory.com/81

 

UserService 작성

jdbc를 이용해 인증했을 때와 마찬가지로 UserDetailsService의 loadUserByName 메소드를 오버라이딩하고 이를 AuthenticationManager에 등록하면 되기 때문에 원리만 알고 있다면 간단하다.

 

 

코드

@Service
public class UserService implements UserDetailsService {

    private final UserRepository userRepository;

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

    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByLoginId(username)
                .map(user -> User.builder()
                        .username(user.getLoginId())
                        .password(user.getPassword())
                        .authorities(user.getGroup().getAuthorities())
                        .build())
                .orElseThrow(() -> new UsernameNotFoundException("User not found user for login id: " + username));
    }
}

 

위 코드 중 빌더 패턴을 쓴 User 객체가 있는데 해당 객체는 Entity 객체가 아닌 스프링 시큐리티에서 제공하는 객체이다.

 

이후 jdbc를 이용할 때의 방식과 동이하게 AuthenticationManager에 해당 UserService를 등록하면 된다.

 

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        /**
         * spring security user 추가
         */

        AuthenticationManagerBuilder auth = http.getSharedObject(AuthenticationManagerBuilder.class);
        auth.userDetailsService(userService);
    }

 

 

번외

위 UserService 코드중 빌더 패턴이 적용된 부분을 봤을때 다음의 코드가 있다. 

.password(user.getPassword())
              .authorities(user.getGroup().getAuthorities())

이는 N+1 문제라는 것을 야기하는데 조회 메소드에 연관 관계에 있는 객체를 조회하기 위해 쿼리를 한번 더 수행한다. 

만약 findAll 메소드였다면 N개의 유저 엔티티를 조회하는 쿼리 + 각 N개에 해당하는 연관관계에 있는 Entity를 조회하는 쿼리 N개가 나오게 되어 성능상 문제가 발생한다.

 

따라서 이를 Fetch Join 이라는 것을 이용해서 연관 관계에 있는 객체들을 한번에 조회할 수 있는데,

아래와 같이 코드를 작성하면 Fetch Join이 적용된다. 이외에도 @BatchSize나 직접 fetch 쿼리를 작성하는 방법도 있는데 아래의 방법이 제일 간단한 것 같다.

 

@EntityGraph(attributePaths = {"group", "group.permissions"})
Optional<User> findByLoginId(String loginId);