로그아웃 추가

서블릿 필터에서 Exception 발생시 처리
This commit is contained in:
Klaus 2023-08-02 15:46:02 +09:00
parent fff8037277
commit 16c5c5f6b6
4 changed files with 71 additions and 0 deletions

View File

@ -0,0 +1,26 @@
package kr.co.vividnext.sodalive.common
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.web.filter.OncePerRequestFilter
import javax.servlet.FilterChain
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
class ExceptionHandlerFilter(private val objectMapper: ObjectMapper) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
try {
filterChain.doFilter(request, response)
} catch (e: Exception) {
response.status = 401
response.contentType = "application/json"
response.characterEncoding = "UTF-8"
val json = objectMapper.writeValueAsString(ApiResponse.error("로그인 정보를 확인해주세요."))
response.writer.write(json)
}
}
}

View File

@ -1,5 +1,7 @@
package kr.co.vividnext.sodalive.configs
import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.vividnext.sodalive.common.ExceptionHandlerFilter
import kr.co.vividnext.sodalive.jwt.JwtAccessDeniedHandler
import kr.co.vividnext.sodalive.jwt.JwtAuthenticationEntryPoint
import kr.co.vividnext.sodalive.jwt.JwtFilter
@ -21,6 +23,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig(
private val objectMapper: ObjectMapper,
private val tokenProvider: TokenProvider,
private val accessDeniedHandler: JwtAccessDeniedHandler,
private val authenticationEntryPoint: JwtAuthenticationEntryPoint
@ -69,6 +72,7 @@ class SecurityConfig(
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter::class.java)
.addFilterBefore(ExceptionHandlerFilter(objectMapper), JwtFilter::class.java)
.build()
}
}

View File

@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RequestPart
@ -29,6 +30,16 @@ class MemberController(private val service: MemberService) {
@PostMapping("/login")
fun login(@RequestBody loginRequest: LoginRequest) = service.login(loginRequest)
@PostMapping("/logout")
fun logout(
@RequestHeader("Authorization") token: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(service.logout(token.removePrefix("Bearer "), member.id!!))
}
@GetMapping("/info")
fun getMemberInfo(
@RequestParam container: String?,

View File

@ -25,6 +25,7 @@ import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgreeRepository
import kr.co.vividnext.sodalive.member.stipulation.StipulationIds
import kr.co.vividnext.sodalive.member.stipulation.StipulationRepository
import kr.co.vividnext.sodalive.member.token.MemberTokenRepository
import kr.co.vividnext.sodalive.utils.generateFileName
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.repository.findByIdOrNull
@ -38,11 +39,14 @@ import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
@Service
@Transactional(readOnly = true)
class MemberService(
private val repository: MemberRepository,
private val tokenRepository: MemberTokenRepository,
private val stipulationRepository: StipulationRepository,
private val stipulationAgreeRepository: StipulationAgreeRepository,
private val creatorFollowingRepository: CreatorFollowingRepository,
@ -64,6 +68,9 @@ class MemberService(
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String
) : UserDetailsService {
private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf()
@Transactional
fun signUp(
profileImage: MultipartFile?,
@ -349,4 +356,27 @@ class MemberService(
}
.toList()
}
@Transactional
fun logout(token: String, memberId: Long) {
val member = repository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
member.pushToken = null
val lock = getOrCreateLock(memberId = memberId)
lock.write {
val memberToken = tokenRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
val memberTokenSet = memberToken.tokenList.toMutableSet()
memberTokenSet.remove(token)
memberToken.tokenList = memberTokenSet.toList()
tokenRepository.save(memberToken)
}
}
private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock {
return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() }
}
}