Spring Security 개념과 처리 과정 (2) with Spring Boot 3, Kotlin

eunji·2024년 4월 18일
0

Spring

목록 보기
6/7
post-custom-banner

Spring Security 설정

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()
    }
}

Spring Security 인증 처리 과정

1. 클라이언트(유저)가 로그인을 시도한다.

2. AuthenticationFilter가 요청을 처리한다.

Servlet Filter에 의해 Security Filter 로 작업이 위임되고, 여러 Security Filter 중 UsernamePasswordAuthenticationFilter 에서 인증을 처리한다.
작성자는 UsernamePasswordAuthenticationFilter 처리 과정을 생략하고 로그인 정보를 받아서 UsernamePasswordAuthenticationToken 토큰을 발급한다.

// 사용자 인증을 위한 Authentication 토큰 객체 (인증 전)
val authenticationToken = UsernamePasswordAuthenticationToken(loginMemberRequestDto.loginId, foundMember.password)

3. AuthenticationManager 에게 인증 객체 전달

AuthenticationManager 는 인증을 담당하므로 인증 객체를 전달하여 2번에서 발급한 토큰이 올바른 유저인지 확인한다.

4. 인증을 위해 AuthenticationProvider 에게 인증 객체 전달

// authenticationManagerBuilder를 통해 인증된 Authentication 객체를 반환
// 인증 실패 시, 에러 발생
val authentication = authenticationManagerBuilder.`object`.authenticate(authenticationToken)

5. 전달받은 인증 객체의 정보를 UserDetailsService에 전달

6. UserDetails 구현 객체 생성

@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}") }
        )

}

7. UserDetails 객체를 AuthenticationProvider에 전달

8. ProviderManager에게 권한을 담은 검증된 인증 객체 전달

AuthenticationProvider은 전달받은 UserDetails를 인증에 성공하면 ProviderManager에게 권한을 담은 검증된 인증 객체를 전달한다.

9. 검증된 인증 객체를 AuthenticationFilter 에게 전달

ProviderManager가 AuthenticationFilter에 전달한다.

// authenticationManagerBuilder를 통해 인증된 Authentication 객체를 반환
// 인증 실패 시, 에러 발생
val authentication = authenticationManagerBuilder.`object`.authenticate(authenticationToken)

10. 검증된 인증 객체를 SecurityContextHolder의 SecurityContext에 저장

// 인증된 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
    }

출처

profile
tech blog
post-custom-banner

0개의 댓글