Akashic Records

Spring Security 는 처음이에요. 본문

Spring for Beginners

Spring Security 는 처음이에요.

Andrew's Akashic Records 2024. 2. 27. 13:33
728x90

HTTP 요청이 스프링 시큐리티 프레임워크를 통해 처리되는 과정을 자세히 보여줍니다. 여기서는 다양한 보안 필터를 거쳐 인증 및 인가 과정이 어떻게 이루어지는지, 그리고 AuthenticationManager 와 AccessDecisionManager 의 역할이 강조되어 있습니다.

 

스프링 시큐리티(Spring Security)는 스프링 기반의 애플리케이션 보안을 위한 강력하고 맞춤화가 가능한 인증 및 접근 제어 프레임워크입니다. 웹과 엔터프라이즈 애플리케이션 모두에 적합하며, 주로 다음과 같은 보안 기능을 제공합니다:

  1. 인증(Authentication): 사용자가 누구인지 확인하는 과정입니다. 스프링 시큐리티는 폼 기반 로그인, LDAP, OAuth, JWT 등 다양한 인증 메커니즘을 지원합니다.

  2. 권한 부여(Authorization): 인증된 사용자가 특정 자원에 접근할 수 있는 권한이 있는지 결정하는 과정입니다. 이는 URL 접근 제어, 메소드 수준의 보안 등을 포함합니다.

  3. 세션 관리(Session Management): 사용자 세션을 관리하고, 동시 세션, 세션 고정 보호, 세션 만료 등의 기능을 제공합니다.

  4. CSRF(Cross-Site Request Forgery) 보호: 사용자를 대신하여 악의적인 변경 요청을 보낼 수 있는 공격을 방지합니다.

  5. CORS(Cross-Origin Resource Sharing) 지원: 웹 애플리케이션이 다른 도메인의 리소스에 안전하게 접근할 수 있도록 합니다.

스프링 시큐리티는 세부적인 보안 요구 사항에 맞게 맞춤 설정이 가능하며, 다양한 인증 메커니즘과 통합될 수 있어 널리 사용됩니다. 또한, 스프링 부트와의 통합을 통해 보안 설정을 더욱 쉽고 빠르게 구성할 수 있습니다.

 

스프링 시큐리티를 설정하고 사용하기 위해서는 다음 단계를 거칩니다:

  1. 의존성 추가: 스프링 부트를 사용하는 경우, spring-boot-starter-security 의존성을 프로젝트에 추가합니다.

  2. 보안 설정 구성: WebSecurityConfigurerAdapter를 확장하여 자신의 보안 요구 사항에 맞게 구성합니다. 인증 방법, URL 접근 규칙, 로그아웃 처리 등을 설정할 수 있습니다.

  3. 사용자 세부 정보 서비스 구현: 사용자 정보를 로드하는 방법을 정의합니다. 인메모리 사용자 저장소, 데이터베이스, LDAP 등 다양한 저장소를 사용할 수 있습니다.

  4. 비밀번호 인코더 구성: 비밀번호 저장과 검증을 위해 비밀번호 인코더를 구성합니다. BCryptPasswordEncoder와 같은 해시 기반 인코더가 권장됩니다.

스프링 시큐리티를 사용함으로써 애플리케이션의 보안을 강화하고, 보안 관련 베스트 프랙티스를 쉽게 적용할 수 있습니다.

인증(Authentication)과 인가(Authorization)

정보 보안의 두 핵심 요소이며, 종종 혼동되기도 합니다. 하지만 이 둘은 명확히 다른 개념입니다.

인증 (Authentication)

  • 목적: 사용자가 누구인지 확인하는 과정입니다. 즉, 사용자가 자신이 주장하는 사람이 맞는지를 확인합니다.
  • 방법: 로그인 폼에 사용자 이름과 비밀번호를 입력하는 것부터 시작하여, 더 복잡한 방법으로는 이중 인증(2FA), 생체 인식, OTP(일회용 비밀번호), 디지털 인증서 등이 있습니다.
  • 예시: 웹 사이트에 로그인할 때 사용자 이름과 비밀번호를 입력하거나, 스마트폰에 지문을 사용하여 잠금을 해제하는 것 등이 인증 과정에 해당합니다.

