🌿SPRING/🍀공부 [SPRING]

[SPRING] Spring Security framework

디카페인라떼 2022. 8. 23. 00:38

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

  • 스프링 서버에 필요한 인증 및 인가를 위해 많은 기능을 제공해 줌으로써 개발의 수고를 덜어줌!

Spring Security는 ' 인증'과 '권한'에 대한 부분을 Filter 흐름에 따라 처리한다.

 

더보기

※ 인증 ? 해당 사용자가 본인이 맞는지를 확인하는 절차

    인가 ? 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차

※ Principal (접근 주체) : 보호받는  Resource에 접근하는 대상

    Credential (비밀번호) : Resource에 접근하는 대상의 비밀번호 

 

<Filter / Interceptor>

  • Filter와 Interceptor는 실행되는 시점이 다름!
  • Filter : Web Application에 등록 // Servlet 에서 처리하기 전후로 다룸
  • Interceptor : Spring의 Context에 등록 // 실행시점을 다르게 가져감

 


<스프링 시큐리티 프레임워크 추가>

build.gradle

		// 스프링 시큐리티
    implementation 'org.springframework.boot:spring-boot-starter-security'

<스프링 시큐리티 활성화>

security > WebSecurityConfig

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.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authz) -> authz
                        // 어떤 요청이든 '인증'
                        .anyRequest().authenticated()
                )
                // 로그인 기능 허용
                .formLogin()
                .defaultSuccessUrl("/")
                .permitAll()
                .and()
                //로그아웃 기능 허용
                .logout()
                .permitAll();

        return http.build();
    }
}

<로그인/로그아웃 처리과정 이해>

  • 스프링 시큐리티 사용 전

  • 스프링 시큐리티 사용 후

 

  • Client 의 요청은 모두 Spring Security 를 거침
  • Spring Security 역할
    1. 인증/인가
      1. 성공 시: Controller 로 Client 요청 전달
        1. Client 요청 + 사용자 정보 (UserDetails)
      2. 실패 시: Controller 로 Client 요청 전달되지 않음
        1. Client 에게 Error Response 보냄
  • 로그인 처리과정

더보기
  1. Client
    1. 로그인 시도
    2. 로그인 시도할 username, password 정보를 HTTP body 로 전달 (POST 요청)
    3. 로그인 시도 URL 은 WebSecurityConfig 클래스에서 변경 가능
  2. 인증 관리자 (Authentication Manager)
    1. UserDetailsService 에게 username 을 전달하고 회원상세 정보를 요청
  3. UserDetailsService
    1. 회원 DB 에서 회원 조회
      1. 회원 정보가 존재하지 않을 시 → Error 발생
    2. 조회된 회원 정보(user) 를 UserDetails 로 변환
    3. UserDetails 를 "인증 관리자"에게 전달
  4. "인증 관리자" 가 인증 처리
    1. 아래 2 개의 username, password 일치 여부 확인
      1. Client 가 로그인 시도한 username, password
      2. UserDetailsService 가 전달해준 UserDetails 의 username, password
      1. password 비교 시
        1. Client 가 보낸 password 는 평문이고, UserDetails 의 password 는 암호문
        2. Client 가 보낸 password 를 암호화해서 비교
      2. 인증 성공 시 → 세션에 로그인 정보 저장
      3. 인증 실패 시 → Error 발생
  • 로그아웃 처리
    • "GET /user/logout" 요청 시 로그아웃
    • 서버 세션에 저장되어 있는 로그인 사용자 정보 삭제

<로그인/로그아웃 구현>

1. 로그인, 로그아웃 처리 URL 설정

security > WebSecurityConfig

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.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig {

    @Bean
    public BCryptPasswordEncoder encodePassword() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        // h2-console 사용에 대한 허용 (CSRF, FrameOptions 무시)
        return (web) -> web.ignoring()
                .antMatchers("/h2-console/**");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 회원 관리 처리 API (POST /user/**) 에 대해 CSRF 무시
        http.csrf()
                .ignoringAntMatchers("/user/**");

        http
                .authorizeHttpRequests((authz) -> authz
                        // image 폴더를 login 없이 허용
                        .antMatchers("/images/**").permitAll()
                        // css 폴더를 login 없이 허용
                        .antMatchers("/css/**").permitAll()
                        // 회원 관리 처리 API 전부를 login 없이 허용
                        .antMatchers("/user/**").permitAll()
                        // 어떤 요청이든 '인증'
                        .anyRequest().authenticated()
                )
                // [로그인 기능]
                .formLogin()
                // 로그인 View 제공 (GET /user/login)
                .loginPage("/user/login")
                // 로그인 처리 (POST /user/login)
                .loginProcessingUrl("/user/login")
                // 로그인 처리 후 성공 시 URL
                .defaultSuccessUrl("/")
                // 로그인 처리 후 실패 시 URL
                .failureUrl("/user/login?error")
                .permitAll()
                .and()
                // [로그아웃 기능]
                .logout()
                // 로그아웃 처리 URL
                .logoutUrl("/user/logout")
                .permitAll();

        return http.build();
    }
}

2. DB 의 회원 정보 조회 → 스프링 시큐리티의 "인증 관리자" 에게 전달

  • UserDetailsService 구현
    • UserDetailsService 인터페이스(기본 스프링시큐리티) → UserDetailsServiceImpl 클래스(직접 만들어야함)

security > UserDetailsServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Autowired
    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("Can't find " + username));

        return new UserDetailsImpl(user);
    }
}
  • UserDetails 구현
    • UserDetails 인터페이스 → UserDetailsImpl 클래스(직접구현)

security > UserDetailsImpl

public class UserDetailsImpl implements UserDetails {

    private final User user;

    public UserDetailsImpl(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }

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

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

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.emptyList();
    }
}

 


설정하는 것도 좋고 default 로그인 기능도 다 좋으나!

이슈가 발생하기 쉬움!

EX)

  • 로그인 페이지에 CSS 가 적용되지 않음
  • 웹 페이지 이슈가 생겼을 때 원인을 파악하고 해결할 수 있는 능력 필요

1) '개발자 도구' 로 에러 내용 확인

2) 해결 방법 : 스프링 시큐리티의 URL 허용 정책 변경 필요 확인

  // image 폴더를 login 없이 허용
  .antMatchers("/images/**").permitAll()
  // css 폴더를 login 없이 허용
  .antMatchers("/css/**").permitAll()