본문 바로가기
Spring/09. spring-security

Spring Security의 보안 구성 흐름 / 설정 설명

by 989898 2025. 3. 5.

🔍 1. Spring Security의 보안 구성 흐름

Spring Security에서 보안 구성을 설정하는 주요 과정은 다음과 같습니다.

📌 (1) HttpSecurity를 활용한 보안 설정

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/", "/login").permitAll()
            .requestMatchers("/admin").hasRole("ADMIN")
            .anyRequest().authenticated()
        )
        .formLogin(auth -> auth
            .loginPage("/login")
            .loginProcessingUrl("/loginProc")
            .permitAll()
        )
        .csrf(auth -> auth.disable());

    return http.build();
}

 

✔️ HttpSecurity를 설정하면 Spring Security가 모든 요청을 가로채서 인증 및 권한 검사를 수행
✔️ 이 설정만으로도 기본적인 보안 필터 체인이 완성됨


📌 (2) Spring Security 내부에서 자동으로 처리하는 것

  • SecurityFilterChain을 반환하면 Spring Security는 자동으로
    1️⃣ 모든 요청을 보안 필터 체인을 통해 감시
    2️⃣ 로그인 요청(/loginProc)을 가로채서 인증 수행
    3️⃣ 인증된 사용자의 정보(SecurityContext)를 관리
    4️⃣ 각 URL 접근 권한(Role)을 검사하여 허용/거부 결정

✔️ 즉, HttpSecurity만 설정하면 Spring Security의 보안 기능은 기본적으로 동작함.


2. 하지만 추가로 설정이 필요한 경우도 있다!

HttpSecurity만 설정하면 보안 구성이 거의 끝나지만, 추가적인 설정이 필요한 경우가 있습니다.


🔹 (1) 사용자 정보 관리 (UserDetailsService)

HttpSecurity 설정만으로는 사용자 정보(아이디, 비밀번호)를 직접 관리하지 않음
✔️ 로그인 시, 사용자 정보를 어디서 가져올 것인지 별도로 정의해야 함.
✔️ 기본적으로 UserDetailsService를 구현하여 DB 또는 메모리에서 사용자 정보를 가져옴.

@Bean
public UserDetailsService userDetailsService() {
    UserDetails user = User.withDefaultPasswordEncoder()
        .username("admin")
        .password("1234")
        .roles("ADMIN")
        .build();
    return new InMemoryUserDetailsManager(user);
}

 

✔️ 위 설정이 없으면 로그인할 사용자 정보가 없기 때문에 로그인 자체가 불가능
✔️ 즉, HttpSecurity 설정만으로는 보안 구성이 완전하지 않고 사용자 정보 제공 설정이 추가로 필요함


🔹 (2) SecurityContext 관리 (로그인 세션, JWT 토큰 등)

기본적으로 Spring Security는 세션을 사용하여 사용자 인증 상태를 관리하지만,
JWT 같은 방식으로 세션 없이 인증을 유지하려면 추가 설정이 필요함

✔️ 기본 세션 기반 인증 방식:

http
    .sessionManagement(session -> session
        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 필요할 때만 세션 생성
    );

✔️ JWT 기반 인증 방식:

http
    .sessionManagement(session -> session
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 없이 JWT 사용
    );

✔️ HttpSecurity 설정만으로는 세션 관리 방식까지는 자동으로 설정되지 않으므로, 개발자가 직접 설정해야 함


🔹 (3) CORS & CSRF 설정

✔️ REST API 기반 애플리케이션에서는 CORS 및 CSRF 보안 설정을 직접 해줘야 함
✔️ HttpSecurity 기본 설정에는 CORS 설정이 포함되지 않으므로 개발자가 직접 추가해야 함

http
    .cors(Customizer.withDefaults()) // CORS 활성화
    .csrf(csrf -> csrf.disable()); // CSRF 보호 비활성화 (REST API에서는 주로 비활성화)

✔️ REST API에서는 대부분 CSRF를 비활성화해야 하지만, 웹 애플리케이션에서는 활성화가 필요할 수도 있음


3. 결론

✔️ 보안 구성을 설정하는 가장 핵심적인 부분은 HttpSecurity
✔️ 하지만, 추가로 필요한 설정 (사용자 정보 관리, SecurityContext 관리, CORS/CSRF 설정 등)은 개발자가 직접 추가해야 함

✔️ 즉, HttpSecurity만 설정한다고 모든 보안 설정이 끝나는 것은 아니며, 추가적인 설정이 필요할 수도 있다!


