팀프로젝트_PetHarmony

Spring Security 기록 (1)

이채림 2024. 8. 9. 04:27

작업 내용을 기록하고자 한다.

 

1) build.gradle 설정

ext {
	springSecurityVersion = '6.1.9'
}
dependencies {
	implementation "org.springframework.boot:spring-boot-starter-security"
}

 

1.5) Role, UserState 클래스 작성 (Enum)

User 테이블을 만들 때 권한과 상태를 까먹어서 급하게 만들었다,,

 

2) Spring Security 설정 클래스 작성

(import문 생략)

package luckyvicky.petharmony.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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private final UserDetailsService userDetailsService;
    public SecurityConfig(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorize -> authorize
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        .anyRequest().authenticated()
                )
                .formLogin(form -> form
                        .loginPage("/login")
                        .permitAll()
                )
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .permitAll()
                )
                .exceptionHandling(exception -> exception.accessDeniedPage("/login"));
        return http.build();
    }

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

 

이 코드에 대해서 설명하자면

@Configuration
@EnableWebSecurity
이 클래스가 Spring Security 설정을 담당하는 구성 클래스임을 나타낸다.

 

 

http.authorizeRequests()
요청 URL 권한 설정을 구성한다.

/admin/** 경로에 대해서는 "ADMIN" 권한이 필요하다.

그 외 모든 요청은 인증이 필요하다.

http.formLogin()
폼 기반 로그인 설정을 구성한다.

로그인 URL를 /login으로 지정한다.

로그인 페이지 접근을 모든 사용자에게 허용한다.

http.logout()
로그아웃 설정을 구성한다.

로그아웃 URL을 /logout으로 지정한다.

로그아웃 기능을 모든 사용자에게 허용한다.

http.exceptionHandling()
예외 처리 설정을 구성한다.

접근 거부 시 이동할 페이지를 /login으로 지정한다.

http.build()
구성한 설정을 기반으로 SecurityFilterChain 빈을 생성한다

 

PasswordEncoder

비밀번호 암호화를 위해 BCryptPasswordEncoder를 사용하도록 설정되어 있다.

 

요약하면,

이 코드는 Spring Security를 사용하여 ADMIN 권한이 필요한 /admin/** 경로, 폼 기반 로그인 및 로그아웃 기능, 그리고 비밀번호 암호화 기능을 구현하고 있다. 이를 통해 사용자 인증 및 권한 관리가 가능해진다.

 

3) CustomUserDetailsService

Spring Security에서 기본적으로 제공하는 UserDetailsService 인터페이스는

username을 사용하여 사용자를 검색하도록 설계되었다.

 

문제

아래 사진을 보면 알 수 있듯이 우리 프로젝트에서는 Email과 Password로 로그인을 진행하고,

회원가입때 사용자 이름을 받는데 username은 중복될 가능성이 있다.

우리 프로젝트에서는 username 대신 이메일을 사용하여 사용자를 검색하는 것이 더 적절하다.

 

해결

UserDetailService 구현을 이메일 기반으로 수정하면 된다.

 

먼저, 그럼 이메일이 고유해야 하기 때문에 엔티티를 수정해야 한다.

User.java

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

 

username 대신 email을 사용하기 때문에 추가해줘야 한다.

SecurityConfig.java

 .formLogin(form -> form
            .loginPage("/login")
            .usernameParameter("email")  // 추가
            .permitAll()
)

 

CustomUserDetailService.java

(import문 생략)

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> new UsernameNotFoundException("찾을 수 없는 이메일: " + email));
        return org.springframework.security.core.userdetails.User
                .withUsername(user.getEmail())
                .password(user.getPassword())
                .roles(user.getRole().name())
                .build();
    }
}

 

Q. 난 이메일을 이용해서 사용자를 찾는 데 왜 예외처리는 UsernameNotFoundException 일까?

 

A. 이 예외는 Spring Security에서 사용자 인증이 실패했을 때 던져지는 예외로,

이름이 UsernameNotFoundException이지만 실제로는 사용자를 찾지 못했을 때 발생하는 일반적인 예외로 사용된다.

 

 


이제 테스트로 설정이 올바르게 작동하는지 확인하면 된다.

근데 아직 로그인, 로그아웃 코드를 작업하지 않아서 시큐리티는 여기까지 ㅎㅎ

 

추가해야 할 것

  • Remember-Me 기능
  • OAuth2
  • JWT 인증

 

다음 포스팅

https://chaereemee.tistory.com/6

 

Spring Security 기록 (2)

전엔 JWT 인증 방법을 쓰지 않고 UserDetails에 대해서 기록했다.JWT를 추가한 전체 코드를 기록하려고 한다. 1) build.gradle필요한 Spring Security, JWT 관련 의존성을 추가한다.이 단계는 프로젝트의 모든

chaereemee.tistory.com