프로젝트 진행 중 기업회원 로그인 기능을 추가하는 과정에서 다양한 이슈가 발생했습니다. 이 문서는 그 트러블슈팅 과정과 해결 방법을 상세히 기록한 것입니다. 이를 통해 발생할 수 있는 잠재적인 문제를 사전에 파악하고, 유사한 상황에서 효과적으로 대응할 수 있도록 돕기 위해 작성되었습니다.
기존 시스템에서는 일반 사용자 로그인 기능만을 제공하고 있었으며, 기업회원 로그인 기능이 추가됨에 따라 다음과 같은 문제들이 발생했습니다:
1. 기업회원과 일반 사용자의 구분 문제: 기업회원과 일반 사용자를 어떻게 구분하여 인증하고, 각 사용자 유형에 맞는 권한을 어떻게 부여할 것인가.
2. Security Configuration 문제: Spring Security 설정에서 기업회원과 일반 사용자의 접근 권한을 구분하여 설정하는 방법.
3. UserDetailsService 문제: 기업회원과 일반 사용자의 정보를 어떻게 로드할 것인가.
먼저, 기업회원과 일반 사용자를 구분할 수 있는 데이터베이스 스키마 수정이 필요했습니다. 이를 위해 User
테이블과 별도로 CompanyUser
테이블을 생성하고, 각각의 사용자 정보를 저장할 수 있도록 구성했습니다.
기존의 UserDetailsImpl
클래스는 일반 사용자만을 고려한 구조였습니다. 이를 수정하여 기업회원의 정보를 포함하도록 변경했습니다.
package team9502.sinchulgwinong.global.security;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class UserDetailsImpl implements UserDetails {
private final String email;
private final String password;
private final Collection<? extends GrantedAuthority> authorities;
private final String userType; // 추가된 필드
public UserDetailsImpl(String email, String password, Collection<? extends GrantedAuthority> authorities, String userType) {
this.email = email;
this.password = password;
this.authorities = authorities;
this.userType = userType;
}
// 기존 메서드들...
public String getUserType() {
return userType;
}
}
UserDetailsServiceImpl
클래스를 수정하여 기업회원과 일반 사용자를 구분하여 로드할 수 있도록 했습니다.
package team9502.sinchulgwinong.global.security;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import team9502.sinchulgwinong.domain.user.entity.User;
import team9502.sinchulgwinong.domain.user.repository.UserRepository;
import team9502.sinchulgwinong.domain.companyuser.repository.CompanyUserRepository;
import java.util.Collections;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
private final CompanyUserRepository companyUserRepository;
public UserDetailsServiceImpl(UserRepository userRepository, CompanyUserRepository companyUserRepository) {
this.userRepository = userRepository;
this.companyUserRepository = companyUserRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email).orElse(null);
if (user != null) {
return new UserDetailsImpl(user.getEmail(), user.getPassword(), Collections.emptyList(), "USER");
}
CompanyUser companyUser = companyUserRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email + "으로 등록된 사용자를 찾을 수 없습니다."));
return new UserDetailsImpl(companyUser.getEmail(), companyUser.getPassword(), Collections.emptyList(), "COMPANY_USER");
}
}
Spring Security 설정을 통해 기업회원과 일반 사용자의 접근 권한을 구분했습니다.
package team9502.sinchulgwinong.global.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/home", "/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**").permitAll()
.requestMatchers("/auth/signup", "auth/login", "/auth/cp-signup", "/auth/cp-login").permitAll()
.requestMatchers("/business/status", "business/verify").permitAll()
.anyRequest().authenticated())
.formLogin(AbstractAuthenticationFilterConfigurer::disable)
.logout(LogoutConfigurer::permitAll);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
}
위의 변경사항을 적용한 후, 기업회원과 일반 사용자가 각각 로그인을 시도했을 때 올바르게 인증 및 권한 부여가 이루어지는 것을 확인했습니다. 각 사용자 유형에 맞는 페이지 접근이 정상적으로 이루어졌으며, 이를 통해 보안성이 향상되었습니다.