Spring Security에서 JPA와 데이터베이스 연계를 통해 사용자 정보를 조회하는 방식은 UserDetailsService
를 구현하고 JPA Repository를 활용하는 방법입니다. 인메모리 방식(InMemoryUserDetailsManager
) 대신 데이터베이스에 저장된 사용자 정보를 사용하도록 설정해야 합니다.
1. 데이터베이스 테이블 준비
우선, 사용자 정보를 저장할 테이블이 필요합니다. 기본적으로 아이디, 비밀번호, 권한을 포함해야 합니다.
SQL 예제: users 테이블과 roles 테이블
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
enabled BOOLEAN NOT NULL
);
CREATE TABLE roles (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
role VARCHAR(50) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
2. 엔티티(Entity) 클래스 생성
User 엔티티
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.HashSet;
import java.util.Set;
@Entity
@Getter @Setter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private boolean enabled;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Role> roles = new HashSet<>();
}
Role 엔티티
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter @Setter
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String role;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
3. JPA Repository 생성
사용자 정보와 권한을 조회하기 위한 리포지토리를 만듭니다.
UserRepository
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
4. UserDetails 구현
JPA를 통해 조회한 User 엔티티를 UserDetails로 변환합니다.
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.stream.Collectors;
public class CustomUserDetails implements UserDetails {
private final User user;
public CustomUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles()
.stream()
.map(role -> (GrantedAuthority) () -> role.getRole())
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return user.isEnabled();
}
}
5. UserDetailsService 구현
데이터베이스에서 사용자 정보를 조회하여 UserDetails
를 반환합니다.
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.Service;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
return new CustomUserDetails(user);
}
}
6. Security Configuration
Spring Security를 설정하고 CustomUserDetailsService를 사용하도록 구성합니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
private final CustomUserDetailsService userDetailsService;
public SecurityConfig(CustomUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.permitAll()
);
return http.build();
}
}
7. 데이터베이스에 사용자 삽입
비밀번호는 BCrypt로 암호화해서 저장해야 합니다.
비밀번호 암호화 예시
public class PasswordEncoderTest {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "password123";
String encodedPassword = encoder.encode(rawPassword);
System.out.println("Encoded Password: " + encodedPassword);
}
}
8. 요약
- UserDetails와 UserDetailsService를 구현하여 JPA를 통해 데이터베이스에서 사용자 정보를 불러옵니다.
- 사용자 정보를
CustomUserDetails
객체로 변환해 Spring Security가 인증에 사용하도록 설정합니다. BCryptPasswordEncoder
를 사용해 비밀번호를 안전하게 암호화하고 저장합니다.- SecurityFilterChain을 설정하여 사용자 인증 및 권한 검사를 수행합니다.
이 방식은 인메모리 방식보다 확장성이 뛰어나며 실제 운영 환경에서 데이터베이스를 통해 사용자 정보를 관리할 수 있는 안전하고 효율적인 방법입니다.
'Springboot > Springboot Security' 카테고리의 다른 글
인트로스펙션(Introspection) (+ 불투명 토큰 ) (0) | 2024.12.18 |
---|---|
OpenID Connect (OIDC) (1) | 2024.12.18 |
JWT (JSON Web Token) (0) | 2024.11.01 |
CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유) (0) | 2024.11.01 |
CSRF(Cross-Site Request Forgery) (0) | 2024.10.31 |