From e6aafebb53226acccb8a50be50ef223c49183e87 Mon Sep 17 00:00:00 2001 From: beaniejoy Date: Sat, 29 Oct 2022 11:49:20 +0900 Subject: [PATCH] =?UTF-8?q?[#17]=20feat:=20=EC=9D=B8=EC=A6=9D=20=EC=A0=88?= =?UTF-8?q?=EC=B0=A8=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Spring Security 스펙 활용한 인증 절차 개발(Custom Filter 적용중) - UserDetailsService custom 구현체 적용 - Member Entity activated(활성화 여부) 칼럼 및 필드 추가 --- .../common/config/SecurityConfig.kt | 19 ++++++- .../dongnecafe/common/entity/SecurityUser.kt | 5 ++ .../security/ApiAuthenticationFilter.kt | 53 +++++++++++++++++++ .../security/ApiAuthenticationProvider.kt | 31 +++++++++++ .../dongnecafe/controller/AuthController.kt | 15 +++--- .../error/MemberNotActivatedException.kt | 3 ++ .../service/UserDetailsServiceImpl.kt | 38 +++++++++++++ .../dongnecafe/domain/member/entity/Member.kt | 4 ++ .../db/migration/V008__Create_member.sql | 1 + 9 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/entity/SecurityUser.kt create mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt create mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt create mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberNotActivatedException.kt create mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/UserDetailsServiceImpl.kt diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt index eaed309..2d94751 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt @@ -1,12 +1,16 @@ package io.beaniejoy.dongnecafe.common.config +import io.beaniejoy.dongnecafe.common.security.ApiAuthenticationFilter import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.crypto.factory.PasswordEncoderFactories import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter +import org.springframework.security.web.util.matcher.AntPathRequestMatcher @Configuration @EnableWebSecurity @@ -19,9 +23,22 @@ class SecurityConfig { @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain { return http - .cors().disable() .csrf().disable() .formLogin().disable() + + .authorizeRequests() + .antMatchers("/auth/members/sign-up").permitAll() + .anyRequest().authenticated() + .and() + .addFilterBefore(apiAuthenticationFilter(), UsernamePasswordAuthenticationFilter::class.java) + .build() } + + @Bean + fun apiAuthenticationFilter(): ApiAuthenticationFilter { + return ApiAuthenticationFilter( + AntPathRequestMatcher("/auth/authenticate", HttpMethod.POST.name) + ) + } } \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/entity/SecurityUser.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/entity/SecurityUser.kt new file mode 100644 index 0000000..459f0cd --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/entity/SecurityUser.kt @@ -0,0 +1,5 @@ +package io.beaniejoy.dongnecafe.common.entity + +class SecurityUser( + +) \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt new file mode 100644 index 0000000..24a6bca --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt @@ -0,0 +1,53 @@ +package io.beaniejoy.dongnecafe.common.security + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import io.beaniejoy.dongnecafe.domain.member.model.request.SignInRequest +import mu.KotlinLogging +import org.springframework.http.HttpMethod +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter +import org.springframework.security.web.util.matcher.AntPathRequestMatcher +import org.springframework.util.StringUtils +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +class ApiAuthenticationFilter(requestMatcher: AntPathRequestMatcher) : + AbstractAuthenticationProcessingFilter(requestMatcher) { + + private val log = KotlinLogging.logger {} + + private val objectMapper = jacksonObjectMapper() + + override fun attemptAuthentication( + request: HttpServletRequest, + response: HttpServletResponse, + ): Authentication { + if (isPostMethod(request).not()) { + val errorMsg = "Authentication is not supported (only support for POST method)" + log.error { errorMsg } + throw IllegalStateException(errorMsg) + } + + val signInRequest = objectMapper.readValue(request.reader, SignInRequest::class.java) + + val token = signInRequest.let { + if (StringUtils.hasText(it.email).not() || StringUtils.hasText(it.password).not()) { + log.error { "Email(${it.email}) & Password are not empty" } + throw IllegalArgumentException("Email & Password are not empty!!") + } + + UsernamePasswordAuthenticationToken(it.email, it.password) + } + + return authenticationManager.authenticate(token) + } + + private fun isPostMethod(request: HttpServletRequest): Boolean { + if (request.method != HttpMethod.POST.name) { + return false + } + + return true + } +} \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt new file mode 100644 index 0000000..cbef10c --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt @@ -0,0 +1,31 @@ +package io.beaniejoy.dongnecafe.common.security + +import mu.KLogging +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.stereotype.Component + +@Component +class ApiAuthenticationProvider( + private val userDetailsService: UserDetailsService, + private val passwordEncoder: PasswordEncoder +) : AuthenticationProvider { + companion object: KLogging() + + override fun authenticate(authentication: Authentication): Authentication { + logger.info { "start authentication" } + + val email = authentication.name + val password = authentication.credentials as String? + + val user = userDetailsService.loadUserByUsername(email) + TODO("Not yet implemented") + } + + override fun supports(authentication: Class<*>): Boolean { + return UsernamePasswordAuthenticationToken::class.java.isAssignableFrom(authentication) + } +} \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt index 009c484..29ffd60 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt @@ -13,12 +13,11 @@ import org.springframework.web.bind.annotation.RestController class AuthController( private val authenticationManagerBuilder: AuthenticationManagerBuilder ) { - - @PostMapping("/authenticate") - fun signIn(@RequestBody signInRequest: SignInRequest) { - val authenticationToken = - UsernamePasswordAuthenticationToken(signInRequest.email, signInRequest.password) - - val authenticate = authenticationManagerBuilder.`object`.authenticate(authenticationToken) - } +// @PostMapping("/authenticate") +// fun signIn(@RequestBody signInRequest: SignInRequest) { +// val authenticationToken = +// UsernamePasswordAuthenticationToken(signInRequest.email, signInRequest.password) +// +// val authenticate = authenticationManagerBuilder.`object`.authenticate(authenticationToken) +// } } \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberNotActivatedException.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberNotActivatedException.kt new file mode 100644 index 0000000..97bedd5 --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberNotActivatedException.kt @@ -0,0 +1,3 @@ +package io.beaniejoy.dongnecafe.error + +class MemberDeactivatedException(email: String): RuntimeException("Member[$email] is deactivated") \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/UserDetailsServiceImpl.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/UserDetailsServiceImpl.kt new file mode 100644 index 0000000..ac293f9 --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/UserDetailsServiceImpl.kt @@ -0,0 +1,38 @@ +package io.beaniejoy.dongnecafe.service + +import io.beaniejoy.dongnecafe.domain.member.entity.Member +import io.beaniejoy.dongnecafe.domain.member.repository.MemberRepository +import io.beaniejoy.dongnecafe.error.MemberDeactivatedException +import mu.KLogging +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.userdetails.User +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.Component + +@Component("userDetailsService") +class UserDetailsServiceImpl( + private val memberRepository: MemberRepository +) : UserDetailsService { + companion object: KLogging() + + override fun loadUserByUsername(email: String): UserDetails { + return memberRepository.findByEmail(email)?.let { + logger.info { "[LOAD MEMBER] email: ${it.email}, role: ${it.roleType}, activated: ${it.activated}" } + createSecurityUser(it) + } ?: throw UsernameNotFoundException(email) + } + + private fun createSecurityUser(member: Member): User { + if (member.activated.not()) { + throw MemberDeactivatedException(member.email) + } + + return User( + /* username = */ member.email, + /* password = */ member.password, + /* authorities = */ listOf(SimpleGrantedAuthority(member.roleType.name)) + ) + } +} \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/domain/member/entity/Member.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/domain/member/entity/Member.kt index 4c5c25d..4c4f235 100644 --- a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/domain/member/entity/Member.kt +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/domain/member/entity/Member.kt @@ -38,6 +38,10 @@ class Member( var roleType: RoleType = RoleType.ROLE_USER protected set + @Column(name = "activated", nullable = false) + var activated: Boolean = true + protected set + companion object { fun createMember( email: String, diff --git a/dongne-service-api/src/main/resources/db/migration/V008__Create_member.sql b/dongne-service-api/src/main/resources/db/migration/V008__Create_member.sql index 78a0650..e3fad2f 100644 --- a/dongne-service-api/src/main/resources/db/migration/V008__Create_member.sql +++ b/dongne-service-api/src/main/resources/db/migration/V008__Create_member.sql @@ -5,6 +5,7 @@ CREATE TABLE `member` ( `address` varchar(100) NOT NULL COMMENT '회원 주소', `phone_number` varchar(11) NOT NULL COMMENT '회원 전화번호', `role_type` varchar(20) COMMENT '회원 권한', + `activated` tinyint(1) NOT NULL COMMENT '계정 활성화 여부', `created_at` datetime NOT NULL COMMENT '회원 등록날짜', `created_by` varchar(20) NOT NULL COMMENT '회원 등록자', `updated_at` datetime NULL COMMENT '회원 변경날짜',