@Configuration  // 이 클래스가 Spring Security 설정을 위한 구성 클래스임을 나타냅니다.
@EnableWebSecurity(debug = true)  // Spring Security의 웹 보안을 활성화하고, 디버깅 정보를 출력하도록 설정합니다.
public class SecurityConfig {
    /**
     * TODO#2 - '/*' <-- 모든 요청에 대해서 인증받은 사용자만 서비스를 이용할 수 있습니다.
     *  HttpSecurity를 사용해서 보안 설정을 정의할 수 있습니다.
     *  - httpBasic 활성화 합니다. - 교재를 참고 합니다.
     *  - formLogin 활성화 합니다. - 교재를 참고 합니다.
     */
    // HttpSecurity 객체를 이용해 보안 설정을 정의하는 메서드
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // HTTP 요청에 대한 보안 설정
        http
                .authorizeHttpRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().authenticated()) // 모든 요청에 대해 인증을 요구합니다.
                .httpBasic(Customizer.withDefaults())  // HTTP Basic 인증을 사용합니다.
                .formLogin(Customizer.withDefaults()); // 폼 로그인 기능을 사용합니다.

        // 설정이 끝나면 SecurityFilterChain 객체를 반환하여 보안 구성을 완료합니다.
        return http.build();
    }

    /**
     * TODO#3 - UserDetailsService 빈을 정의하여 사용자 인증 정보를 제공합니다.
     * UserDetailsService는 사용자의 인증 정보를 가져오는 역할을 합니다.
     * Spring Security는 이 인터페이스를 통해 사용자 이름을 기반으로 사용자 정보를 조회하고,
     * 그 정보를 사용하여 인증을 수행합니다. 기본적으로 아이디, 비밀번호, 권한 등의 정보를 제공합니다.
     */

    @Bean
    public UserDetailsService userDetailsService() {
        // 사용자 정보를 정의하고, 기본 비밀번호 인코더를 사용하여 암호화합니다.
        UserDetails userDetails = User.withDefaultPasswordEncoder()  // 기본 비밀번호 인코더 사용
                .username("marco")  //TODO#3-1 사용자 이름
                .password("nhnacademy")  //TODO#3-2 비밀번호
                .roles("MEMBER")  //TODO#3-3 사용자 역할 (MEMBER)
                .build();  // UserDetails 객체 생성

        //TODO#3-4 InMemoryUserDetailsManager를 사용하여 메모리 내에서 사용자 정보를 관리합니다.
        return new InMemoryUserDetailsManager(userDetails);
    }
}

🔍 Spring Security 설정 (SecurityConfig) 설명

이 코드는 Spring Security를 사용하여 인증(Authentication)과 권한(Authorization)을 설정하는 클래스입니다.
특히 In-Memory 방식으로 사용자를 관리하며, HTTP Basic 및 Form Login을 활성화하는 기본적인 설정을 포함하고 있습니다.


1. 주요 개념

개념 설명
SecurityFilterChain 모든 요청이 통과하는 보안 필터 체인 (인증 & 권한 체크)
HttpSecurity Spring Security 설정을 정의하는 객체
UserDetailsService 사용자 정보를 조회하는 서비스
InMemoryUserDetailsManager 메모리 기반으로 사용자 정보를 관리하는 방식

2. SecurityConfig 클래스 분석

@Configuration
@EnableWebSecurity(debug = true)

✔️ @Configuration → 이 클래스가 Spring Security 설정을 담당하는 구성 클래스임을 나타냄
✔️ @EnableWebSecurity(debug = true) → Spring Security를 활성화하고, 디버깅 정보를 출력하도록 설정


3. SecurityFilterChain (보안 필터 체인) 설정

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) // 모든 요청에 대해 인증을 요구
        .httpBasic(Customizer.withDefaults())  // HTTP Basic 인증 사용
        .formLogin(Customizer.withDefaults()); // Form Login 사용

    return http.build();
}

📌 1) 모든 요청에 대해 인증 요구

.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
  • anyRequest().authenticated() → 모든 요청에 대해 인증된 사용자만 접근 가능

📌 2) HTTP Basic 인증 활성화

.httpBasic(Customizer.withDefaults())
  • HTTP Basic 인증을 활성화
  • 로그인 창 없이 브라우저 기본 팝업을 이용하여 인증 수행

📌 3) Form Login (웹 로그인 폼) 활성화

