24 KiB
스프링 시큐리티에 대한 설명
스프링 시큐리티(Spring Security)는 스프링 기반 애플리케이션에서 보안 기능을 제공하는 강력하고 유연한 프레임워크입니다. 인증(Authentication)과 인가(Authorization)를 비롯한 다양한 보안 요구사항을 처리하며, 웹 애플리케이션, REST API, 마이크로서비스 등에서 널리 사용됩니다. 스프링 부트와 통합 시 자동 설정 기능을 통해 최소한의 코드로 보안을 구현할 수 있다는 점이 큰 장점입니다. 아래에서 주요 개념, 동작 방식, 설정 방법 등을 설명합니다.
1. 스프링 시큐리티의 주요 기능
- 인증(Authentication): 사용자가 누구인지 확인합니다 (예: 로그인).
- 인가(Authorization): 인증된 사용자가 어떤 작업을 수행할 수 있는지 권한을 확인합니다 (예: 관리자만 접근 가능).
- 보안 필터 체인: HTTP 요청을 가로채 보안 로직을 적용합니다.
- 세션 관리: 로그인 상태 유지 및 세션 공격 방지.
- CSRF 방어: 크로스 사이트 요청 위조 공격 방지.
- 비밀번호 암호화: 안전한 비밀번호 저장을 위한 해시 알고리즘 제공.
2. 스프링 시큐리티의 동작 원리
스프링 시큐리티는 필터 기반 아키텍처를 사용합니다. HTTP 요청이 들어오면 FilterChain을 통해 보안 필터들이 순차적으로 실행되며, 각 필터는 특정 보안 작업(인증, 인가 등)을 처리합니다.
-
핵심 컴포넌트:
SecurityFilterChain: 요청을 처리하는 필터 체인을 정의.AuthenticationManager: 인증 요청을 처리.UserDetailsService: 사용자 정보를 로드 (예: DB에서 조회).PasswordEncoder: 비밀번호 암호화 및 검증.
-
동작 흐름:
- 사용자가
/login으로 로그인 요청. UsernamePasswordAuthenticationFilter가 요청을 가로채 인증 시도.AuthenticationManager가UserDetailsService로 사용자 정보를 조회.- 비밀번호 일치 여부 확인 후 인증 객체(
Authentication) 생성. SecurityContextHolder에 인증 정보 저장.- 이후 요청에서 인가 확인 후 접근 허용/차단.
- 사용자가
3. 스프링 부트에서 기본 설정
스프링 부트에 spring-boot-starter-security 의존성을 추가하면 기본 보안 설정이 활성화됩니다.
-
의존성 추가 (
pom.xml):<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> -
기본 동작:
- 모든 요청에 대해 인증 요구.
- 기본 사용자 생성 (ID:
user, 비밀번호: 실행 시 로그에 출력). /login폼 제공.
-
application.yaml로 기본 사용자 설정:
spring: security: user: name: admin password: password123
4. 커스터마이징 예시
스프링 시큐리티를 커스터마이징하려면 SecurityFilterChain을 정의하는 설정 클래스를 작성합니다.
- 예시: 기본 인증 및 인가 설정
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() // 공개 경로 .requestMatchers("/admin/**").hasRole("ADMIN") // 관리자만 접근 .anyRequest().authenticated() // 나머지는 인증 필요 ) .formLogin(form -> form .loginPage("/login") // 커스텀 로그인 페이지 .permitAll() ) .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") ); return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password(passwordEncoder().encode("pass")) .roles("USER") .build(); UserDetails admin = User.withUsername("admin") .password(passwordEncoder().encode("admin")) .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }- 설명:
/public/**: 인증 없이 접근 가능./admin/**: "ADMIN" 역할 필요.- 그 외: 인증된 사용자만 접근.
- 메모리 내 사용자 정의 (실제로는 DB 연동 추천).
- 설명:
5. 데이터베이스 연동
실제 애플리케이션에서는 UserDetailsService를 구현해 데이터베이스에서 사용자 정보를 가져옵니다.
- 예시:
@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 { UserEntity user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); return User.withUsername(user.getUsername()) .password(user.getPassword()) .roles(user.getRole()) .build(); } }SecurityConfig에 주입:@Bean public UserDetailsService userDetailsService(CustomUserDetailsService customUserDetailsService) { return customUserDetailsService; }
6. REST API와 JWT
REST API에서는 폼 로그인 대신 JWT(JSON Web Token)를 사용해 stateless 인증을 구현할 수 있습니다.
-
구현 개요:
/login에서 사용자 인증 후 JWT 발급.- 클라이언트가
Authorization헤더에 JWT 포함. JwtAuthenticationFilter로 요청마다 토큰 검증.
-
필터 예시:
public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token = request.getHeader("Authorization"); // JWT 검증 로직 추가 chain.doFilter(request, response); } }
7. 주요 어노테이션
@EnableWebSecurity: 시큐리티 설정 활성화.@PreAuthorize,@PostAuthorize: 메서드 수준에서 인가 확인.- 예:
@PreAuthorize("hasRole('ADMIN')")
- 예:
@Secured: 역할 기반 접근 제어.
8. 모범 사례
- 비밀번호 암호화:
BCryptPasswordEncoder사용. - CSRF 비활성화: REST API에서는
http.csrf().disable()설정. - HTTPS: 프로덕션에서 보안 통신 강제.
- 에러 처리: 인증/인가 실패 시 커스텀 응답 제공.
결론
스프링 시큐리티는 강력한 보안 기능을 제공하며, 스프링 부트와의 통합으로 설정이 간편합니다. 기본 설정으로 빠르게 시작할 수 있고, 커스터마이징을 통해 복잡한 요구사항도 충족할 수 있습니다. 웹 애플리케이션부터 REST API, OAuth2 통합까지 다양한 시나리오를 지원하므로, 보안이 중요한 애플리케이션에서 필수적인 도구입니다. 추가 질문이 있다면 언제든 물어보세요!
스프링 시큐리티와 관련된 주요 어노테이션과 클래스들을 표로 정리하겠습니다. 스프링 시큐리티는 인증(Authentication)과 인가(Authorization)를 관리하기 위한 다양한 어노테이션과 클래스를 제공하며, 이를 통해 보안 설정을 코드 수준에서 유연하게 적용할 수 있습니다.
1. 스프링 시큐리티 관련 어노테이션
| 어노테이션 | 설명 | 사용 예시 |
|---|---|---|
@EnableWebSecurity |
스프링 시큐리티를 활성화하고, 웹 보안 설정을 커스터마이징할 수 있게 함 | @EnableWebSecurity public class SecurityConfig |
@EnableGlobalMethodSecurity |
메서드 수준 보안을 활성화 (예: @PreAuthorize, @Secured 등 사용 가능) |
@EnableGlobalMethodSecurity(prePostEnabled = true) |
@PreAuthorize |
메서드 실행 전에 권한을 확인 (SpEL 지원) | @PreAuthorize("hasRole('ADMIN')") |
@PostAuthorize |
메서드 실행 후 반환값을 기반으로 권한 확인 | @PostAuthorize("returnObject.owner == authentication.name") |
@Secured |
특정 역할(Role)이 있는 사용자만 메서드 실행 허용 | @Secured("ROLE_ADMIN") |
@RolesAllowed |
JSR-250 표준 어노테이션으로 @Secured와 유사한 역할 기반 인가 |
@RolesAllowed("ROLE_USER") |
@AuthenticationPrincipal |
현재 인증된 사용자의 Principal 객체를 메서드 파라미터로 주입 |
@GetMapping("/me") public String getUser(@AuthenticationPrincipal UserDetails user) |
@EnableMethodSecurity |
메서드 보안을 개별적으로 활성화 (Spring 6.1 이상에서 @EnableGlobalMethodSecurity 대체) |
@EnableMethodSecurity |
- 참고:
@PreAuthorize와@PostAuthorize는 Spring Expression Language(SpEL)를 지원해 복잡한 조건을 정의할 수 있습니다.@Secured와@RolesAllowed는 단순 역할 체크에 적합하지만, SpEL은 지원하지 않습니다.
2. 스프링 시큐리티 관련 주요 클래스
| 클래스 | 설명 | 주요 사용처 |
|---|---|---|
SecurityFilterChain |
HTTP 요청을 처리하는 보안 필터 체인을 정의 | HttpSecurity 설정에서 반환 |
HttpSecurity |
HTTP 보안 설정을 구성 (인증, 인가, CSRF, 로그인 등) | SecurityFilterChain 구성 |
Authentication |
인증된 사용자의 정보를 나타내는 인터페이스 | SecurityContext에서 가져옴 |
AuthenticationManager |
인증 요청을 처리하고 Authentication 객체를 반환 |
사용자 인증 로직 |
UserDetails |
사용자 정보를 나타내는 인터페이스 (ID, 비밀번호, 권한 등) | UserDetailsService에서 반환 |
UserDetailsService |
사용자 정보를 로드하는 인터페이스 (DB 연동 시 구현 필요) | 커스텀 사용자 조회 로직 |
PasswordEncoder |
비밀번호를 암호화하고 검증하는 인터페이스 | BCryptPasswordEncoder 구현체 사용 |
SecurityContext |
현재 인증 상태를 저장하는 컨텍스트 | SecurityContextHolder에서 관리 |
SecurityContextHolder |
현재 스레드의 보안 컨텍스트를 관리 (인증 정보 접근) | Authentication 객체 가져오기 |
UsernamePasswordAuthenticationToken |
사용자 이름과 비밀번호 기반 인증 토큰 | 로그인 인증 시 생성 |
AccessDeniedException |
권한 부족으로 접근이 거부될 때 발생하는 예외 | 인가 실패 처리 |
AuthenticationEntryPoint |
인증 실패 시 응답을 처리하는 인터페이스 (예: 401 반환) | 커스텀 로그인 실패 처리 |
OncePerRequestFilter |
요청당 한 번만 실행되는 필터를 구현하기 위한 추상 클래스 | JWT 인증 필터 구현 |
InMemoryUserDetailsManager |
메모리 내에서 사용자 정보를 관리하는 UserDetailsService 구현체 |
테스트용 사용자 설정 |
WebSecurityCustomizer |
웹 보안 설정을 커스터마이징 (특정 경로 무시 등) | 정적 리소스 보안 제외 |
추가 설명
어노테이션 사용 팁
@EnableWebSecurity: 필수 설정으로,SecurityFilterChain빈을 정의할 때 사용됩니다.@PreAuthorizevs@Secured:@PreAuthorize는 조건이 복잡할 때,@Secured는 단순 역할 체크에 적합합니다.@AuthenticationPrincipal: 컨트롤러에서 현재 로그인한 사용자를 쉽게 가져올 수 있어 편리합니다.
클래스 활용 예시
-
HttpSecurity 설정:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .formLogin(); return http.build(); } -
UserDetailsService 구현:
@Service public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) { // DB에서 사용자 조회 로직 return new User(username, "{bcrypt}hashedPassword", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))); } } -
PasswordEncoder 설정:
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
결론
스프링 시큐리티의 어노테이션은 설정과 메서드 수준 보안을 간편하게 적용할 수 있게 해주며, 관련 클래스들은 인증/인가 로직을 세밀하게 제어할 수 있도록 돕습니다. 이를 조합하면 웹 애플리케이션부터 REST API까지 다양한 보안 시나리오를 구현할 수 있습니다. 추가적인 질문이나 구체적인 예제가 필요하면 말씀해주세요!
HttpSecurity 클래스의 메서드 정리와 예시 설명
스프링 시큐리티에서 HttpSecurity 클래스는 HTTP 요청에 대한 보안 설정을 구성하는 핵심 클래스입니다. SecurityFilterChain을 생성할 때 사용되며, 인증(Authentication), 인가(Authorization), 로그인, 로그아웃, CSRF 보호 등 다양한 보안 기능을 메서드 체이닝 방식으로 설정할 수 있습니다. 아래에서 주요 메서드를 표로 정리하고, 예시와 함께 설명하겠습니다.
1. HttpSecurity 주요 메서드 표
| 메서드 | 설명 |
|---|---|
authorizeHttpRequests() |
HTTP 요청에 대한 인가 규칙을 정의 (경로별 접근 제어 설정) |
formLogin() |
폼 기반 로그인 설정 (로그인 페이지, 성공/실패 처리 등) |
httpBasic() |
HTTP 기본 인증 설정 (클라이언트가 사용자 이름과 비밀번호를 헤더로 전송) |
logout() |
로그아웃 설정 (로그아웃 URL, 성공 후 리다이렉션 등) |
csrf() |
CSRF(Cross-Site Request Forgery) 보호 설정 (활성화/비활성화) |
cors() |
CORS(Cross-Origin Resource Sharing) 설정 |
sessionManagement() |
세션 관리 설정 (세션 생성 정책, 최대 세션 수 등) |
exceptionHandling() |
인증/인가 예외 처리 설정 (접근 거부, 인증 실패 시 동작) |
headers() |
HTTP 헤더 보안 설정 (X-Frame-Options, XSS 보호 등) |
requestMatchers() |
특정 요청 경로에만 보안 설정 적용 |
anonymous() |
익명 사용자에 대한 설정 (익명 사용자의 권한 정의) |
oauth2Login() |
OAuth2 기반 로그인 설정 (소셜 로그인 등) |
addFilter() |
커스텀 필터를 필터 체인에 추가 |
addFilterBefore() |
기존 필터 앞에 커스텀 필터 추가 |
addFilterAfter() |
기존 필터 뒤에 커스텀 필터 추가 |
authenticationProvider() |
커스텀 인증 제공자(AuthenticationProvider)를 추가 |
accessDeniedHandler() |
인가 실패(403) 시 호출되는 핸들러 설정 |
and() |
메서드 체이닝에서 이전 설정을 종료하고 새로운 설정 섹션으로 이동 |
2. HttpSecurity 설정 예시와 설명
기본 설정 예시
다음은 일반적인 웹 애플리케이션의 보안 설정 예시입니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll() // 공개 경로
.requestMatchers("/admin/**").hasRole("ADMIN") // 관리자만 접근
.anyRequest().authenticated() // 나머지 인증 필요
)
.formLogin(form -> form
.loginPage("/login") // 커스텀 로그인 페이지
.defaultSuccessUrl("/home") // 로그인 성공 시 이동
.permitAll() // 로그인 페이지는 누구나 접근 가능
)
.logout(logout -> logout
.logoutUrl("/logout") // 로그아웃 요청 URL
.logoutSuccessUrl("/login?logout") // 로그아웃 후 이동
)
.exceptionHandling(ex -> ex
.accessDeniedPage("/access-denied") // 인가 실패 시 이동 페이지
)
.csrf(csrf -> csrf
.disable() // CSRF 비활성화 (테스트용)
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password("{noop}password") // {noop}은 암호화 없이 사용
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
메서드별 설명
-
authorizeHttpRequests()- 설명: 요청 경로별로 접근 권한을 설정합니다.
requestMatchers()로 경로를 지정하고,permitAll(),hasRole(),authenticated()등으로 권한을 정의합니다. - 예시:
/public/**은 누구나 접근 가능,/admin/**은 "ROLE_ADMIN" 역할 필요.
- 설명: 요청 경로별로 접근 권한을 설정합니다.
-
formLogin()- 설명: 폼 기반 로그인을 활성화하고, 로그인 페이지와 성공/실패 동작을 커스터마이징합니다.
- 예시:
/login페이지로 이동하며, 성공 시/home으로 리다이렉션.
-
logout()- 설명: 로그아웃 요청을 처리하고, 성공 후 이동할 경로를 설정합니다.
- 예시:
/logout요청으로 로그아웃 후/login?logout으로 이동.
-
exceptionHandling()- 설명: 인증/인가 실패 시 동작을 정의합니다.
accessDeniedPage()로 403 오류 페이지를 지정할 수 있습니다. - 예시: 권한 없는 사용자가 접근 시
/access-denied로 이동.
- 설명: 인증/인가 실패 시 동작을 정의합니다.
-
csrf()- 설명: CSRF 보호를 설정합니다. REST API에서는 보통 비활성화(
disable())하지만, 웹 폼에서는 활성화 권장. - 예시:
csrf().disable()로 비활성화 (REST API 환경에서 유용).
- 설명: CSRF 보호를 설정합니다. REST API에서는 보통 비활성화(
REST API용 설정 예시 (CSRF 비활성화 및 HTTP Basic)
REST API에서는 폼 로그인 대신 HTTP Basic 인증을 사용할 수 있습니다.
@Configuration
@EnableWebSecurity
public class RestSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.httpBasic(httpBasic -> httpBasic
.realmName("My API") // Basic 인증 영역 이름
)
.csrf(csrf -> csrf.disable()) // REST API는 CSRF 비활성화
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용 안 함
);
return http.build();
}
}
httpBasic(): 클라이언트가Authorization: Basic base64(username:password)헤더로 인증.sessionManagement(): 세션 생성 정책을STATELESS로 설정해 RESTful 특성 유지.
커스텀 필터 추가 예시
JWT 기반 인증을 위해 커스텀 필터를 추가할 수 있습니다.
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
// JWT 검증 로직
chain.doFilter(request, response);
}
}
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf(csrf -> csrf.disable());
return http.build();
}
}
addFilterBefore():UsernamePasswordAuthenticationFilter앞에 JWT 필터 추가.
3. 결론
HttpSecurity의 메서드는 스프링 시큐리티에서 HTTP 요청 보안을 세밀하게 제어할 수 있게 해줍니다. authorizeHttpRequests()로 경로별 권한을 설정하고, formLogin(), httpBasic(), logout() 등으로 인증 방식을 정의하며, csrf(), sessionManagement()으로 추가 보안 옵션을 조정할 수 있습니다. 예시를 통해 웹 애플리케이션과 REST API 모두에 적용 가능한 설정을 확인할 수 있으며, 필요에 따라 커스텀 필터를 추가해 확장성도 확보할 수 있습니다. 추가 질문이 있다면 언제든 물어보세요!