Framework/Spring

Spring Security로 비밀번호 암호화 하기

뚜sh뚜sh 2024. 1. 27. 20:26

1. 의존성 주입

  • build.gradle에 아래 코드 추가
implementation 'org.springframework.boot:spring-boot-starter-security'

 

 

 

2. 스프링 시큐리티를 사용하여 웹 보안을 구성하는 데 사용되는 설정 클래스 생성

  • 'SecurityConfig' 클래스는 '@EnableWebSecurity' 어노테이션을 사용하여 활성화되고, 암호화를 위해 BCrypt 알고리즘을 사용하며 기본적인 HTTP 보안 구성을 설정함
package qt.qr_backend.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.Customizer;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

// 이 클래스가 Spring 설정 클래스임을 나타냄
// 이 어노테이션을 사용하면 클래스 내에서 @Bean 어노테이션이 붙은 메소드로부터 생성된 빈 객체들이 스프링 컨테이너에 등록됨
@Configuration
// @EnableWebSecurity 어노테이션은 스프링 시큐리티를 활성화하는 어노테이션
// 이 어노테이션을 사용하면 스프링 시큐리티와 관련된 설정을 구성할 수 있는 클래스로 인식됨
@EnableWebSecurity
public class SecurityConfig {

    // passwordEncoder 메서드는 비밀번호를 해싱하기 위한 BCryptPasswordEncoder 빈을 생성하여 반환함
    // 이 해시 알고리즘은 안전한 비밀번호 저장을 위해 사용됨
    // 사용자의 비밀번호는 이 해시 알고리즘을 사용하여 저장됨
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // filterChain 메서드는 SecurityFilterChain 빈을 생성하여 반환함
    // 이 빈은 스프링 시큐리티 필터 체인을 정의하는 역할을 함
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // 이 부분은 모든 HTTP 요청에 대해 인증(authenticated)이 필요하다고 설정하는 부분
                // 즉, 사용자는 로그인 후에만 접근이 허용됨
                .authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
                // HTTP Basic 인증을 사용하도록 설정
                // 이것은 기본적인 HTTP 인증 방식으로, 사용자 이름과 비밀번호를 전달하여 인증
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

 

  • filterChain을 따로 설정하지 않으면 스프링 시큐리티의 기본 설정에 따라 동작함
    1. 모든 요청에 대한 접근이 허용되지 않으며, 인증이 필요합니다. (인증된 사용자만 접근 가능)
    2. HTTP Basic 인증 방식을 사용하여 사용자를 인증합니다.
  • 한 마디로 위의 코드에 설정한 방식이 기본 설정이랑 같다는 말임

 

 

 

3. password를 포함한 엔티티에 암호화 함수를 생성

public class User {
    private String password;

    public void encodePassword(PasswordEncoder passwordEncoder, String password) {
        this.password = passwordEncoder.encode(password);
    }
}

 

4. 비밀번호를 객체에 저장할 때 encodePassword(password)의 값을 저장함

 

 

 

 

 

 

 

 

 

[ERROR] 스프링 시큐리티를 추가하고 나서 빌드에 계속 실패했다

원인을 알고 보니 PasswordEncoder의 빈 이름과 PasswordEncoder를 주입할 때의 변수명을 다르게 지정해놓은 것 때문이었다..

부끄러웠지만 다음부터는 이런 실수를 하지 않기 위해 기록한다..

 

알고보니 다른 근본적인 문제가 있었다. 2-3시간을 찾다가 Service의 PasswordEncoder에 빈이 주입되지 않아서 빌드에 실패한다는 것을 알게 되었고, 그 원인은 SecurityConfig에 @Configuration을 붙이지 않아서였다.

기능 구현도 좋지만 구현해야 하는 기능에 대한 기본적인 지식에 대해 자세히 살펴보는 게 필요할 것 같다..

 

이젠 진짜 되는 줄 알았는데 401 에러를 뱉었다..

하지만 .csrf(AbstractHttpConfigurer::disable)를 추가해서 해결했다!!!!!!!

401 에러가 해결된 이유는 클라이언트가 서버로 요청을 보낼 때 CSRF 토큰을 포함하지 않으면, Spring Security는 요청을 거부하고 401 Unauthorized 오류를 반환한다.

하지만 'csrf'를 비활성화하면 이러한 CSRF 토큰 검증이 수행되지 않아 401 에러가 발생하지 않는다!!!!!

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // Cross-Site Request Forgery 보호 기능을 비활성화함
                // CSRF는 웹 애플리케이션에서 사용자가 자신의 의지와는 무관하게 위조된 요청을 보내는 공격을 막기 위한 기능
                // 이 기능을 비활성화하면 서버는 CSRF 토큰 없이도 요청을 받아들임
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests((authz) -> authz
                        // 'requestMatchers' 메서드로 특정 경로를 지정하고
                        // 'permitAll'로 그 경로들에 대한 접근을 인증되지 않은 모든 사용자에게 허용함
                        .requestMatchers("/signup", "/login").permitAll()
                        // 위에서 지정한 경로들을 제외한 모든 요청에 대해서는 인증된 사용자만 접근할 수 있도록 설정함
                        .anyRequest().authenticated())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}