인가 (Authorization)

  • 목적: 인증된 사용자가 특정 자원이나 데이터에 접근할 수 있는 권한이 있는지를 결정하는 과정입니다. 사용자가 무엇을 할 수 있는지, 어떤 데이터에 접근할 수 있는지를 정의합니다.
  • 방법: 사용자의 역할(Role)이나 권한(Permission)을 기반으로 시스템 내의 자원에 대한 접근을 제어합니다. 이는 일반적으로 접근 제어 목록(ACL), 역할 기반 접근 제어(RBAC), 속성 기반 접근 제어(ABAC) 등의 방법으로 구현됩니다.
  • 예시: 관리자는 웹 사이트의 모든 섹션에 접근할 수 있지만, 일반 사용자는 오직 공개 섹션에만 접근할 수 있습니다. 또는 특정 문서에 대한 읽기 권한은 있지만 수정 권한은 없는 경우 등이 인가 과정에 해당합니다.

차이점 요약

  • 인증은 사용자가 그들이 주장하는 사람임을 증명하는 과정입니다.
  • 인가는 인증된 사용자가 수행할 수 있는 작업과 접근할 수 있는 데이터를 결정하는 과정입니다.

이 두 과정은 보안 시스템에서 중요한 역할을 하며, 대부분의 경우 인증이 선행되고 그 다음에 인가 과정이 이루어집니다.

CSRF(Cross-Site Request Forgery)

사이트 간 요청 위조는 웹 보안 취약점 중 하나로, 공격자가 사용자의 웹 브라우저를 속여, 사용자가 의도하지 않은 행위를 인터넷 상에서 이미 로그인한 다른 사이트에 대해 수행하게 하는 공격 방법입니다. 사용자는 자신이 의도하지 않은 요청을 보내게 되며, 이는 웹 애플리케이션에서 중요한 작업을 수행할 수 있게 합니다. 예를 들어, 소셜 미디어 계정 설정 변경, 비밀번호 변경, 금융 거래 등이 있습니다.

CSRF 공격의 작동 방식

  1. 사용자 로그인: 사용자가 웹 애플리케이션에 로그인하여 정상적인 세션을 시작합니다.
  2. 악의적인 링크: 공격자는 사용자가 클릭할 가능성이 있는 악의적인 링크나 버튼을 준비합니다. 이 링크는 정상적인 요청처럼 보이지만, 실제로는 공격자가 의도한 행위를 수행하는 요청을 포함하고 있습니다.
  3. 요청 전송: 사용자가 이 링크를 클릭하면, 사용자의 브라우저는 이미 인증된 쿠키를 사용하여 공격자가 준비한 요청을 해당 사이트에 전송합니다.
  4. 악의적인 요청 실행: 서버는 요청을 정상적인 사용자의 요청으로 인식하고, 공격자가 의도한 작업을 수행합니다.

CSRF 공격 예방 방법

웹 애플리케이션은 다음과 같은 방법으로 CSRF 공격을 방지할 수 있습니다:

  1. CSRF 토큰 사용: 각 사용자 세션에 대해 유니크하고 예측 불가능한 CSRF 토큰을 생성하고, 폼 제출이나 AJAX 요청에 이 토큰을 포함시킵니다. 서버는 요청을 처리하기 전에 이 토큰을 검증합니다.
  2. SameSite 쿠키 속성 설정: 최신 브라우저는 쿠키에 SameSite 속성을 지원합니다. 이 속성을 설정함으로써 브라우저가 다른 사이트의 요청에서 쿠키를 전송하지 않도록 할 수 있습니다. 예를 들어, SameSite=Lax 또는 SameSite=Strict 설정을 사용합니다.
  3. 사용자의 중요한 작업에 대한 재인증 요구: 사용자가 중요한 작업(비밀번호 변경, 계정 설정 변경 등)을 수행할 때 추가적인 인증(비밀번호 재입력, 이중 인증 등)을 요구합니다.
  4. Referer 검증: HTTP 헤더의 Referer 값을 검증하여 요청이 신뢰할 수 있는 소스에서 발생했는지 확인합니다. 그러나 이 방법은 Referer 헤더가 없거나 조작될 수 있으므로 주의가 필요합니다.
  5. 쿠키에 Custom Header 사용: AJAX 요청에 사용자 정의 헤더를 추가하고, 서버에서는 이 헤더를 검증합니다. 이 방법은 전통적인 폼 제출에는 적용되지 않으므로, CSRF 토큰 방법과 결합하여 사용하는 것이 좋습니다.

