Skip to content

Commit

Permalink
Jwt, refresh API 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
hhhello0507 committed Jan 12, 2025
1 parent 20ed3ad commit 4bd1ad6
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 4 deletions.
5 changes: 5 additions & 0 deletions src/main/kotlin/com/ohayo/moyamoya/api/user/UserApi.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.ohayo.moyamoya.api.user

import com.ohayo.moyamoya.api.user.value.RefreshReq
import com.ohayo.moyamoya.api.user.value.SignUpRequest
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
Expand All @@ -17,4 +19,7 @@ class UserApi(

@GetMapping("exists")
fun exists(@RequestParam tel: String) = userService.exists(tel)

@PostMapping("refresh")
fun refresh(@RequestBody req: RefreshReq) = userService.refresh(req)
}
19 changes: 18 additions & 1 deletion src/main/kotlin/com/ohayo/moyamoya/api/user/UserService.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package com.ohayo.moyamoya.api.user

import com.ohayo.moyamoya.api.user.value.RefreshReq
import com.ohayo.moyamoya.api.user.value.SignUpRequest
import com.ohayo.moyamoya.core.*
import com.ohayo.moyamoya.core.extension.findByIdSafety
import com.ohayo.moyamoya.global.CustomException
import com.ohayo.moyamoya.infra.token.JwtClient
import com.ohayo.moyamoya.infra.token.JwtPayloadKey
import com.ohayo.moyamoya.infra.token.Token
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service

