Auditing 마무리

뚜우웅이·2025년 2월 7일

프로젝트를 시작할 때 작성 시간과, 수정 시간만 entity에 포함시켰다.
이제 로그인 기능을 구현했기 때문에 작성자와, 수정자를 추가해줄 것이다.

앞서 구현한 JwtAuthenticationFilter 부분에서 사용자의 권한 정보를 주지 않아 AnonymousAuthenticationToken으로 처리되는 문제가 있다.
그렇기 때문에 먼저 코드를 수정해준다.

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String token = resolveToken(request);

        if (token != null && tokenProvider.validateToken(token)) {
            String username = tokenProvider.getUsernameFromToken(token);

            // 🔹 UserDetailsService를 통해 사용자 정보 가져오기
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            // 🔹 권한 정보를 포함한 Authentication 객체 생성
            Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            // 인증 정보 설정
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

거기에 맞게 WebSecurityConfig도 수정해준다.

private final UserDetailsService userDetailsService;


                .addFilterBefore(new JwtAuthenticationFilter(tokenProvider, userDetailsService), UsernamePasswordAuthenticationFilter.class)

Auditing

baseEntity

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity extends BaseTimeEntity {

    @CreatedBy
    @Column(updatable = false)
    private Long createdBy;

    @LastModifiedBy
    private Long lastModifiedBy;
}

Post

public class Post extends BaseEntity{
}

PostResponse

@Builder
public record PostResponse(
        Long id,
        String title,
        String content,
        LocalDateTime createdAt,
        LocalDateTime lastModifiedAt,
        Long createdBy,
        Long lastModifiedBy
) {
    public static PostResponse toDto(Post post) {
        return PostResponse.builder()
                .id(post.getId())
                .title(post.getTitle())
                .content(post.getContent())
                .createdAt(post.getCreatedAt())
                .lastModifiedAt(post.getLastModifiedAt())
                .createdBy(post.getCreatedBy())
                .lastModifiedBy(post.getLastModifiedBy())
                .build();
    }
}

bean

@SpringBootApplication
@EnableJpaAuditing
public class SpringbootDeveloperApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDeveloperApplication.class, args);
    }

    @Bean
    public AuditorAware<Long> auditorProvider() {
        return new AuditorAwareImpl();
    }

    @Bean
    JPAQueryFactory jpaQueryFactory(EntityManager em) {
        return new JPAQueryFactory(em);
    }
}

Impl

@Slf4j
public class AuditorAwareImpl implements AuditorAware<Long> {

    @Override
    public Optional<Long> getCurrentAuditor() {
        // SecurityContext에서 현재 사용자의 Autehntication 객체를 가져옴
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        log.info("Authentication-> {}", authentication);
        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.empty();
        }

        // JWT에서 사용자 ID 추출
        Object principal = authentication.getPrincipal();
        log.info("principal -> {}", principal);

        if (principal instanceof CustomUserDetails) {
            return Optional.of(((CustomUserDetails) principal).getUserId());
        }

        return Optional.empty();
    }
}

Spring SecurityAuditing 기능과 연동하여, 현재 인증된 사용자의 ID를 가져오는 역할을 한다.
Spring Data JPA@CreatedBy@LastModifiedBy 어노테이션과 함께 사용되며, 자동으로 엔티티에 작성자 정보를 저장할 수 있다.

  • SecurityContextHolder.getContext().getAuthentication()를 통해 현재 로그인한 사용자의 인증 객체를 가져온다.

    • 하지만 익명 사용자인 경우 AnonymousAuthenticationToken이 반환될 수도 있다.
  • 인증 객체가 null이거나, 사용자가 인증되지 않았다면 작성자를 저장할 필요가 없으므로 Optional.empty()를 반환한다.

  • authentication.getPrincipal()을 호출하여 현재 로그인한 사용자의 정보(Principal 객체)를 가져온다.

  • principalCustomUserDetails의 인스턴스인지 확인한다.

    • CustomUserDetails는 사용자의 ID, 이메일, 권한 등의 정보를 가지고 있는 객체다.
  • getUserId()를 호출하여 현재 로그인한 사용자의 ID를 가져와 반환한다.

  • principalCustomUserDetails가 아니라면, 익명 사용자 또는 예상치 못한 타입일 가능성이 있다

    • 따라서 Optional.empty()를 반환하여 작성자 정보를 저장하지 않도록 설정한다.

CustomUserDetails

@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {

    private final Long userId;
    private final String username;
    private final String password;
    private final Collection<? extends GrantedAuthority> authorities;


    public Long getUserId() {
        return userId;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

Spring SecurityUserDetails 인터페이스를 구현한 CustomUserDetails 클래스다.
UserDetails 인터페이스는 Spring Security에서 사용자 인증 및 권한 관리를 위한 핵심 인터페이스로, 사용자 정보를 제공하는 데 사용된다.

  • userId: 사용자의 고유 ID (예: 데이터베이스에서 사용자를 구분하는 ID)
  • username: 사용자 이름 (로그인에 사용되는 값)
  • password: 사용자의 암호 (로그인 인증에 사용)
  • authorities: 사용자의 권한을 담은 컬렉션 (권한을 GrantedAuthority 객체로 표현)

UserDetailService 수정

@Service
@RequiredArgsConstructor
public class UserDetailService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) {
        User user = userRepository.findByEmail(email).orElseThrow(NotFoundUserException::new);
        return new CustomUserDetails(
                user.getId(),
                user.getEmail(),
                user.getPassword(),
                user.getAuthorities()
        );
    }
}
  • 이 클래스는 Spring Security에서 인증을 처리하는 UserDetailsService를 구현하여, 이메일을 기반으로 사용자를 조회하고, 조회된 사용자 정보를 CustomUserDetails 객체로 반환한다.

  • loadUserByUsername() 메서드는 Spring Security의 인증 과정에서 호출되며, 사용자의 ID, 이메일, 비밀번호, 권한 등을 포함한 사용자 정보를 반환한다.

  • 사용자 인증을 위해 CustomUserDetails 객체를 생성하고, 이를 통해 Spring Security에서 인증과 권한 관리를 처리한다.

profile
공부하는 초보 개발자

0개의 댓글