CSRF는 웹 애플리케이션의 보안을 위협하는 중요한 취약점 중 하나이며, 개발자는 이를 심각하게 고려하고 적절한 보호 조치를 구현해야 합니다.

CORS(Cross-Origin Resource Sharing)

웹 페이지가 다른 도메인의 자원에 접근할 수 있게 해주는 보안 메커니즘입니다. 기본적으로, 웹 브라우저는 같은 출처 정책(Same-Origin Policy)을 따릅니다. 이 정책은 보안상의 이유로, 한 출처(origin)에서 로드된 문서나 스크립트가 다른 출처의 리소스와 "상호 작용"하는 것을 제한합니다. 여기서 "출처"란 동일한 프로토콜, 도메인(호스트) 이름, 그리고 포트 번호를 의미합니다.

CORS의 필요성

웹 애플리케이션은 종종 다른 출처에서 데이터를 가져오거나 API를 사용해야 할 필요가 있습니다. 예를 들어, 주식 정보를 제공하는 서버에서 데이터를 가져와서 다른 웹 사이트에서 표시하려는 경우가 있을 수 있습니다. CORS는 이러한 상황에서 안전하게 다른 출처의 리소스를 요청하고 사용할 수 있도록 해줍니다.

CORS 작동 방식

CORS는 HTTP 헤더를 사용하여 다른 출처의 리소스에 접근할 수 있는 권한을 부여합니다. 웹 애플리케이션에서 다른 출처의 리소스를 요청할 때, 브라우저는 자동으로 해당 요청에 Origin 헤더를 추가합니다. 이 헤더는 요청을 만든 문서의 출처를 나타냅니다.

서버는 이 요청을 받고, 접근을 허용하려면 응답에 Access-Control-Allow-Origin 헤더를 포함시켜야 합니다. 이 헤더의 값으로는 특정 출처를 명시하거나 *를 사용하여 모든 출처에서의 접근을 허용할 수 있습니다.

주요 CORS 헤더

  • Origin: 브라우저가 요청을 보낼 때 자동으로 설정하는 헤더로, 요청이 시작된 출처를 나타냅니다.
  • Access-Control-Allow-Origin: 서버가 응답할 때 설정하는 헤더로, 해당 리소스에 접근할 수 있는 출처를 명시합니다.
  • Access-Control-Allow-Methods: 서버가 허용하는 HTTP 메소드를 명시합니다 (예: GET, POST).
  • Access-Control-Allow-Headers: 서버가 허용하는 헤더를 명시합니다.
  • Access-Control-Max-Age: 브라우저가 preflight 요청의 결과를 캐싱할 수 있는 최대 시간을 나타냅니다.

Preflight 요청

"복잡한" HTTP 요청(예: HTTP 메소드가 GET, HEAD, POST 외의 것을 사용하거나, 특정 HTTP 헤더를 포함하는 경우)을 보내기 전에, 브라우저는 "preflight" 요청을 보냅니다. 이는 OPTIONS 메소드를 사용하여 서버에게 실제 요청을 보내기 전에 통신을 시도해도 되는지를 묻는 것입니다. 서버는 preflight 요청에 대해 허용하는 메소드, 헤더 등을 응답으로 보내고, 이를 바탕으로 브라우저는 실제 요청을 보낼지 말지를 결정합니다.

CORS 정책 구현

서버 측에서는 CORS 정책을 구현하여, 어떤 출처에서 자원에 접근할 수 있는지를 제어할 수 있습니다. 이는 보안을 강화하고, 민감한 데이터에 대한 무단 접근을 방지하는 데 중요합니다. 대부분의 웹 서버 및 프레임워크는 CORS를 설정할 수 있는 옵션을 제공합니다.

