로그아웃 추가
서블릿 필터에서 Exception 발생시 처리
This commit is contained in:
		| @@ -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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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?, | ||||
|   | ||||
| @@ -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() } | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user