스프링 시큐리티는 스프링 기반의 애플리케이션 보안을 담당하는 프레임워크입니다. 이를 이용하여 로그인 기능을 구현할 수 있습니다.
로그인 기능을 구현하기 위해서는 스프링 시큐리티에서 제공하는 AuthenticationManager와 UserDetailsService를 구현해야 합니다. AuthenticationManager는 인증(Authentication)을 담당하고, UserDetailsService는 사용자 정보를 가져오는 역할을 합니다.
또한, 로그인 페이지와 로그인 성공/실패 시의 처리를 위한 컨트롤러와 뷰를 구현해야 합니다. 로그인 폼에서 입력한 아이디와 비밀번호를 AuthenticationManager에 전달하여 인증을 수행하고, 인증 결과에 따라 로그인 성공/실패를 처리합니다.
먼저 스프링시큐리티의 설정 코드를 작성해줍니다.
@Configuration
public class SecurityConfig {
@Autowired
private PrincipalDetailService principalDetailService;
@Bean
public BCryptPasswordEncoder encodePWD(){
return new BCryptPasswordEncoder();
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD());
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http.csrf().disable() // csrf 토큰 비활성화 (테스트시 걸어두는 게 좋음)
.authorizeRequests()
.antMatchers("/", "/auth/**", "/js/**", "/css/**", "/image/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/auth/loginForm")
.loginProcessingUrl("/auth/loginProc")
.defaultSuccessUrl("/");
return http.build();
}
}
해당 서비스의 회원가입 로직에서 비밀번호는 BCryptPasswordEncoder
에 의해 해시 암호화 되므로 해당 객체를 빈에 등록해줍니다.
SecurityFilterChain
을 사용하여 필요한 권한을 설정할 수 있습니다.
위의 예제는 모든 사용자(로그인 하지 않은 사용자 포함)가 "/", "/auth/**", "/js/**", "/css/**", "/image/**"
의 접근을 가능하게 하며 스프링 시큐리티 기반 login을 "/auth/loginForm"
경로에서 진행되게 합니다. 로그인 Form Action Url은 "/auth/loginProc"
경로가 됩니다.
@Service
public class PrincipalDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User principal = userRepository.findByUsername(username)
.orElseThrow(()->{
return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. " + username);
});
return new PrincipalDetail(principal); // 시큐리티 세션에 유저 정보가 저장이 됨
}
}
UserDetailsService
인터페이스를 구현하는 서비스입니다. 스프링 시큐리티에서 사용자 인증 처리를 위해 UserDetailsService
를 이용합니다. loadUserByUsername
메서드를 오버라이딩하여 사용자 정보를 가져옵니다.
userRepository.findByUsername(username)
메서드를 이용해 해당 username을 가진 사용자 정보를 가져옵니다. 사용자 정보가 존재하지 않으면 UsernameNotFoundException
을 던집니다.
PrincipalDetail
객체를 생성하여 사용자 정보를 인자로 전달합니다. PrincipalDetail
객체는 UserDetails
인터페이스를 구현한 클래스로, 시큐리티 세션에 저장됩니다.