SecurityFilterChain

SecurityFilterChain은 스프링 시큐리티에서 HTTP 요청에 대한 보안 처리를 담당하는 필터들의 집합입니다. 스프링 시큐리티는 다양한 보안 관련 작업을 처리하기 위해 서블릿 필터 기반의 아키텍처를 사용합니다. 이러한 필터들은 인증, 인가, CSRF 보호, 세션 관리 등과 같은 보안 기능을 수행합니다. SecurityFilterChain을 통해 이러한 필터들이 순서대로 구성되고, HTTP 요청이 들어올 때마다 이 필터 체인을 통과하면서 필요한 보안 처리가 이루어집니다.

SecurityFilterChain 구성

SecurityFilterChain은 여러 Filter 인스턴스를 포함하며, 각 필터는 특정 보안 관련 작업을 담당합니다. 일반적으로 포함되는 필터의 예는 다음과 같습니다:

  • UsernamePasswordAuthenticationFilter: 폼 기반 로그인 처리를 담당합니다.
  • BasicAuthenticationFilter: HTTP 기본 인증 처리를 담당합니다.
  • CsrfFilter: CSRF(Cross-Site Request Forgery) 공격을 방지합니다.
  • LogoutFilter: 로그아웃 요청을 처리합니다.
  • ExceptionTranslationFilter: 인증 및 인가 과정에서 발생하는 예외를 처리합니다.
  • FilterSecurityInterceptor: 접근 제어 결정 및 인가 처리를 담당합니다.

SecurityFilterChain 커스터마이징

스프링 시큐리티는 WebSecurityConfigurerAdapter를 확장하여 SecurityFilterChain을 커스터마이징할 수 있는 방법을 제공합니다. 개발자는 이를 통해 필요에 따라 필터 체인에 필터를 추가하거나 제거하고, 필터의 순서를 변경하며, 다양한 보안 설정을 커스터마이징할 수 있습니다.

 

예를 들어, 다음은 스프링 시큐리티 설정의 일부를 커스터마이징하는 예시입니다:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll() // 특정 경로에 대한 접근 허용
                .anyRequest().authenticated() // 나머지 요청에 대해 인증 요구
            .and()
                .formLogin()
                .loginPage("/login") // 커스텀 로그인 페이지 지정
                .permitAll()
            .and()
                .logout()
                .permitAll();
    }

    // 다른 커스텀 보안 설정...
}

정리

SecurityFilterChain은 스프링 시큐리티에서 중요한 역할을 하는데, HTTP 요청이 서버에 도달하기 전에 여러 보안 관련 작업을 수행합니다. 이를 통해 애플리케이션의 보안을 강화할 수 있으며, 필요에 따라 유연하게 커스터마이징할 수 있습니다. 스프링 시큐리티 설정을 통해 개발자는 애플리케이션의 보안 요구사항에 맞게 이 필터 체인을 쉽게 구성하고 관리할 수 있습니다.

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter는 스프링 시큐리티의 필터 중 하나로, 주로 폼 기반 로그인 인증 처리를 담당합니다. 이 필터는 사용자가 로그인 폼을 통해 제출한 사용자 이름과 비밀번호를 기반으로 인증 과정을 수행합니다. 사용자 이름과 비밀번호는 HttpServletRequest에서 추출되며, 이 정보를 사용하여 Authentication 객체를 생성하고 인증 과정을 시작합니다.

UsernamePasswordAuthenticationFilter 작동 방식

  1. 사용자가 로그인 폼에 사용자 이름과 비밀번호를 입력하고 제출합니다.
  2. UsernamePasswordAuthenticationFilter가 요청을 가로채고, 폼 데이터에서 사용자 이름과 비밀번호를 추출합니다.
  3. 이 필터는 추출한 정보를 사용하여 UsernamePasswordAuthenticationToken 객체를 생성합니다. 이 단계에서의 토큰은 아직 인증되지 않은 상태입니다.
  4. 생성된 UsernamePasswordAuthenticationTokenAuthenticationManager에 전달되어 인증 과정이 수행됩니다.
  5. 인증 과정이 성공적으로 완료되면, 인증된 Authentication 객체가 반환되고, 사용자는 시스템에 성공적으로 로그인합니다.
  6. 인증에 실패하면, 실패 핸들러가 실행되어 사용자에게 적절한 오류 메시지를 표시합니다.

