[#17] feat: 인증 절차 개발
- Spring Security 스펙 활용한 인증 절차 개발(Custom Filter 적용중) - UserDetailsService custom 구현체 적용 - Member Entity activated(활성화 여부) 칼럼 및 필드 추가
This commit is contained in:
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package io.beaniejoy.dongnecafe.common.entity
|
||||
|
||||
class SecurityUser(
|
||||
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package io.beaniejoy.dongnecafe.error
|
||||
|
||||
class MemberDeactivatedException(email: String): RuntimeException("Member[$email] is deactivated")
|
||||
@@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 '회원 변경날짜',
|
||||
|
||||
Reference in New Issue
Block a user