@Service
class UserService(
private val userRepository: UserRepository,
private val schoolRepository: SchoolRepository
private val schoolRepository: SchoolRepository,
private val jwtClient: JwtClient,
) {
fun signUp(req: SignUpRequest): Token {
if (userRepository.existsByTel(req.tel)) throw CustomException(HttpStatus.BAD_REQUEST, "이미 가입된 계정")
Expand All @@ -38,4 +42,17 @@ class UserService(
}

fun exists(tel: String) = userRepository.existsByTel(tel)



fun refresh(req: RefreshReq): Token {
jwtClient.parseToken(req.refreshToken)

val user = run {
val tel = jwtClient.payload(JwtPayloadKey.TEL, req.refreshToken)
userRepository.findByTelSafety(tel)
}

return jwtClient.generate(user)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.ohayo.moyamoya.api.user.value

data class RefreshReq(
val refreshToken: String,
)
11 changes: 9 additions & 2 deletions src/main/kotlin/com/ohayo/moyamoya/core/UserRepository.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package com.ohayo.moyamoya.core

import com.ohayo.moyamoya.global.CustomException
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Repository

@Repository
interface UserRepository: JpaRepository<UserEntity, Int> {
interface UserRepository : JpaRepository<UserEntity, Int> {
fun existsByTel(tel: String): Boolean
}

fun findByTel(tel: String): UserEntity?
}

fun UserRepository.findByTelSafety(tel: String) =
findByTel(tel) ?: throw CustomException(HttpStatus.NOT_FOUND, "유저를 찾을 수 없습니다")
46 changes: 46 additions & 0 deletions src/main/kotlin/com/ohayo/moyamoya/global/LogInterceptor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.ohayo.moyamoya.global

import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import mu.KLogger
import org.springframework.stereotype.Component
import org.springframework.web.servlet.HandlerInterceptor
import org.springframework.web.servlet.ModelAndView


@Component
class LogInterceptor(
private val logger: KLogger
) : HandlerInterceptor {
@Throws(Exception::class)
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
logger.info("✅ request url - ${request.requestURI}")
logger.info("✅ request method - ${request.method}")
return super.preHandle(request, response, handler)
}

@Throws(Exception::class)
override fun postHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
modelAndView: ModelAndView?
) {
logger.info("✅ response status - ${response.status}")
super.postHandle(request, response, handler, modelAndView)
}

@Throws(Exception::class)
override fun afterCompletion(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
ex: Exception?
) {
super.afterCompletion(request, response, handler, ex)
}
}
8 changes: 7 additions & 1 deletion src/main/kotlin/com/ohayo/moyamoya/global/SecurityConfig.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ohayo.moyamoya.global

import com.ohayo.moyamoya.global.jwt.JwtAuthenticationFilter
import com.ohayo.moyamoya.global.jwt.JwtExceptionFilter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
Expand All @@ -17,6 +19,8 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource
@Configuration
@EnableWebSecurity
class SecurityConfig(
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
private val jwtExceptionFilter: JwtExceptionFilter,
private val httpExceptionFilter: HttpExceptionFilter,
private val sender: ErrorResponseSender
) {
Expand All @@ -41,7 +45,9 @@ class SecurityConfig(
).permitAll()
.anyRequest().authenticated()
}
.addFilterBefore(httpExceptionFilter, UsernamePasswordAuthenticationFilter::class.java)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
.addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter::class.java)
.addFilterBefore(httpExceptionFilter, JwtExceptionFilter::class.java)
.exceptionHandling {
it.authenticationEntryPoint { _, response, _ -> sender.send(response, HttpStatus.UNAUTHORIZED) }
it.accessDeniedHandler { _, response, _ -> sender.send(response, HttpStatus.FORBIDDEN) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.ohayo.moyamoya.global.jwt

import com.ohayo.moyamoya.infra.token.JwtClient
import com.ohayo.moyamoya.infra.token.JwtPayloadKey
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter

@Component
class JwtAuthenticationFilter(
private val jwtUtils: JwtClient,
private val userDetailsService: UserDetailsService
) : OncePerRequestFilter() {

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val token = TokenExtractor.extract(request)
if (token != null) {
jwtUtils.parseToken(token)
setAuthentication(token)
}

doFilter(request, response, filterChain)
}

private fun setAuthentication(token: String) {
val tel = jwtUtils.payload(JwtPayloadKey.TEL, token)
val details = userDetailsService.loadUserByUsername(tel)
SecurityContextHolder.getContext().authentication =
UsernamePasswordAuthenticationToken(details, null, details.authorities)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ohayo.moyamoya.global.jwt
import com.ohayo.moyamoya.global.CustomException
import com.ohayo.moyamoya.global.ErrorResponseSender
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

@Component
class JwtExceptionFilter(
private val sender: ErrorResponseSender
) : OncePerRequestFilter() {

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
try {
filterChain.doFilter(request, response)
} catch (exception: CustomException) {
sender.send(response, exception)
} catch (exception: Exception) {
sender.send(response, status = HttpStatus.INTERNAL_SERVER_ERROR)
}
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/com/ohayo/moyamoya/global/jwt/JwtUserDetails.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.ohayo.moyamoya.global.jwt

import com.ohayo.moyamoya.core.UserEntity
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.UserDetails

class JwtUserDetails(
val user: UserEntity
) : UserDetails {
override fun getAuthorities() = listOf(GrantedAuthority { user.userRole.name })
override fun getPassword() = user.password
override fun getUsername() = user.tel
override fun isAccountNonExpired() = true // 계정이 만료되지 않았는지
override fun isAccountNonLocked() = true // 계정이 잠기지 않았는지
override fun isCredentialsNonExpired() = true // 비밀번호가 만료되지 않았는지
override fun isEnabled() = true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.ohayo.moyamoya.global.jwt
import com.ohayo.moyamoya.core.UserRepository
import com.ohayo.moyamoya.core.findByTelSafety
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.stereotype.Service

@Service
class JwtUserDetailsService(
private val userRepository: UserRepository,
) : UserDetailsService {
override fun loadUserByUsername(username: String) =
JwtUserDetails(userRepository.findByTelSafety(username))
}
18 changes: 18 additions & 0 deletions src/main/kotlin/com/ohayo/moyamoya/global/jwt/TokenExtractor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ohayo.moyamoya.global.jwt
import com.ohayo.moyamoya.global.CustomException
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.HttpStatus

object TokenExtractor {
fun extract(request: HttpServletRequest): String? {
val authorization = request.getHeader("Authorization") ?: return null
return token(authorization)
}

private fun token(authorization: String): String {
if (!authorization.startsWith("Bearer ")) {
throw CustomException(HttpStatus.UNAUTHORIZED, "token does not start with Bearer")
}
return authorization.removePrefix("Bearer ")
}
}

0 comments on commit 4bd1ad6

Please sign in to comment.