스프링 시큐리티

2024. 3. 20. 15:28Backend 취업준비/Spring

스프링 기반의 애플리케이션 보안(인증, 인가)을 담당하는 스프링 하위 프레임워크

 

  • 인증(Authentication): 사용자의 신원 확인 및 인증을 담당. 예를 들어, 사용자가 제공한 자격 증명을 확인하고 인증.
  • 인가(Authorization): 인증된 사용자에 대한 권한 부여를 담당. 특정 리소스에 접근할 수 있는 권한을 확인.
  • 보안 설정: 웹 애플리케이션의 보안 설정을 구성. 예를 들어, 특정 URL 패턴에 대한 보안 정책을 설정하거나, CSRF(Cross-Site Request Forgery) 공격 방지를 위한 설정을 할 수 있다

 

스프링 시큐리티는 앞서 공부한 서블릿 필터 기반으로 동작

SecurityFilterChain의 각 필터에서 인증, 인가와 관련된 작업 처리

 

많은 필터들이 있다.

  • UsernamePasswordAuthenticationFilter : 폼 기반 로그인을 할 때 사용되는 필터로 아이디, 패스워드 데이터를 파싱해 인증 요청을 위임한다. 인증 성공 시 AuthenticationSuccessHandler, 인증 실패시 AuthenticationFailureHandler 실행
  • FilterSecurityIntercepter : 접근 결정 관리자. AccessDecisionManager로 권한 부여 처리를 위임함으로써 접근 제어 결정을 쉽게 해준다. 이 과정에서는 이미 사용자가 인증되어 있으므로 유효한 사용자 인지도 알 수 있다. 즉 인가 관련 설정을 한다 (스프링 인터셉터 아님)
  • LogoutFilter : 설정된 로그아웃 URL로 오는 요청을 확인해 해당 사용자를 로그아웃 처리

 

 

 

스프링 시큐리티 인증 관련 절차

 

 

  1. 사용자가 폼에 아이디와 패스워드를 입력하면, HTTPServletRequest에 아이디와 비밀번호정보가 전달. 이때 AuthenticationFilter가 넘어온 아이디와 비밀번호의 유효성 검사를 한다.
  2. 유효성 검사가 끝나면 실제 구현체인 UsernamePasswordAuthenticationToken을 만들어 넘겨준다.
  3. 전달받은 인증용 객체인 UsernamePasswordAuthenticationToken을 AuthenticationManager에게 보낸다
  4. Username PasswordAuthenticationToken을 AuthenticationProvider에 보낸다.
  5. 사용자 아이디를 UserDetailService에 보낸다. UserDetailService는 사용자 아이디로찾은 사용자의 정보를 UserDetails 객체로 만들어 AuthenticationProvider에게 전달 한다.
  6. DB에 있는 사용자 정보를 가져온다.
  7. 입력 정보와 UserDetails의 정보를 비교해 실제 인증 처리

8 ~ 10까지 인증이 완료되면 SecurityContextHolder에 Authentication을 저장. 인증 성공 여부에 따라 성공하면 AuthenticationSuccessHandler, 실패하면AuthenticationFailureHandler 핸들러를 실행.

 

 

 

@EnableWebSecurity 로 WebSecurityConfig클래스 만들기

 

과거엔 WebSecurityConfigurerAdapter를 상속받아 만들었지만 스프링 버전이 업데이트 됨에 따라 Deprecated 됐다

 

대신 @EnableWebSecurity 어노테이션 사용 하여 설정 클래스에 @EnableWebSecurity 어노테이션을 사용하여 Spring Security의 기본 구성을 활성화 한다

시큐리티를 위한 클래스 들이 import되어 있다

 

 

보안 전반적 설정 커스터마이징

 

  • WebSecurityCustomizer  인터페이스를 사용하면 기본적인 Spring Security 설정을 조정하고 웹 보안을 조정
  • WebSecurityCustomizer 인터페이스의 customize(WebSecurity web) 메서드를 구현 하여 사용

 

  • 특정 URL에 대한 보안 예외 처리: 특정 URL 패턴에 대한 보안 설정을 변경하거나 해당 URL을 보안 필터링에서 제외할 수 있다. 예를 들어, 로그인 페이지나 공개적으로 접근 가능한 리소스에 대한 보안 설정을 커스터마이징할 수 있다.
  • 웹 리소스 보안 구성: 웹 리소스(예: HTML, CSS, JavaScript 파일)에 대한 보안 설정을 구성할 수 있다. 예를 들어, 정적 리소스에 대한 보안 검사를 비활성화하거나, 특정 경로에 대한 보안 정책을 추가할 수 있다
