Skip to content

Commit

Permalink
Merge pull request #21 from DDD-Community/feature/POLABO-108
Browse files Browse the repository at this point in the history
fix(POLABO-108): mvp 2차 개발 4차 수정
  • Loading branch information
dldmsql authored Aug 14, 2024
2 parents 5b68f99 + 27d58c4 commit de7cd66
Show file tree
Hide file tree
Showing 17 changed files with 363 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.ddd.sonnypolabobe.domain.oauth.service.OauthService
import com.ddd.sonnypolabobe.domain.user.dto.UserDto
import com.ddd.sonnypolabobe.global.response.ApplicationResponse
import io.swagger.v3.oas.annotations.Operation
import org.springframework.http.HttpHeaders
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.bind.annotation.*

Expand All @@ -26,4 +25,15 @@ class OauthController(private val oauthService: OauthService) {
fun reIssue(
@RequestHeader(name = "Authorization", required = true) header: String
) = ApplicationResponse.ok(this.oauthService.reIssue(header))

@Operation(summary = "로그아웃", description = """
로그아웃을 진행합니다.
액세스 토큰을 헤더에 담아주세요.
""")
@PostMapping("/sign-out")
fun signOut() : ApplicationResponse<Nothing> {
val userId = SecurityContextHolder.getContext().authentication.principal as UserDto.Companion.Res
this.oauthService.signOut(userId.id)
return ApplicationResponse.ok()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.ddd.sonnypolabobe.domain.user.token.dto.UserTokenDto
import com.ddd.sonnypolabobe.domain.user.token.repository.UserTokenJooqRepository
import com.ddd.sonnypolabobe.global.security.JwtUtil
import com.ddd.sonnypolabobe.global.util.DateConverter.dateToLocalDateTime
import com.ddd.sonnypolabobe.logger
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

Expand Down Expand Up @@ -87,4 +88,8 @@ class OauthService(
this.userTokenRepository.updateByUserId(userToken)
return tokenRes
}

fun signOut(id: Long) {
this.userTokenRepository.deleteByUserId(id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ class UserController(
private val userService: UserService
) {

@Operation(summary = "닉네임 변경", description = """
닉네임을 변경합니다.
@Operation(summary = "프로필 변경", description = """
프로필 사항을 변경합니다.
유저가 가진 정보 중 변경한 값 + 변경하지 않은 값 모두 보내주세요.
보내는 값을 그대로 디비에 저장합니다.
""")
@PutMapping("/nickname")
fun updateNickname(@RequestBody request: UserDto.Companion.UpdateReq)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ class UserDto {

data class UpdateReq(
@JsonProperty("nickName")
val nickName : String
val nickName : String,
@JsonProperty("birthDt")
val birthDt : LocalDate?,
val gender : GenderType?
)

data class CreateTokenReq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ interface UserJooqRepository {
fun findById(id: Long): UserDto.Companion.Res?
fun findByEmail(email: String): UserDto.Companion.Res?
fun updateProfile(request: UserDto.Companion.UpdateReq, userId: Long)
fun deleteById(id: Long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import java.time.LocalDateTime
class UserJooqRepositoryImpl(private val dslContext: DSLContext) : UserJooqRepository{
override fun insertOne(request: UserDto.Companion.CreateReq): Long {
val jUser = User.USER
return this.dslContext.insertInto(jUser,
val result = this.dslContext.insertInto(jUser,
jUser.EMAIL,
jUser.NICK_NAME,
jUser.CREATED_AT,
Expand All @@ -27,9 +27,13 @@ class UserJooqRepositoryImpl(private val dslContext: DSLContext) : UserJooqRepos
1,
request.birthDt,
UserGender.valueOf(request.gender?.name ?: UserGender.NONE.name)
)
.returningResult(jUser.ID)
.fetchOne(0, Long::class.java) ?: 0
).execute()
if(result == 0) throw Exception("Failed to insert user")

return this.dslContext.select(jUser.ID)
.from(jUser)
.where(jUser.EMAIL.eq(request.email).and(jUser.YN.eq(1)))
.fetchOneInto(Long::class.java) ?: throw Exception("Failed to get user id")
}

override fun findById(id: Long): UserDto.Companion.Res? {
Expand All @@ -53,7 +57,7 @@ class UserJooqRepositoryImpl(private val dslContext: DSLContext) : UserJooqRepos
override fun findByEmail(email: String): UserDto.Companion.Res? {
val jUser = User.USER
val record = this.dslContext.selectFrom(jUser)
.where(jUser.EMAIL.eq(email))
.where(jUser.EMAIL.eq(email).and(jUser.YN.eq(1)))
.fetchOne()

return record?.let {
Expand All @@ -72,8 +76,18 @@ class UserJooqRepositoryImpl(private val dslContext: DSLContext) : UserJooqRepos
val jUser = User.USER
this.dslContext.update(jUser)
.set(jUser.NICK_NAME, request.nickName)
.set(jUser.BIRTH_DT, request.birthDt)
.set(jUser.GENDER, UserGender.valueOf(request.gender?.name ?: UserGender.NONE.name))
.set(jUser.UPDATED_AT, DateConverter.convertToKst(LocalDateTime.now()))
.where(jUser.ID.eq(userId))
.execute()
}

override fun deleteById(id: Long) {
val jUser = User.USER
this.dslContext.update(jUser)
.set(jUser.YN, 0)
.where(jUser.ID.eq(id))
.execute()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class UserService(

fun withdraw(request: UserDto.Companion.WithdrawReq, id: Long) {
this.withdrawJooqRepository.insertOne(request, id)
this.userJooqRepository.deleteById(id)
}

fun checkExist(email: String): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ interface UserTokenJooqRepository {
fun insertOne(userToken: UserTokenDto)
fun findByRefreshToken(token: String): UserTokenDto?
fun updateByUserId(userToken: UserTokenDto)
fun deleteByUserId(userId: Long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,11 @@ class UserTokenJooqRepositoryImpl(private val dslContext: DSLContext) : UserToke
.where(jUserToken.USER_ID.eq(userToken.userId))
.execute()
}

override fun deleteByUserId(userId: Long) {
val jUserToken = UserToken.USER_TOKEN
this.dslContext.deleteFrom(jUserToken)
.where(jUserToken.USER_ID.eq(userId))
.execute()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ddd.sonnypolabobe.global.config

import com.ddd.sonnypolabobe.global.security.JwtAuthenticationFilter
import com.ddd.sonnypolabobe.global.security.JwtExceptionFilter
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
Expand All @@ -20,8 +21,24 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource
@EnableMethodSecurity
class SecurityConfig(
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
private val jwtExceptionFilter: JwtExceptionFilter
) {

companion object {
val ALLOW_URLS = listOf<String>(
"/api/v1/boards/{id}",
"/api/v1/boards/create-available",
"/api/v1/boards/total-count",
"/api/v1/file/**",
"/api/v1/oauth/sign-in",
"/api/v1/oauth/re-issue",
"/api/v1/user/check-exist",
"/health",
"/swagger-ui/**",
"/v3/api-docs/**",
"/api/v1/polaroids/{id}",
"/api/v1/boards/{boardId}/polaroids"
)
}
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
return http
Expand All @@ -37,20 +54,13 @@ class SecurityConfig(
sessionManagementConfig.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
.addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter::class.java)
.authorizeHttpRequests {
it.requestMatchers("/api/v1/boards/create-available").permitAll()
it.requestMatchers("/api/v1/boards/total-count").permitAll()
it.requestMatchers("/api/v1/file/**").permitAll()
it.requestMatchers("/api/v1/oauth/**", "/api/v1/user/check-exist").permitAll()
it.requestMatchers("/health", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
it.requestMatchers("/api/v1/boards/{id}", "/api/v1/polaroids/{id}", "/api/v1/boards/{boardId}/polaroids").permitAll()
it.requestMatchers(RequestMatcher { request ->
ALLOW_URLS.any { url -> AntPathRequestMatcher(url).matches(request) }
}).permitAll()
it.anyRequest().authenticated()
}
.exceptionHandling{
it.authenticationEntryPoint { _, response, _ ->
response.sendError(500)
}
}
.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ enum class CustomErrorCode(

// board
BOARD_CREATED_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "BOARD001", "보드 생성에 실패했습니다."),
BOARD_NOT_FOUND(HttpStatus.NOT_FOUND, "BOARD002", "보드를 찾을 수 없습니다."),

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ class GlobalExceptionHandler(
@ExceptionHandler(ApplicationException::class)
fun applicationException(ex: ApplicationException): ResponseEntity<ApplicationResponse<Error>> {
logger().error("error : ${ex.error}")
this.discordApiClient.sendErrorTrace(
ex.error.code, ex.message,
ex.stackTrace.contentToString()
)
if(ex.error.status.is5xxServerError) {
this.discordApiClient.sendErrorTrace(
ex.error.status.toString(), ex.error.message,
ex.stackTrace.contentToString()
)
}
return ResponseEntity.status(ex.error.status).body(ApplicationResponse.error(ex.error))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class JwtAuthenticationFilter(
filterChain: FilterChain
) {
val authorizationHeader = request.getHeader("Authorization")
if(request.requestURI.contains("/api/v1/oauth")) {
if(request.requestURI.contains("/api/v1/oauth/re-issue")) {
filterChain.doFilter(request, response)
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.ddd.sonnypolabobe.global.security

import com.ddd.sonnypolabobe.global.exception.CustomErrorCode
import com.ddd.sonnypolabobe.global.util.DiscordApiClient
import com.ddd.sonnypolabobe.global.util.HttpLog
import com.ddd.sonnypolabobe.logger
import com.fasterxml.jackson.databind.ObjectMapper
import io.jsonwebtoken.JwtException
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter
import org.springframework.web.util.ContentCachingRequestWrapper
import org.springframework.web.util.ContentCachingResponseWrapper
import org.springframework.web.util.WebUtils
import java.io.UnsupportedEncodingException
import java.time.LocalDateTime
import java.util.*


@Component
class JwtExceptionFilter(
private val discordApiClient: DiscordApiClient
) : OncePerRequestFilter() {
private val excludedUrls = setOf("/actuator", "/swagger-ui", "/v3/api-docs")

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
try {
val requestWrapper: ContentCachingRequestWrapper =
ContentCachingRequestWrapper(request as HttpServletRequest)
val responseWrapper: ContentCachingResponseWrapper =
ContentCachingResponseWrapper(response as HttpServletResponse)
if (excludeLogging(request.requestURI)) {
filterChain.doFilter(request, response)
} else {
val startedAt = System.currentTimeMillis()
filterChain.doFilter(requestWrapper, responseWrapper)
val endedAt = System.currentTimeMillis()
logger().info(
"\n" +
"[REQUEST] ${request.method} - ${request.requestURI} ${responseWrapper.status} - ${(endedAt - startedAt) / 10000.0} \n" +
"Headers : ${getHeaders(request)} \n" +
"Parameters : ${getRequestParams(request)} \n" +
"Request body : ${getRequestBody(requestWrapper)} \n" +
"Response body : ${getResponseBody(responseWrapper)}"
)
if (responseWrapper.status >= 400 && getResponseBody(responseWrapper).contains(
CustomErrorCode.INTERNAL_SERVER_EXCEPTION.message
)
) {
this.discordApiClient.sendErrorLog(
HttpLog(
request.method,
request.requestURI,
responseWrapper.status,
(endedAt - startedAt) / 10000.0,
getHeaders(request),
getRequestParams(request),
getRequestBody(requestWrapper),
getResponseBody(responseWrapper)
)
)
}
}
} catch (e: JwtException) {
response.contentType = "application/json;charset=UTF-8"
response.status = HttpStatus.UNAUTHORIZED.value()
response.characterEncoding = "utf-8"

val mapper = ObjectMapper()
val errorJson = mapper.createObjectNode()
errorJson.put("message", e.message)
response.writer.write(mapper.writeValueAsString(errorJson))
}
}

private fun excludeLogging(requestURI: String): Boolean {
return excludedUrls.any { requestURI.startsWith(it) }
}

private fun getResponseBody(response: ContentCachingResponseWrapper): String {
var payload: String? = null
response.characterEncoding = "utf-8"
val wrapper =
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper::class.java)
if (wrapper != null) {
val buf = wrapper.contentAsByteArray
if (buf.isNotEmpty()) {
payload = String(buf, 0, buf.size, charset(wrapper.characterEncoding))
wrapper.copyBodyToResponse()
}
}
return payload ?: " - "
}

private fun getRequestBody(request: ContentCachingRequestWrapper): String {
request.characterEncoding = "utf-8"
val wrapper = WebUtils.getNativeRequest<ContentCachingRequestWrapper>(
request,
ContentCachingRequestWrapper::class.java
)
if (wrapper != null) {
val buf = wrapper.contentAsByteArray
if (buf.isNotEmpty()) {
return try {
String(buf, 0, buf.size, charset(wrapper.characterEncoding))
} catch (e: UnsupportedEncodingException) {
" - "
}
}
}
return " - "
}

private fun getRequestParams(request: HttpServletRequest): Map<String, String> {
val parameterMap: MutableMap<String, String> = HashMap()
request.characterEncoding = "utf-8"
val parameterArray: Enumeration<*> = request.parameterNames

while (parameterArray.hasMoreElements()) {
val parameterName = parameterArray.nextElement() as String
parameterMap[parameterName] = request.getParameter(parameterName)
}

return parameterMap
}

private fun getHeaders(request: HttpServletRequest): Map<String, String> {
val headerMap: MutableMap<String, String> = HashMap()

val headerArray: Enumeration<*> = request.headerNames
while (headerArray.hasMoreElements()) {
val headerName = headerArray.nextElement() as String
headerMap[headerName] = request.getHeader(headerName)
}
return headerMap
}
}
Loading

0 comments on commit de7cd66

Please sign in to comment.