.formLogin(Customizer.withDefaults());
  • Spring Security 기본 제공 로그인 폼 활성화
  • /login 페이지가 자동으로 생성됨

4. 사용자 정보 관리 (UserDetailsService)

@Bean
public UserDetailsService userDetailsService() {
    UserDetails userDetails = User.withDefaultPasswordEncoder()
            .username("marco") // 사용자 이름
            .password("nhnacademy") // 비밀번호
            .roles("MEMBER") // 사용자 역할 (MEMBER)
            .build();

    return new InMemoryUserDetailsManager(userDetails);
}

📌 1) UserDetailsService란?

  • Spring Security에서 사용자 정보를 조회하는 서비스
  • 로그인 시 입력된 아이디(username)로 DB 또는 메모리에서 사용자 정보를 가져옴

📌 2) InMemoryUserDetailsManager (메모리 기반 사용자 관리)

return new InMemoryUserDetailsManager(userDetails);

 

✔️ 메모리 기반으로 사용자 정보를 저장
✔️ DB 없이 간단한 테스트 용도로 사용 가능
✔️ 애플리케이션이 재시작되면 데이터가 초기화됨


📌 3) UserDetails 객체 생성

UserDetails userDetails = User.withDefaultPasswordEncoder()
        .username("marco") // 사용자 이름
        .password("nhnacademy") // 비밀번호
        .roles("MEMBER") // 역할 (MEMBER)
        .build();

 

✔️ 기본적인 사용자 정보 (아이디, 비밀번호, 역할)를 설정
✔️ withDefaultPasswordEncoder() → 비밀번호를 평문(암호화 없이) 저장 (테스트 용도)
✔️ "marco" 라는 사용자가 "MEMBER" 역할을 가짐


5. Spring Security의 동작 흐름

🔹 (1) 사용자가 /admin 페이지에 접근

GET /admin

 

✔️ Spring Security의 SecurityFilterChain이 요청을 가로챔
✔️ 인증되지 않은 사용자면 /login 페이지로 자동 리다이렉트


🔹 (2) 사용자가 로그인 요청을 보냄

POST /login

 

✔️ username=marco & password=nhnacademy 입력 후 로그인 시도
✔️ UserDetailsService에서 marco 계정을 조회
✔️ 비밀번호 검증 후 인증 성공 시 SecurityContextHolder에 사용자 정보 저장


🔹 (3) 로그인 성공 후 원래 요청한 페이지로 이동

.defaultSuccessUrl("/index", false);

✔️ 기본적으로 로그인 전에 요청했던 페이지로 이동
✔️ .defaultSuccessUrl("/index", false); 설정 시 특정 페이지로 이동 가능


6. 결론

💡 Spring Security는 모든 요청을 가로채서 인증과 권한을 검사한 후, 올바른 요청이면 원래 목적지로 보내는 방식으로 동작한다!

✔️ SecurityFilterChain을 통해 모든 요청을 인증 필요하게 설정
✔️ HTTP Basic 및 Form Login을 활성화하여 로그인 기능 제공
✔️ In-Memory 방식으로 사용자 정보를 저장 (테스트 용도)
✔️ 로그인 후 사용자의 역할(Role)을 기반으로 접근을 제한할 수 있음


package com.nhnacademy.springsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SercurityConfig {

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {

        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/", "/login", "/loginProc", "/join", "/joinProc").permitAll() // 인증 없이 접근 가능
                        .requestMatchers("/admin").hasRole("ADMIN") // ADMIN 권한이 있어야 접근 가능
                        .requestMatchers("/my/**").hasAnyRole("ADMIN", "USER") // ADMIN 또는 USER 권한 필요
                        .anyRequest().authenticated() // 그 외 모든 요청은 인증 필요
                );

        /*
            .loginPage("/login")
            → 로그인하지 않은 사용자가 인증이 필요한 페이지(/admin 등)에 접근하면 자동으로 /login 페이지로 리다이렉트

            .loginProcessingUrl("/loginProc")
            → 로그인 폼이 제출되면 /loginProc 경로에서 로그인 처리를 수행
         */
        // loginPage 메서드 덕분에 이제부터 admin 페이지로 가도 오류 뜨지 않고 /login 페이지로 이동하게 된다.
        // .loginPage("/login") 설정 덕분에 404 오류 없이 /login 페이지로 이동 가능
        http
                .formLogin((auth) -> auth
                        .loginPage("/login") // 로그인 페이지 지정
                        .loginProcessingUrl("/loginProc") // 로그인 요청을 처리하는 URL
                        .permitAll()
                );

        http
                .csrf((auth) -> auth.disable());

        return http.build();
    }
}

 