@Bean
    public WebSecurityCustomizer configure() {
        return web -> web.ignoring().requestMatchers(toH2Console())
                .requestMatchers("/static/**");
    }
  • 코드에서는 H2 콘솔에 대한 접근 및 /static/** 패턴의 URL에 대한 보안 검사를 비활성화

 

 

보안 필터 커스터마이징

  • 인증 및 인가 필터 추가: 사용자의 인증 및 권한 부여를 처리하는 필터를 추가할 수 있다. 예를 들어, 폼 기반 인증을 처리하는 필터나 권한 검사를 수행하는 필터를 추가할 수 있다.
  • 보안 설정 구성: CSRF 방어를 비롯한 다양한 보안 설정을 구성할 수 있다. 보안 필터 체인을 통해 HTTPS 리다이렉션, 헤더 보안, XSS 방어 등의 기능을 활성화하거나 비활성화할 수 있다.
  • 로그인 및 로그아웃 설정: 로그인 페이지, 로그인 실패 처리, 로그아웃 처리 등의 설정을 추가할 수 있다
@Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeHttpRequests(auth ->              // 인증, 인가 설정
                        auth.requestMatchers("/login", "/signup", "/user").permitAll()
                                .anyRequest().authenticated())
                .formLogin(auth -> auth.loginPage("/login")     // 폼 기반 로그인 설정
                        .defaultSuccessUrl("/articles"))
                .logout(auth -> auth.logoutSuccessUrl("/login") // 로그아웃 설정
                        .invalidateHttpSession(true))
                .csrf(auth -> auth.disable());                  // csrf 비활성화
        return httpSecurity.build();
    }
  • /login, /signup, /user 경로에 대한 요청은 인증 없이 접근을 허용하고, 그 외의 모든 요청은 인증된 사용자만 접근할 수 있도록 설정
  • formLogin(): 폼 기반 로그인을 설정합니다. 로그인 페이지는 /login으로 설정되고, 로그인 성공 시 /articles로 이동하도록 설정
  • logout(): 로그아웃 설정을 구성합니다. 로그아웃 후 세션을 무효화하고, 로그아웃 성공 시 /login 페이지로 이동하도록 설정
  • csrf(): CSRF(Cross-Site Request Forgery) 보호를 비활성화

 

1. 해당 메서드에서는 딱히 AuthenticationFilter같은 필터를 커스터마이징 하지 않았다

2. 내장 필터들은 보통 스프링 시큐리티의 기본 동작을 따르며, 별도의 사용자 정의 설정이나 활성화 없이도 기본적으로 실행된다

 

 

위의 두 메서드의 차이

  • public WebSecurityCustomizer configure()
    • FilterChainProxy에 대한 커스터마이징을 담당, 리턴값 WebSecurity
    • WebSecurity: 웹 애플리케이션 전반에 대한 보안 구성을 담당하는 클래스. 주로 웹 애플리케이션의 전체적인 보안 설정을 다룸
  • public SecurityFilterChain filterChain()
    • FilterChain에 대한 커스터마이징을 담당 리턴값 HttpSecurity
    • HttpSecurity :  SecurityFilterChain 내에서 각각의 필터에 대한 구체적인 설정을 제공

 

 

사용자 인증 관리자 커스터마이징

 

AuthenticationManager

  • 인증 수행: 클라이언트가 제공한 자격 증명을 검증하여 사용자를 인증합니다. 이는 주로 사용자의 아이디와 비밀번호를 검사하거나, 토큰 기반의 인증 방식을 사용하여 수행됩니다.
  • 인증 결과 반환: 인증에 성공하면 인증된 사용자에 대한 정보를 포함한 Authentication 객체를 반환합니다. 이 정보에는 사용자의 권한, 인증된 아이디 등이 포함
@Bean
    public AuthenticationManager authenticationManager(HttpSecurity httpSecurity, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) {
        return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailService)  // UserDetailsService 구현체
                .passwordEncoder(bCryptPasswordEncoder) // 패스워드 인코더로 사용할 빈
                .and()
                .build();
    }

 

 

  • Spring Security 5 버전부터는 보편적인 경우에 대부분의 애플리케이션에서 AuthenticationManager를 구성하는데 필요한 UserDetailsService와 PasswordEncoder를 자동으로 설정해주는 기능이 추가
  • 따라서 대부분의 경우에는 별도의 AuthenticationManager 빈을 명시적으로 등록할 필요가 없다. 대신에 UserDetailsService와 PasswordEncoder를 구현하고 빈으로 등록해두면 Spring Security가 자동으로 이를 사용하여 AuthenticationManager를 구성

 

 

UserDetailsService

 

  • UserDetailsService는 Spring Security에서 사용자 정보를 조회하는 인터페이스. 이를 구현하여 사용자의 인증에 필요한 사용자 정보를 제공
  • 데이터베이스나 LDAP 등의 백엔드 스토리지에서 사용자 정보를 조회하고 반환하는 데 사용. Spring Security는 사용자가 제공한 인증 정보(예: 사용자 이름 또는 이메일, 비밀번호)를 기반으로 인증을 수행하기 위해 UserDetailsService를 호출.
  • UserDetailsService주요 메서드
    • loadUserByUsername(String username): 주어진 사용자 이름을 기준으로 사용자 정보를 조회. 이 메서드는 Spring Security에 의해 인증 요청이 발생할 때 호출되며, 인증에 사용되는 사용자 정보를 반환
@Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        return userRepository.findByEmail(email)
                .orElseThrow(() -> new IllegalArgumentException(email));
    }

 

 

PasswordEncoder

 

  • 비밀번호를 안전하게 저장하기 위해 사용되는 인터페이스
  • BCryptPasswordEncoder는 Spring Security에서 제공하는 PasswordEncoder의 구현체 중 하나로, BCrypt 알고리즘을 사용하여 비밀번호를 안전하게 해싱

 

UserDetails

  • 사용자 정보를 나타내는 인터페이스
  • 일반적으로 UserDetails를 구현하는 클래스는 User 클래스를 사용
  • 사용자의 세부 정보를 담고 있으며, 주로 사용자의 아이디, 비밀번호, 권한 목록 등의 정보를 포함

메서드들

  • getAuthorities(): 사용자의 권한 목록을 반환. 각 권한은 GrantedAuthority 인터페이스의 구현체로써 사용자의 권한을 나타낸다.
  • getPassword(): 사용자의 비밀번호를 반환. 보안상의 이유로 실제 비밀번호가 아닌 해싱된 비밀번호가 반환된다.
  • getUsername(): 사용자의 아이디(또는 사용자 이름)를 반환
  • isAccountNonExpired(): 계정의 만료 여부를 반환
  • isAccountNonLocked(): 계정의 잠김 여부를 반환
  • isCredentialsNonExpired(): 자격 증명(비밀번호)의 만료 여부를 반환
  • isEnabled(): 계정의 활성화 여부를 반환
@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("user"));
    }
  • getAuthorities() 메서드는 사용자의 권한 목록을 반환하는 메서드로, Spring Security에서 사용된다. 사용자의 권한은 GrantedAuthority 인터페이스의 구현체인 객체들의 컬렉션으로 표현된다.
  • 위의 코드에서는 단순히 "user"라는 권한을 가지고 있는 것으로 설정되어 있다.
  • 이 권한은 SimpleGrantedAuthority 객체를 사용하여 생성되었다. SimpleGrantedAuthority는 Spring Security에서 제공하는 기본적인 권한 객체로, 생성자에 권한을 나타내는 문자열을 전달하여 생성

 

 

로그아웃

 

  • 로그아웃 처리는 Spring Security의 LogoutFilter를 통해 이루어진다.
  • 이 LogoutFilter는 로그아웃 요청을 감지하고 사용자를 로그아웃하며, 그 결과로 세션을 종료하고 필요한 경우 리다이렉트를 수행
@GetMapping("/logout")
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder.getContext().getAuthentication());
        return "redirect:/login";
    }
  • 사용자가 "/logout" 경로로 GET 요청을 보내면, 이 메서드가 실행되어 사용자를 로그아웃하고 로그인 페이지로 리다이렉트
  • 여기서 주요한 역할을 하는 것은 SecurityContextLogoutHandler. 이 클래스는 사용자의 로그아웃을 처리하기 위해 사용된다.
  • logout() 메서드를 호출하여 현재 사용자의 인증 정보를 제거하고 세션을 종료. 이 때, HttpServletRequest와 HttpServletResponse를 사용하여 요청과 응답을 처리하며, SecurityContextHolder.getContext().getAuthentication()를 통해 현재 사용자의 인증 정보를 가져온다.