Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
… into feat/YS-30
  • Loading branch information
chock-cho committed Dec 29, 2024
2 parents 56683ae + 32a94f4 commit d328163
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cd-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
docker images
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
- name: Deploy to Prod WAS Server
- name: Deploy to Dev WAS Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.WAS_HOST }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.dobby.backend.domain.exception

open class AuthorizationException(
errorCode: ErrorCode,
) : DomainException(errorCode)

class PermissionDeniedException : AuthorizationException(ErrorCode.PERMISSION_DENIED)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.dobby.backend.domain.exception

open class DefaultException(
errorCode: ErrorCode,
) : DomainException(errorCode)

class UnknownErrorException : DefaultException(ErrorCode.UNKNOWN_SERVER_ERROR)
class UnauthorizedException : DefaultException(ErrorCode.UNAUTHORIZED)
class InvalidInputException : DefaultException(ErrorCode.INVALID_INPUT)
class UnknownResourceException : DefaultException(ErrorCode.UNKNOWN_RESOURCE)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ enum class ErrorCode(
TOKEN_EXPIRED("AU0003", "Authentication token has expired.", HttpStatus.UNAUTHORIZED),
INVALID_TOKEN_TYPE("AU0004", "Invalid token type", HttpStatus.UNAUTHORIZED),

/**
* Authorization error codes
*/
PERMISSION_DENIED("AZ0001", "Permission denied", HttpStatus.FORBIDDEN),

/**
* Member error codes
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package com.dobby.backend.infrastructure.gateway

import com.dobby.backend.domain.gateway.TokenGateway
import com.dobby.backend.domain.model.Member
import com.dobby.backend.infrastructure.token.JWTTokenProvider
import com.dobby.backend.infrastructure.token.JwtTokenProvider
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.stereotype.Component

@Component
class TokenGatewayImpl(
private val tokenProvider: JWTTokenProvider,
private val tokenProvider: JwtTokenProvider,
) : TokenGateway {
override fun generateAccessToken(member: Member): String {
val authentication = UsernamePasswordAuthenticationToken(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec

@Component
class JWTTokenProvider(
class JwtTokenProvider(
private val tokenProperties: TokenProperties
) {
private final val signKey: SecretKey = SecretKeySpec(tokenProperties.secretKey.toByteArray(), "AES")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.dobby.backend.presentation.api.config

import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration
class WebConfig : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedMethods("*")
.allowedOrigins(
"http://localhost:3000",
"http://localhost:3300",
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.dobby.backend.presentation.api.config

import com.dobby.backend.domain.exception.AuthenticationException
import com.dobby.backend.domain.exception.AuthorizationException
import com.dobby.backend.domain.exception.DomainException
import com.dobby.backend.domain.exception.PermissionDeniedException
import com.dobby.backend.presentation.api.dto.payload.ApiResponse
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.access.AccessDeniedException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice

@RestControllerAdvice
class WebExceptionHandler {

@ExceptionHandler(value = [DomainException::class])
fun handleDomainException(
e: DomainException,
request: HttpServletRequest,
): ResponseEntity<ApiResponse<Unit>> {
return ResponseEntity
.badRequest()
.body(e.toErrorResponse())
}

@ExceptionHandler(value = [AuthenticationException::class])
fun handleAuthenticationException(
exception: AuthenticationException,
request: HttpServletRequest,
): ResponseEntity<ApiResponse<Unit>> {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(exception.toErrorResponse())
}

@ExceptionHandler(value = [AuthorizationException::class])
fun handleAuthorizationException(
exception: AuthorizationException,
): ResponseEntity<ApiResponse<Unit>> {
return ResponseEntity
.status(HttpStatus.FORBIDDEN)
.body(exception.toErrorResponse())
}

@ExceptionHandler(value = [AccessDeniedException::class])
fun handleAccessDeniedException() = handleAuthorizationException(PermissionDeniedException())

private fun DomainException.toErrorResponse(): ApiResponse<Unit> {
return ApiResponse.onFailure(
code = this.code,
message = this.errorMessage
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.dobby.backend.presentation.api.config

import com.dobby.backend.domain.exception.PermissionDeniedException
import com.dobby.backend.domain.exception.UnauthorizedException
import com.dobby.backend.infrastructure.token.JwtTokenProvider
import com.dobby.backend.presentation.api.config.filter.JwtAuthenticationFilter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.web.servlet.HandlerExceptionResolver

@Configuration
@EnableMethodSecurity
class WebSecurityConfig {
@Bean
@Order(0)
fun securityFilterChain(
httpSecurity: HttpSecurity,
jwtTokenProvider: JwtTokenProvider,
handlerExceptionResolver: HandlerExceptionResolver,
): SecurityFilterChain = httpSecurity
.securityMatcher( "/v1/members/**")
.csrf { it.disable() }
.cors(Customizer.withDefaults())
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.authorizeHttpRequests {
it.anyRequest().authenticated()
}
.addFilterBefore(
JwtAuthenticationFilter(jwtTokenProvider, handlerExceptionResolver),
UsernamePasswordAuthenticationFilter::class.java
)
.exceptionHandling {
it.accessDeniedHandler { request, response, exception ->
handlerExceptionResolver.resolveException(request, response, null, PermissionDeniedException())
}.authenticationEntryPoint { request, response, authException ->
handlerExceptionResolver.resolveException(request, response, null, UnauthorizedException())
}
}
.build()

@Bean
@Order(1)
fun authSecurityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain = httpSecurity
.securityMatcher("/v1/auth/**")
.csrf { it.disable() }
.cors(Customizer.withDefaults())
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.authorizeHttpRequests {
it.anyRequest().permitAll()
}
.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.dobby.backend.presentation.api.config.filter

import com.dobby.backend.domain.exception.AuthenticationTokenNotFoundException
import com.dobby.backend.domain.exception.AuthenticationTokenNotValidException
import com.dobby.backend.infrastructure.token.JwtTokenProvider
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.filter.OncePerRequestFilter
import org.springframework.web.servlet.HandlerExceptionResolver

class JwtAuthenticationFilter(
private val jwtTokenProvider: JwtTokenProvider,
private val handlerExceptionResolver: HandlerExceptionResolver,
) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain,
) {
try {
val authenticationHeader =
request.getHeader("Authorization") ?: throw AuthenticationTokenNotFoundException()
val accessToken = if (authenticationHeader.startsWith("Bearer "))
authenticationHeader.substring(7)
else throw AuthenticationTokenNotValidException()

val authentication = jwtTokenProvider.parseAuthentication(accessToken)
SecurityContextHolder.getContext().authentication = authentication
return filterChain.doFilter(request, response)
} catch (e: Exception) {
handlerExceptionResolver.resolveException(request, response, null, e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlin.test.assertNotNull
class JwtTokenProviderTest : BehaviorSpec() {

@Autowired
lateinit var jwtTokenProvider: JWTTokenProvider
lateinit var jwtTokenProvider: JwtTokenProvider

init {
given("회원 정보가 주어지고") {
Expand Down

0 comments on commit d328163

Please sign in to comment.