### 스프링 시큐리티에 대한 설명 스프링 시큐리티(Spring Security)는 스프링 기반 애플리케이션에서 보안 기능을 제공하는 강력하고 유연한 프레임워크입니다. 인증(Authentication)과 인가(Authorization)를 비롯한 다양한 보안 요구사항을 처리하며, 웹 애플리케이션, REST API, 마이크로서비스 등에서 널리 사용됩니다. 스프링 부트와 통합 시 자동 설정 기능을 통해 최소한의 코드로 보안을 구현할 수 있다는 점이 큰 장점입니다. 아래에서 주요 개념, 동작 방식, 설정 방법 등을 설명합니다. --- #### 1. 스프링 시큐리티의 주요 기능 - **인증(Authentication)**: 사용자가 누구인지 확인합니다 (예: 로그인). - **인가(Authorization)**: 인증된 사용자가 어떤 작업을 수행할 수 있는지 권한을 확인합니다 (예: 관리자만 접근 가능). - **보안 필터 체인**: HTTP 요청을 가로채 보안 로직을 적용합니다. - **세션 관리**: 로그인 상태 유지 및 세션 공격 방지. - **CSRF 방어**: 크로스 사이트 요청 위조 공격 방지. - **비밀번호 암호화**: 안전한 비밀번호 저장을 위한 해시 알고리즘 제공. --- #### 2. 스프링 시큐리티의 동작 원리 스프링 시큐리티는 **필터 기반 아키텍처**를 사용합니다. HTTP 요청이 들어오면 `FilterChain`을 통해 보안 필터들이 순차적으로 실행되며, 각 필터는 특정 보안 작업(인증, 인가 등)을 처리합니다. - **핵심 컴포넌트**: - `SecurityFilterChain`: 요청을 처리하는 필터 체인을 정의. - `AuthenticationManager`: 인증 요청을 처리. - `UserDetailsService`: 사용자 정보를 로드 (예: DB에서 조회). - `PasswordEncoder`: 비밀번호 암호화 및 검증. - **동작 흐름**: 1. 사용자가 `/login`으로 로그인 요청. 2. `UsernamePasswordAuthenticationFilter`가 요청을 가로채 인증 시도. 3. `AuthenticationManager`가 `UserDetailsService`로 사용자 정보를 조회. 4. 비밀번호 일치 여부 확인 후 인증 객체(`Authentication`) 생성. 5. `SecurityContextHolder`에 인증 정보 저장. 6. 이후 요청에서 인가 확인 후 접근 허용/차단. --- #### 3. 스프링 부트에서 기본 설정 스프링 부트에 `spring-boot-starter-security` 의존성을 추가하면 기본 보안 설정이 활성화됩니다. - **의존성 추가** (`pom.xml`): ```xml org.springframework.boot spring-boot-starter-security ``` - **기본 동작**: - 모든 요청에 대해 인증 요구. - 기본 사용자 생성 (ID: `user`, 비밀번호: 실행 시 로그에 출력). - `/login` 폼 제공. - **application.yaml로 기본 사용자 설정**: ```yaml spring: security: user: name: admin password: password123 ``` --- #### 4. 커스터마이징 예시 스프링 시큐리티를 커스터마이징하려면 `SecurityFilterChain`을 정의하는 설정 클래스를 작성합니다. - **예시**: 기본 인증 및 인가 설정 ```java @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`를 구현해 데이터베이스에서 사용자 정보를 가져옵니다. - **예시**: ```java @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`에 주입: ```java @Bean public UserDetailsService userDetailsService(CustomUserDetailsService customUserDetailsService) { return customUserDetailsService; } ``` --- #### 6. REST API와 JWT REST API에서는 폼 로그인 대신 JWT(JSON Web Token)를 사용해 stateless 인증을 구현할 수 있습니다. - **구현 개요**: 1. `/login`에서 사용자 인증 후 JWT 발급. 2. 클라이언트가 `Authorization` 헤더에 JWT 포함. 3. `JwtAuthenticationFilter`로 요청마다 토큰 검증. - **필터 예시**: ```java 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` 빈을 정의할 때 사용됩니다. - **`@PreAuthorize` vs `@Secured`**: `@PreAuthorize`는 조건이 복잡할 때, `@Secured`는 단순 역할 체크에 적합합니다. - **`@AuthenticationPrincipal`**: 컨트롤러에서 현재 로그인한 사용자를 쉽게 가져올 수 있어 편리합니다. #### 클래스 활용 예시 - **HttpSecurity 설정**: ```java @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .formLogin(); return http.build(); } ``` - **UserDetailsService 구현**: ```java @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 설정**: ```java @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 설정 예시와 설명 #### 기본 설정 예시 다음은 일반적인 웹 애플리케이션의 보안 설정 예시입니다. ```java @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); } } ``` #### 메서드별 설명 1. **`authorizeHttpRequests()`** - **설명**: 요청 경로별로 접근 권한을 설정합니다. `requestMatchers()`로 경로를 지정하고, `permitAll()`, `hasRole()`, `authenticated()` 등으로 권한을 정의합니다. - **예시**: `/public/**`은 누구나 접근 가능, `/admin/**`은 "ROLE_ADMIN" 역할 필요. 2. **`formLogin()`** - **설명**: 폼 기반 로그인을 활성화하고, 로그인 페이지와 성공/실패 동작을 커스터마이징합니다. - **예시**: `/login` 페이지로 이동하며, 성공 시 `/home`으로 리다이렉션. 3. **`logout()`** - **설명**: 로그아웃 요청을 처리하고, 성공 후 이동할 경로를 설정합니다. - **예시**: `/logout` 요청으로 로그아웃 후 `/login?logout`으로 이동. 4. **`exceptionHandling()`** - **설명**: 인증/인가 실패 시 동작을 정의합니다. `accessDeniedPage()`로 403 오류 페이지를 지정할 수 있습니다. - **예시**: 권한 없는 사용자가 접근 시 `/access-denied`로 이동. 5. **`csrf()`** - **설명**: CSRF 보호를 설정합니다. REST API에서는 보통 비활성화(`disable()`)하지만, 웹 폼에서는 활성화 권장. - **예시**: `csrf().disable()`로 비활성화 (REST API 환경에서 유용). --- #### REST API용 설정 예시 (CSRF 비활성화 및 HTTP Basic) REST API에서는 폼 로그인 대신 HTTP Basic 인증을 사용할 수 있습니다. ```java @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 기반 인증을 위해 커스텀 필터를 추가할 수 있습니다. ```java 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 모두에 적용 가능한 설정을 확인할 수 있으며, 필요에 따라 커스텀 필터를 추가해 확장성도 확보할 수 있습니다. 추가 질문이 있다면 언제든 물어보세요!