Spring Security는 Spring 기반 애플리케이션의 인증과 권한 부여를 담당하는 강력한 보안 프레임워크입니다. Spring Framework와의 완벽한 통합, 세밀한 접근 제어, 웹 환경에 최적화된 설계가 주요 특징입니다. 최신 버전에서는 모듈이 분리되어 웹에 종속되지 않고도 핵심 기능을 사용할 수 있습니다.
1. 기본 의존성 및 설정
Spring Boot 프로젝트에 Security를 추가하려면 다음 의존성을 pom.xml에 포함시킵니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
기본적인 보안 설정을 위한 구성 클래스는 다음과 같습니다.
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
2. 권한 관리 핵심 개념
- 주체(Principal): 시스템을 사용하는 사용자, 장치, 또는 원격 시스템입니다. 즉, 시스템을 사용하는 존재가 주체입니다.
- 인증(Authentication): 주체의 신원을 확인하여 시스템 접근을 허용하는 과정입니다. 전통적인 로그인 절차가 여기에 해당합니다.
- 권한 부여(Authorization): 특정 기능을 사용할 수 있는 권한을 주체에게 부여하는 과정입니다. 즉, 사용자에게 권한을 할당하는 것입니다.
3. Spring Security 동작 원리
Spring Security는 여러 필터가 체인 형태로 연결되어 동작합니다. 주요 필터는 다음과 같습니다.
SecurityContextPersistenceFilterUsernamePasswordAuthenticationFilterFilterSecurityInterceptorExceptionTranslationFilter
FilterSecurityInterceptor는 체인의 마지막에 위치하며, 메서드 수준의 접근 권한을 검사합니다. ExceptionTranslationFilter는 인증/권한 과정에서 발생한 예외를 처리합니다. UsernamePasswordAuthenticationFilter는 /login POST 요청을 가로채 사용자 이름과 비밀번호를 검증합니다.
4. UserDetailsService 인터페이스
기본적으로 Spring Security는 메모리 상의 계정 정보를 사용하지만, 실제 프로젝트에서는 데이터베이스에서 사용자 정보를 조회해야 합니다. 이를 위해 UserDetailsService 인터페이스를 구현합니다.
UserDetails 객체는 다음 정보를 포함합니다.
getAuthorities(): 사용자의 모든 권한 반환getPassword(): 비밀번호 반환getUsername(): 사용자 이름 반환isAccountNonExpired(): 계정 만료 여부isAccountNonLocked(): 계정 잠금 여부isCredentialsNonExpired(): 자격 증명(비밀번호) 만료 여부isEnabled(): 계정 활성화 여부
5. PasswordEncoder 인터페이스
PasswordEncoder는 비밀번호 암호화 및 검증을 담당합니다.
encode(CharSequence rawPassword): 원본 비밀번호를 인코딩matches(CharSequence rawPassword, String encodedPassword): 원본 비밀번호와 저장된 인코딩 비밀번호 비교upgradeEncoding(String encodedPassword): 인코딩된 비밀번호의 보안 강도 향상 가능 여부 확인
BCryptPasswordEncoder가 Spring Security에서 권장하는 구현체로, bcrypt 해시 알고리즘을 사용하여 단방향 암호화를 수행합니다. strength 속성으로 암호화 강도를 조절할 수 있으며, 기본값은 10입니다.
@Test
public void testPasswordEncoder() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encoded = encoder.encode("myPassword");
System.out.println("Encoded password: " + encoded);
boolean match = encoder.matches("myPassword", encoded);
System.out.println("Match result: " + match);
}
6. Spring Boot 자동 설정
application.properties 파일에서 직접 사용자 정보를 설정할 수 있습니다.
spring.security.user.name=admin
spring.security.user.password=admin123
또는 UserDetailsService를 구현하여 커스텀 로직을 적용할 수 있습니다.
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (!"admin".equals(username)) {
throw new UsernameNotFoundException("User not found");
}
String encodedPassword = "$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na";
return new User(username, encodedPassword,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
7. 고급 설정: WebSecurityConfigurerAdapter 활용
@Configuration
public class AdvancedSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Service
public class DatabaseUserDetailsService implements UserDetailsService {
private static final Map<String, String> userStore = new HashMap<>();
static {
userStore.put("user1", "pass1");
userStore.put("manager", "mgrPass");
}
@Override
public UserDetails loadUserByUsername(String username) {
String key = "user1";
String password = userStore.get(key);
List<GrantedAuthority> authorities =
AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN");
return new User(key, new BCryptPasswordEncoder().encode(password), authorities);
}
}