UsernamePasswordAuthenticationFilter 커스터마이징 예시

스프링 시큐리티 설정에서 UsernamePasswordAuthenticationFilter를 직접 등록하거나 커스터마이즈하는 경우는 드뭅니다. 대신, WebSecurityConfigurerAdapter를 상속받은 설정 클래스에서 configure(HttpSecurity http) 메소드를 오버라이드하여 로그인 폼, 로그인 처리 URL, 성공/실패 핸들러 등을 설정함으로써 간접적으로 이 필터의 동작을 커스터마이즈할 수 있습니다.

 

아래는 UsernamePasswordAuthenticationFilter와 관련된 설정을 커스터마이징하는 간단한 예시입니다:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 인메모리 사용자 저장소에 사용자 추가
        auth.inMemoryAuthentication()
            .withUser("user")
            .password(new BCryptPasswordEncoder().encode("password"))
            .roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated() // 모든 요청에 대해 인증 요구
            .and()
                .formLogin()
                    .loginPage("/login") // 커스텀 로그인 페이지 URL
                    .loginProcessingUrl("/authenticate") // 로그인 폼 제출 URL
                    .usernameParameter("username") // 사용자 이름 파라미터명
                    .passwordParameter("password") // 비밀번호 파라미터명
                    .defaultSuccessUrl("/home") // 로그인 성공시 리다이렉트 URL
                    .failureUrl("/login?error=true") // 로그인 실패시 리다이렉트 URL
            .permitAll(); // 모두에게 로그인 페이지 접근 허용
    }
}

위 코드에서는 사용자 이름과 비밀번호를 usernamepassword 파라미터로 받아 인증을 수행하도록 설정하고 있습니다. 또한, 로그인 성공 및 실패 시 리다이렉션할 URL을 지정하고 있습니다.

 

이 예시는 매우 기본적인 형태이며, 실제 애플리케이션에서는 사용자 인증 정보를 데이터베이스에서 불러오거나, 성공/실패 핸들러를 더 세밀하게 커스터마이징하는 등의 작업이 필요할 수 있습니다.

FilterSecurityInterceptor 

FilterSecurityInterceptor는 스프링 시큐리티의 필터 중 하나로, HTTP 요청에 대한 접근 제어 결정을 담당합니다. 이 필터는 스프링 시큐리티 필터 체인의 마지막에 위치하며, 인증된 사용자가 특정 URL이나 메소드에 접근할 수 있는지를 결정하는 역할을 합니다. FilterSecurityInterceptorAccessDecisionManager를 사용하여 접근 제어 결정을 내리고, SecurityMetadataSource를 통해 해당 요청에 필요한 권한 정보를 얻습니다.

FilterSecurityInterceptor 작동 방식

  1. HTTP 요청이 들어오면 FilterSecurityInterceptor는 해당 요청에 매핑된 권한 정보를 SecurityMetadataSource에서 조회합니다.
  2. 조회된 권한 정보와 현재 인증된 사용자의 권한을 AccessDecisionManager에 전달합니다.
  3. AccessDecisionManager는 사용자의 권한이 요청된 자원에 접근할 수 있는지 여부를 판단합니다.
  4. 접근이 허용되면 요청은 계속 진행되고, 거부되면 AccessDeniedException이 발생합니다.

FilterSecurityInterceptor 커스터마이징 예시