1. SecurityFilterChain 설정 분석

이 설정을 통해 어떤 URL에 접근할 때 인증이 필요한지, 로그인 페이지는 어디인지 등을 지정할 수 있습니다.

🔹 (1) authorizeHttpRequests() - 접근 권한 설정

http
    .authorizeHttpRequests((auth) -> auth
        .requestMatchers("/", "/login").permitAll() // 인증 없이 접근 가능
        .requestMatchers("/admin").hasRole("ADMIN") // ADMIN 권한이 있어야 접근 가능
        .requestMatchers("/my/**").hasAnyRole("ADMIN", "USER") // ADMIN 또는 USER 권한 필요
        .anyRequest().authenticated() // 그 외 모든 요청은 인증 필요
    );
  • requestMatchers("/", "/login").permitAll()
    인증 없이 누구나 접근 가능
  • requestMatchers("/admin").hasRole("ADMIN")
    ADMIN 권한이 있어야 /admin 페이지에 접근 가능
  • requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
    ADMIN 또는 USER 권한이 있어야 /my/... 페이지에 접근 가능
  • anyRequest().authenticated()
    그 외 모든 요청은 반드시 로그인해야 접근 가능

🔹 (2) formLogin() - 로그인 페이지 설정

http
    .formLogin((auth) -> auth
        .loginPage("/login") // 로그인 페이지 지정
        .loginProcessingUrl("/loginProc") // 로그인 요청을 처리하는 URL
        .permitAll()
    );
  • .loginPage("/login")
    → 로그인하지 않은 사용자가 인증이 필요한 페이지(/admin 등)에 접근하면 자동으로 /login 페이지로 리다이렉트
  • .loginProcessingUrl("/loginProc")
    → 로그인 폼이 제출되면 /loginProc 경로에서 로그인 처리를 수행
  • .permitAll()
    → 로그인 페이지(/login)는 누구나 접근 가능

🔹 (3) csrf().disable() - CSRF 보호 비활성화

http
    .csrf((auth) -> auth.disable());
  • CSRF(Cross-Site Request Forgery) 공격 방지를 위한 보안 기능을 비활성화
  • 보통 API 서버에서 사용하지만, CSRF 보호를 유지하는 것이 보안상 더 안전함

2. loginPage()가 admin 페이지 접근 시 오류 없이 /login으로 이동하는 이유

http
    .formLogin((auth) -> auth
        .loginPage("/login") // 로그인 페이지 지정
        .loginProcessingUrl("/loginProc")
        .permitAll()
    );

🧐 🤔 /admin 페이지에 직접 접근할 때 왜 오류가 안 나고 /login으로 이동할까?

  1. 사용자가 /admin 페이지에 접근
  2. .requestMatchers("/admin").hasRole("ADMIN") 설정 때문에 ADMIN 권한이 있는 사용자만 접근 가능
  3. 사용자가 로그인하지 않았거나, ADMIN 권한이 없으면 Spring Security가 자동으로 로그인 페이지(/login)로 리다이렉트
  4. .loginPage("/login") 설정 덕분에 404 오류가 발생하지 않고, /login 페이지로 이동

🔥 즉, loginPage("/login")을 설정하지 않으면 기본 제공되는 로그인 페이지로 리다이렉트되지만, 설정하면 커스텀 페이지(/login)로 이동하도록 변경 가능!


3. 흐름 정리

📌 📍 사용자가 /admin 페이지에 접근하는 경우

1️⃣ 로그인 여부 확인

  • 로그인하지 않은 경우 → 자동으로 /login 페이지로 리다이렉트
  • 로그인한 경우 → 2️⃣번 과정 진행

2️⃣ 권한 확인 (hasRole("ADMIN"))

  • ADMIN 권한이 있으면 /admin 페이지 접근 가능
  • 없으면 403 Forbidden 오류 발생

4. 결론

🎯 🔹 핵심 정리

  1. Spring Security는 권한이 없는 사용자가 인증이 필요한 페이지에 접근하면 자동으로 로그인 페이지로 보냄
  2. .loginPage("/login") 설정 덕분에 404 오류 없이 /login 페이지로 이동 가능
  3. 로그인 성공 후 기본적으로 이전에 요청한 페이지로 이동(설정 변경 가능)