spring security dependency 추가
dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
}
Gradle 새로고침 후, 프로젝트를 실행하면 로그인 페이지가 나타나며, 로그에 패스워드가 출력된다. Spring Security는 보안에 가장 기본적인 기능인 아이디/패스워드 인증 화면을 지원한다.
아이디는 user, 비밀번호는 로그에 출력된 패스워드를 입력하면 된다.
Using generated security password: 67788fa8-0989-44c4-b94b-c42fbaf23825
설정을 위해, SecurityConfig 클래스를 추가한다. 로그인 페이지가 나오지 않도록 모든 요청을 허용하기 위해 anyRequest().permitAll() 를 추가한다.
@Configuration
@EnableMethodSecurity
@EnableWebSecurity
class SecurityConfig {
@Bean
@Throws(Exception::class)
fun filterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain? {
http.authorizeHttpRequests { it.anyRequest().permitAll() }
return http.build()
}
}
Servlet Filter에 의해 Security Filter 로 작업이 위임되고, 여러 Security Filter 중 UsernamePasswordAuthenticationFilter 에서 인증을 처리한다.
작성자는 UsernamePasswordAuthenticationFilter 처리 과정을 생략하고 로그인 정보를 받아서 UsernamePasswordAuthenticationToken 토큰을 발급한다.
// 사용자 인증을 위한 Authentication 토큰 객체 (인증 전)
val authenticationToken = UsernamePasswordAuthenticationToken(loginMemberRequestDto.loginId, foundMember.password)
AuthenticationManager 는 인증을 담당하므로 인증 객체를 전달하여 2번에서 발급한 토큰이 올바른 유저인지 확인한다.
// authenticationManagerBuilder를 통해 인증된 Authentication 객체를 반환
// 인증 실패 시, 에러 발생
val authentication = authenticationManagerBuilder.`object`.authenticate(authenticationToken)
@Service
class CustomUserDetailService(
private val memberRepository: MemberRepository,
private val passwordEncoder: PasswordEncoder,
): UserDetailsService {
override fun loadUserByUsername(username: String): UserDetails {
return memberRepository.findMemberWithRoles(username)
?.let { memberToUserDetails(it) }
?: throw UsernameNotFoundException("해당하는 유저를 찾을 수 없습니다.")
}
private fun memberToUserDetails(member: Member): UserDetails =
User(
member.email,
passwordEncoder.encode(member.password),
member.memberRoleList.map { SimpleGrantedAuthority("ROLE_${it}") }
)
}
AuthenticationProvider은 전달받은 UserDetails를 인증에 성공하면 ProviderManager에게 권한을 담은 검증된 인증 객체를 전달한다.
ProviderManager가 AuthenticationFilter에 전달한다.
// authenticationManagerBuilder를 통해 인증된 Authentication 객체를 반환
// 인증 실패 시, 에러 발생
val authentication = authenticationManagerBuilder.`object`.authenticate(authenticationToken)
// 인증된 Authentication 객체를 SecurityContextHolder에 저장
SecurityContextHolder.getContext().authentication = authentication
fun signIn(loginMemberRequestDto: LoginMemberRequestDto): Authentication {
val foundMember = memberRepository.findMemberWithRoles(loginMemberRequestDto.loginId)
?: throw UsernameNotFoundException("회원 아이디(${loginMemberRequestDto.loginId})가 존재하지 않는 유저입니다.")
if (!passwordEncoder.matches(loginMemberRequestDto.password, foundMember.password)) {
throw UsernameNotFoundException("아이디 혹은 비밀번호를 확인하세요.")
}
// 사용자 인증을 위한 Authentication 토큰 객체 (인증 전)
val authenticationToken = UsernamePasswordAuthenticationToken(loginMemberRequestDto.loginId, foundMember.password)
// authenticationManagerBuilder를 통해 인증된 Authentication 객체를 반환
// 인증 실패 시, 에러 발생
val authentication = authenticationManagerBuilder.`object`.authenticate(authenticationToken)
// 인증된 Authentication 객체를 SecurityContextHolder에 저장
SecurityContextHolder.getContext().authentication = authentication
return authentication
}
출처