스프링 시큐리티에서 FilterSecurityInterceptor를 직접적으로 커스터마이즈하는 것은 일반적인 경우가 아닙니다. 대신, HttpSecurity를 통해 접근 제어 규칙을 구성함으로써 간접적으로 이 필터의 동작을 커스터마이즈할 수 있습니다. 다음은 WebSecurityConfigurerAdapter를 사용하여 접근 제어 규칙을 설정하는 예시입니다:

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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN") // "/admin/**" 경로는 ADMIN 역할을 가진 사용자만 접근 가능
                .antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // "/user/**" 경로는 USER 또는 ADMIN 역할을 가진 사용자 접근 가능
                .antMatchers("/public/**").permitAll() // "/public/**" 경로는 모든 사용자 접근 가능
            .anyRequest().authenticated() // 그 외 모든 요청은 인증된 사용자만 접근 가능
            .and()
                .formLogin() // 폼 로그인 활성화
            .and()
                .httpBasic(); // HTTP 기본 인증 활성화
    }
}

이 코드는 다양한 URL 패턴에 대해 접근 제어 규칙을 정의합니다. 예를 들어, /admin/** 경로는 "ADMIN" 역할을 가진 사용자만 접근할 수 있고, /user/** 경로는 "USER" 또는 "ADMIN" 역할을 가진 사용자가 접근할 수 있습니다. /public/** 경로는 인증되지 않은 사용자도 접근할 수 있습니다.

정리

FilterSecurityInterceptor는 스프링 시큐리티의 핵심 필터 중 하나로, HTTP 요청에 대한 접근 제어를 담당합니다. 개발자는 HttpSecurity 객체를 통해 접근 제어 규칙을 선언적으로 구성함으로써, 이 필터의 동작을 간접적으로 커스터마이즈할 수 있습니다.

스프링 시큐리티 주요 필터

스프링 시큐리티는 다양한 보안 관련 작업을 처리하기 위해 여러 필터를 사용합니다. 아래 표는 스프링 시큐리티에서 사용되는 주요 필터들을 간략하게 정리한 것입니다.

필터이름 설명
SecurityContextPersistenceFilter 인증 정보를 SecurityContext에 저장하거나 복원하는 역할을 담당합니다. 세션에 인증 정보를 유지 관리합니다.
LogoutFilter 로그아웃 요청을 처리하고, 세션을 무효화하고 쿠키를 삭제하는 등의 로그아웃 로직을 실행합니다.
UsernamePasswordAuthenticationFilter 폼 기반 로그인 인증을 처리합니다. 사용자가 제출한 사용자 이름과 비밀번호로 인증을 시도합니다.
BasicAuthenticationFilter HTTP 기본 인증 헤더를 처리하여 인증을 시도합니다.
RememberMeAuthenticationFilter 자동 로그인(remember-me) 기능을 처리합니다. 사용자가 명시적으로 로그아웃하기 전까지 세션을 유지합니다.
AnonymousAuthenticationFilter 현재 사용자가 인증되지 않았을 경우, 익명 사용자로 인증된 Authentication 객체를 SecurityContext에 추가합니다.
ExceptionTranslationFilter 보안 예외 처리를 담당합니다. AccessDeniedExceptionAuthenticationException을 적절히 처리합니다.
FilterSecurityInterceptor HTTP 리소스에 대한 접근 제어 결정을 내리는 필터입니다. AccessDecisionManager를 사용하여 접근을 승인하거나 거부합니다.

이 표에 나열된 필터들은 스프링 시큐리티 필터 체인에서 기본적으로 활성화되어 있으며, 스프링 시큐리티의 보안 메커니즘을 구성하는 핵심 요소입니다. 필터의 순서는 보안 처리 과정에서 중요한 역할을 하며, 필터 체인의 구성은 스프링 시큐리티의 설정에 따라 다를 수 있습니다.

Spring Security Starter

스프링 부트 프로젝트에서 스프링 시큐리티를 사용하기 위해 Gradle 빌드 스크립트에 필요한 의존성을 추가하는 방법은 간단합니다. 아래는 스프링 시큐리티를 프로젝트에 포함시키기 위한 Gradle 설정의 기본적인 예시입니다.

Gradle 설정 예시

build.gradle 파일을 열고, dependencies 섹션에 스프링 시큐리티 관련 의존성을 추가합니다. 스프링 부트의 경우, 스프링 부트 스타터를 사용하면 간편하게 필요한 의존성을 관리할 수 있습니다.

plugins {
    id 'org.springframework.boot' version '2.5.4'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
}

이 예시에서는 다음과 같은 의존성이 추가되었습니다:

  • spring-boot-starter-web: 스프링 부트 웹 애플리케이션 개발을 위한 스타터 패키지입니다.
  • spring-boot-starter-security: 스프링 시큐리티를 사용하기 위한 스타터 패키지입니다. 이 의존성을 추가하면 스프링 시큐리티의 기본 설정이 자동으로 적용됩니다.
  • spring-boot-starter-testspring-security-test: 테스트를 위한 의존성입니다. 스프링 부트와 스프링 시큐리티 관련 테스트를 작성할 때 유용합니다.

의존성 버전 관리

스프링 부트를 사용할 때, spring-boot-starter-parent를 부모 프로젝트로 지정하거나 io.spring.dependency-management 플러그인을 사용하면 스프링 부트가 관리하는 의존성들의 호환 가능한 버전이 자동으로 적용됩니다. 이를 통해 버전 충돌 없이 의존성을 관리할 수 있습니다.

주의 사항

  • 스프링 부트의 버전에 따라 적절한 spring-boot-starter-security의 버전이 다를 수 있으므로, 프로젝트의 스프링 부트 버전과 호환되는지 확인하는 것이 좋습니다.
  • 스프링 시큐리티를 추가하면 기본적으로 모든 요청에 대해 인증을 요구하게 됩니다. 따라서, 웹 애플리케이션의 보안 요구 사항에 맞게 인증 및 권한 부여 설정을 커스터마이징해야 할 수 있습니다.

UserDetails 인터페이스 구현

 

UserDetails 인터페이스를 구현하는 Entity 클래스의 Java 코드를 예시로 만들어 보겠습니다.

아래 코드는 UserDetails 인터페이스를 구현하는 간단한 엔티티 클래스입니다. 이 클래스는 스프링 시큐리티에서 사용자의 정보를 다루기 위해 사용됩니다.

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.util.Collection;
import java.util.Date;

@Entity
@Table(name = "users")
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 255)
    private String email;

    @Column(nullable = false, length = 255)
    private String password;

    @Column(name = "created_at", nullable = false)
    private Date createdAt;

    @Column(name = "updated_at", nullable = false)
    private Date updatedAt;

    // Implementing methods from UserDetails
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // TODO: Return the authorities granted to the user
        return null;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.email;
    }

    @Override
    public boolean isAccountNonExpired() {
        // TODO: Implement logic to determine if the user's account is non-expired
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // TODO: Implement logic to determine if the user's account is non-locked
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // TODO: Implement logic to determine if the user's credentials are non-expired
        return true;
    }

    @Override
    public boolean isEnabled() {
        // TODO: Implement logic to determine if the user is enabled
        return true;
    }

    // Standard getters and setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }

    public Date getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Date updatedAt) {
        this.updatedAt = updatedAt;
    }
}

이 클래스는 UserDetails 인터페이스에서 정의하는 메소드들을 구현하며, 각 메소드는 사용자의 인증과 관련된 세부 정보를 반환합니다. 실제 애플리케이션에서는 getAuthorities(), isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled() 메소드들에 실제 비즈니스 로직을 구현해야 합니다.

 

또한, @Entity@Table 어노테이션은 JPA(Java Persistence API)의 일부로, 데이터베이스의 users 테이블과 매핑되는 엔티티 클래스임을 나타냅니다. @Id@GeneratedValue는 기본 키를 위한 것이며, @Column 어노테이션은 테이블의 각 컬럼과 매핑됩니다.

 

이 클래스를 사용하여 스프링 시큐리티와 통합하면, 스프링 시큐리티는 인증 과정에서 이 엔티티의 인스턴스를 사용하여 사용자의 정보를 처리할 수 있습니다.

 

UserDetails 인터페이스에서 오버라이드할 수 있는 메소드들에 대한 설명을 표 형식으로 정리하면 다음과 같습니다:

메소드 이름 반환 타입 설명
getAuthorities() Collection<? extends GrantedAuthority> 사용자에게 부여된 권한을 반환합니다. 이 권한은 사용자가 액세스할 수 있는 보안된 자원을 결정하는 데 사용됩니다.
getPassword() String 사용자의 비밀번호를 반환합니다. 일반적으로 비밀번호는 인코딩된 형태로 저장됩니다.
getUsername() String 사용자의 이름을 반환합니다. 스프링 시큐리티에서는 이를 사용자의 식별자로 사용합니다.
isAccountNonExpired() boolean 계정이 만료되었는지 여부를 반환합니다. 계정이 만료되지 않았다면 true를 반환해야 합니다.
isAccountNonLocked() boolean 계정이 잠겨있는지 여부를 반환합니다. 계정이 잠기지 않았다면 true를 반환해야 합니다.
isCredentialsNonExpired() boolean 사용자의 자격 증명(비밀번호)이 만료되었는지 여부를 반환합니다. 자격 증명이 만료되지 않았다면 true를 반환해야 합니다.
isEnabled() boolean 사용자 계정이 활성화(사용 가능) 상태인지 여부를 반환합니다. 계정이 활성화되었다면 true를 반환해야 합니다.

이 메소드들은 사용자의 인증 정보를 관리하는 데 필수적이며, 스프링 시큐리티는 이 정보를 사용하여 인증 및 권한 부여 과정을 처리합니다. UserDetails 인터페이스의 구현체는 이 메소드들을 오버라이드하여 애플리케이션의 보안 요구 사항에 맞게 행동을 정의해야 합니다.

예시 코드

스프링 시큐리티를 사용하여 사용자 로그인 및 로그아웃 기능을 구현하는 예시 코드는 다음과 같습니다. 이 예시에서는 User 엔티티를 사용하며, UserDetailsService를 구현하여 사용자 정보를 불러오고, WebSecurityConfigurerAdapter를 상속받아 HTTP 보안 설정을 커스터마이즈합니다.

UserDetailsService 구현

import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.security.core.userdetails.UserDetails;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + username));
        return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), user.getAuthorities());
    }
}

Security Configuration

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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.beans.factory.annotation.Autowired;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // Configure form login
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/home", true)
                .failureUrl("/login?error=true")
                .permitAll()
            .and()
            // Configure logout
            .logout()
                .logoutSuccessUrl("/login?logout=true")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID")
                .permitAll()
            .and()
            // Configure URL authorization
            .authorizeRequests()
                .antMatchers("/signup", "/about").permitAll() // Allow access to these pages without authentication
                .anyRequest().authenticated(); // All other pages require authentication
    }
}

UserRepository 인터페이스

import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

로그인 페이지 Controller

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {

    @GetMapping("/login")
    public String login() {
        return "login"; // 이름이 'login'인 로그인 페이지로 리턴합니다.
    }

    @GetMapping("/home")
    public String home() {
        return "home"; // 로그인 성공 후 'home' 페이지로 리턴합니다.
    }
}

위 코드에서는 다음과 같은 작업을 수행합니다:

  1. CustomUserDetailsServiceUserDetailsService 인터페이스를 구현하여, 데이터베이스에서 사용자 정보를 불러옵니다.
  2. SecurityConfig는 HTTP 보안 설정을 위해 WebSecurityConfigurerAdapter를 상속받습니다. 여기에서 로그인 및 로그아웃 설정을 정의하고, URL 기반의 보안을 설정합니다.
  3. UserRepository는 JPA JpaRepository를 상속받아 사용자 정보에 접근하는 메소드를 제공합니다.
  4. LoginController는 로그인 페이지와 홈 페이지에 대한 요청을 처리합니다.

이 코드는 스프링 시큐리티의 기본적인 설정을 보여주며, 실제 사용시에는 뷰 페이지(login.html, home.html)를 생성하고, 사용자의 비밀번호가 BCryptPasswordEncoder로 인코딩되어 저장되어 있어야 합니다. 또한, 로그인 폼에서 사용자의 이메일과 비밀번호를 올바르게 처리하기 위한 설정이 필요합니다.

728x90

'Spring for Beginners' 카테고리의 다른 글

Spring Rest API는 처음인데요.  (0) 2024.02.22
Spring JPA는 처음인데요.  (0) 2024.02.19
@SpringBootApplication  (1) 2024.02.13
Spring Boot 첫 번째 예제  (0) 2024.02.13
기본 용어의 이해  (0) 2024.02.13
Comments