From 91196d3fb4992d7bcece68f918eea3371391fca1 Mon Sep 17 00:00:00 2001 From: kms Date: Sun, 1 Aug 2021 16:06:31 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EC=8A=A4=ED=94=84=EB=A7=81=20=EC=8B=9C?= =?UTF-8?q?=ED=81=90=EB=A6=AC=ED=8B=B0=EB=A5=BC=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=9C=20api=20=EC=82=AC=EC=9A=A9=20=EC=9D=B8=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 주요 내용 1. jws 검증 usecase를 추가한 테스트코드 작성 2. jws 검증을 위한 filter 추가 3. authenticationException handling을 위한 entrypoint 추가 4. filter 및 entrypoint 설정에 추가 Closes #66 --- .../app/api/user/loginUser/LoginUserApi.kt | 26 +++++++++ .../security/SecurityBeansDefinition.kt | 8 ++- .../appconfig/security/WebSecurityConfig.kt | 16 ++++-- .../authentication/JwsAuthenticationFilter.kt | 30 +++++++++++ .../authentication/UnauthorizedEntryPoint.kt | 18 +++++++ .../kotlin/kr/flab/wiki/app/utils/JwtUtils.kt | 45 ++++++++++++++-- .../LoginWithSpringSecurityAndJwtTest.kt | 54 +++++++++++++++++-- .../kr/flab/wiki/app/testlib/SpringApi.kt | 38 +++++++++++++ 8 files changed, 221 insertions(+), 14 deletions(-) create mode 100644 app-main/src/main/kotlin/kr/flab/wiki/app/components/authentication/JwsAuthenticationFilter.kt create mode 100644 app-main/src/main/kotlin/kr/flab/wiki/app/components/authentication/UnauthorizedEntryPoint.kt create mode 100644 app-main/src/test/kotlin/kr/flab/wiki/app/testlib/SpringApi.kt diff --git a/app-main/src/main/kotlin/kr/flab/wiki/app/api/user/loginUser/LoginUserApi.kt b/app-main/src/main/kotlin/kr/flab/wiki/app/api/user/loginUser/LoginUserApi.kt index 058e0a4..748987e 100644 --- a/app-main/src/main/kotlin/kr/flab/wiki/app/api/user/loginUser/LoginUserApi.kt +++ b/app-main/src/main/kotlin/kr/flab/wiki/app/api/user/loginUser/LoginUserApi.kt @@ -6,6 +6,7 @@ import kr.flab.wiki.app.components.authentication.LoginUserService import kr.flab.wiki.app.type.annotation.ApiHandler import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +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.RestController @@ -21,4 +22,29 @@ class LoginUserApi( ?: return ResponseEntity.status(HttpStatus.BAD_REQUEST).build() return ResponseEntity.ok().body(loginResponse) } + + @GetMapping("/test") + fun test(): ResponseEntity { + return ResponseEntity.ok().body("test") + } + + @GetMapping("/test1") + fun test1(): ResponseEntity { + return ResponseEntity.ok().body("test") + } + + @GetMapping("/test2") + fun test2(): ResponseEntity { + return ResponseEntity.ok().body("test") + } + + @GetMapping("/test3") + fun test3(): ResponseEntity { + return ResponseEntity.ok().body("test") + } + + @GetMapping("/test4") + fun test4(): ResponseEntity { + return ResponseEntity.ok().body("test") + } } diff --git a/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/SecurityBeansDefinition.kt b/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/SecurityBeansDefinition.kt index d1855c2..ba51bbb 100644 --- a/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/SecurityBeansDefinition.kt +++ b/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/SecurityBeansDefinition.kt @@ -3,6 +3,7 @@ package kr.flab.wiki.app.appconfig.security import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import kr.flab.wiki.app.components.authentication.AuthenticationProviderImpl +import kr.flab.wiki.app.components.authentication.JwsAuthenticationFilter import kr.flab.wiki.app.components.authentication.UserAuthentication import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -19,7 +20,7 @@ import org.springframework.security.crypto.password.PasswordEncoder class SecurityBeansDefinition { @Bean - fun objectMapper() : ObjectMapper { + fun objectMapper(): ObjectMapper { return jacksonObjectMapper() } @@ -39,4 +40,9 @@ class SecurityBeansDefinition { fun authenticationManager(webSecurityConfig: WebSecurityConfig): AuthenticationManager { return webSecurityConfig.authenticationManagerBean() } + + @Bean + fun jwsAuthenticationFilter(): JwsAuthenticationFilter { + return JwsAuthenticationFilter() + } } diff --git a/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/WebSecurityConfig.kt b/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/WebSecurityConfig.kt index e9792f5..b47c47e 100644 --- a/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/WebSecurityConfig.kt +++ b/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/WebSecurityConfig.kt @@ -1,15 +1,16 @@ package kr.flab.wiki.app.appconfig.security -import org.springframework.context.annotation.Bean +import kr.flab.wiki.app.components.authentication.JwsAuthenticationFilter +import kr.flab.wiki.app.components.authentication.UnauthorizedEntryPoint import org.springframework.context.annotation.Configuration import org.springframework.http.HttpMethod -import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.AuthenticationProvider import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter /** * 스프링 시큐리티의 설정을 담은 클래스입니다. @@ -19,7 +20,9 @@ import org.springframework.security.config.http.SessionCreationPolicy @Configuration @EnableWebSecurity class WebSecurityConfig( - private val authenticationProvider: AuthenticationProvider + private val authenticationProvider: AuthenticationProvider, + private val jwsAuthenticationFilter: JwsAuthenticationFilter, + private val unauthorizedEntryPoint: UnauthorizedEntryPoint ) : WebSecurityConfigurerAdapter() { /** * 스프링에서 기본 제공해주는 UserDetailsService 와 UserDetails 를 사용하지 않았습니다. @@ -48,11 +51,18 @@ class WebSecurityConfig( */ http.csrf() .disable() + .exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint) + .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(HttpMethod.POST, "/login").permitAll() .anyRequest().authenticated() + + http.addFilterBefore( + jwsAuthenticationFilter, + UsernamePasswordAuthenticationFilter::class.java + ) } } diff --git a/app-main/src/main/kotlin/kr/flab/wiki/app/components/authentication/JwsAuthenticationFilter.kt b/app-main/src/main/kotlin/kr/flab/wiki/app/components/authentication/JwsAuthenticationFilter.kt new file mode 100644 index 0000000..551b924 --- /dev/null +++ b/app-main/src/main/kotlin/kr/flab/wiki/app/components/authentication/JwsAuthenticationFilter.kt @@ -0,0 +1,30 @@ +package kr.flab.wiki.app.components.authentication + +import io.jsonwebtoken.Claims +import io.jsonwebtoken.Jws +import kr.flab.wiki.app.utils.JwtUtils +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.web.filter.OncePerRequestFilter +import javax.servlet.FilterChain +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +class JwsAuthenticationFilter : OncePerRequestFilter() { + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val jws: String? = JwtUtils.parseJws(request.getHeader("Authorization") ?: "") + var result: Jws? + if (jws != null) { + result = JwtUtils.validateJws(jws) + if (result != null) { + val email = JwtUtils.getEmailFromJws(jws) + SecurityContextHolder.getContext().authentication = UsernamePasswordAuthenticationToken(email, null) + } + } + filterChain.doFilter(request, response) + } +} diff --git a/app-main/src/main/kotlin/kr/flab/wiki/app/components/authentication/UnauthorizedEntryPoint.kt b/app-main/src/main/kotlin/kr/flab/wiki/app/components/authentication/UnauthorizedEntryPoint.kt new file mode 100644 index 0000000..9004435 --- /dev/null +++ b/app-main/src/main/kotlin/kr/flab/wiki/app/components/authentication/UnauthorizedEntryPoint.kt @@ -0,0 +1,18 @@ +package kr.flab.wiki.app.components.authentication + +import org.springframework.security.core.AuthenticationException +import org.springframework.security.web.AuthenticationEntryPoint +import org.springframework.stereotype.Component +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +@Component +class UnauthorizedEntryPoint : AuthenticationEntryPoint { + override fun commence( + request: HttpServletRequest?, + response: HttpServletResponse?, + authException: AuthenticationException? + ) { + response?.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied") + } +} diff --git a/app-main/src/main/kotlin/kr/flab/wiki/app/utils/JwtUtils.kt b/app-main/src/main/kotlin/kr/flab/wiki/app/utils/JwtUtils.kt index 01010e8..de546f6 100644 --- a/app-main/src/main/kotlin/kr/flab/wiki/app/utils/JwtUtils.kt +++ b/app-main/src/main/kotlin/kr/flab/wiki/app/utils/JwtUtils.kt @@ -1,20 +1,25 @@ package kr.flab.wiki.app.utils -import io.jsonwebtoken.SignatureAlgorithm +import io.jsonwebtoken.Claims +import io.jsonwebtoken.Jws import io.jsonwebtoken.Jwts +import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.security.Keys import org.springframework.security.core.Authentication +import org.springframework.util.StringUtils import java.security.KeyPair import java.util.Date class JwtUtils private constructor() { companion object { private const val EXPIRATION_TIME: Long = 1 * 1 * 30 * 60 * 1000 + /** * 해당 키페어는 서버 시작할 때마다 재생성됩니다. * 추후에는 미리 생성한 후 설정파일(ex : application-*.yml)에서 읽어들일 예정입니다. */ private val KEY_PAIR: KeyPair = Keys.keyPairFor(SignatureAlgorithm.RS256) + private const val BEARER_LENGTH: Int = 7 fun generateJws(authentication: Authentication): String { val email = authentication.name @@ -30,17 +35,47 @@ class JwtUtils private constructor() { .signWith(KEY_PAIR.private) .compact() } -// fun validateJws(jwsString: String): Jws? { + + fun getEmailFromJws(jwsString: String): String { + return Jwts.parserBuilder() + .setSigningKey(KEY_PAIR.public) + .build() + .parseClaimsJws(jwsString) + .body["email"] + .toString() + } + + fun validateJws(jwsString: String): Jws? { // val jws: Jws // try { // jws = Jwts.parserBuilder() // .setSigningKey(KEY_PAIR.public) // .build() // .parseClaimsJws(jwsString) -// } catch (e: JwtException) { +// } catch (e: ExpiredJwtException) { +// return null +// } catch (e: SignatureException) { +// return null +// } catch (e: MalformedJwtException) { +// return null +// } catch (e: UnsupportedJwtException) { +// return null +// } catch (e: IllegalArgumentException) { +// return null +// } catch (e: UnsupportedEncodingException) { // return null // } -// return jws -// } + return Jwts.parserBuilder() + .setSigningKey(KEY_PAIR.public) + .build() + .parseClaimsJws(jwsString) + } + + fun parseJws(token: String): String? { + if (StringUtils.hasText(token) and token.startsWith("Bearer ")) { + return token.substring(BEARER_LENGTH, token.length) + } + return null + } } } diff --git a/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt b/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt index 23c8774..04986ee 100644 --- a/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt +++ b/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt @@ -18,6 +18,8 @@ import io.restassured.module.kotlin.extensions.Then import io.restassured.module.kotlin.extensions.When import io.restassured.specification.RequestSpecification import kr.flab.wiki.TAG_TEST_E2E +import kr.flab.wiki.app.api.user.request.LoginRequest +import kr.flab.wiki.app.components.authentication.LoginUserService import kr.flab.wiki.app.components.authentication.UserAuthentication import kr.flab.wiki.core.testlib.user.Users import org.mockito.MockitoAnnotations @@ -48,14 +50,18 @@ class LoginWithSpringSecurityAndJwtTest { * 로그인 이후 * 1. 발행한 JWT 토큰을 매 요청마다 검증한다. (스프링 시큐리티에서 인증이 필요하다고 정한 요청에만) * 2. JWT 가 유효하면 정상적으로 요청을 수행하도록 한다. - * 3. JWT 가 유효하지 않으면 Http 403을 반환한다. + * 3. JWT 가 유효하지 않으면 Http 401을 반환한다. * */ private val faker = Faker.instance() + @Inject private lateinit var objectMapper: ObjectMapper + @Inject + private lateinit var loginUserService: LoginUserService + @MockBean private lateinit var userAuthentication: UserAuthentication @@ -169,19 +175,50 @@ class LoginWithSpringSecurityAndJwtTest { } } - @Disabled + @Nested inner class `인증이 필요한 API 는` { + //테스트할 타겟 api uri + //private val targetApiUri = springApi.getRandomAuthenticatedApiPattern() + private val targetApiUri = "/test" + @Nested - inner class `매 요청마다 토큰을 검증해서` { + inner class `매 요청마다 헤더의 토큰을 확인해서` { + + private lateinit var email: String + private lateinit var password: String + + @BeforeEach + fun setup() { + email = faker.internet().emailAddress() + password = faker.internet().password() + } @Nested inner class 유효하면 { + @BeforeEach + fun setup() { + `when`(userAuthentication.authenticateUser(email, password)) + .thenReturn(Users.randomUser(emailAddress = email)) + } + @Test fun `API 를 정상수행한다`() { + //정상적으로 로그인한 정보 + val loginResponse = loginUserService.login(LoginRequest(email, password)) + Given { + spec(requestSpecification) + //정상적으로 로그인한 정보에서 추출한 token을 헤더에 담아서 요청한다. + header("Authorization", "Bearer ${loginResponse?.token}") + } When { + get(targetApiUri) + } Then { + statusCode(200) + log().all() + } } } @@ -190,8 +227,15 @@ class LoginWithSpringSecurityAndJwtTest { inner class `유효하지 않으면` { @Test - fun `403을 반환한다`() { - + fun `401을 반환한다`() { + Given { + spec(requestSpecification) + } When { + get(targetApiUri) + } Then { + statusCode(401) + log().all() + } } } diff --git a/app-main/src/test/kotlin/kr/flab/wiki/app/testlib/SpringApi.kt b/app-main/src/test/kotlin/kr/flab/wiki/app/testlib/SpringApi.kt new file mode 100644 index 0000000..d808bb2 --- /dev/null +++ b/app-main/src/test/kotlin/kr/flab/wiki/app/testlib/SpringApi.kt @@ -0,0 +1,38 @@ +package kr.flab.wiki.app.testlib + +import org.springframework.context.ApplicationContext +import org.springframework.stereotype.Component +import org.springframework.web.method.HandlerMethod +import org.springframework.web.servlet.mvc.method.RequestMappingInfo +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping +import java.util.* +import javax.inject.Inject + + +@Component +class SpringApi { + + @Inject + private lateinit var context: ApplicationContext + + private val random: Random = Random() + + fun getAuthenticatedApiPatternList(): List { + val requestMappingHandlerMapping = context + .getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping::class.java) + val map = requestMappingHandlerMapping + .handlerMethods + + return map.filter { (key: RequestMappingInfo?, value: HandlerMethod?) -> + !key.patternValues.contains("/login") && !key.patternValues.contains("/error") + }.map { (key: RequestMappingInfo?, value: HandlerMethod?) -> + key.patternValues.toString() + } + } + + fun getRandomAuthenticatedApiPattern(): String { + val patternList = getAuthenticatedApiPatternList() + return patternList[random.nextInt(patternList.size)] + } + +} From 50083f7988a3d86b2545750a54ea9a7a3febc36a Mon Sep 17 00:00:00 2001 From: kms Date: Tue, 10 Aug 2021 21:39:52 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EC=9D=BC?= =?UTF-8?q?=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 주요 내용 1. 테스트 함수 이름 변경 2. Bearer 추출 방식 변경에 따른 상수 제거 (substring -> split) 3. SpringApi 클래스 제거 (사용안함) Closes #66 --- .../kotlin/kr/flab/wiki/app/utils/JwtUtils.kt | 3 +- .../LoginWithSpringSecurityAndJwtTest.kt | 7 +--- .../kr/flab/wiki/app/testlib/SpringApi.kt | 38 ------------------- 3 files changed, 3 insertions(+), 45 deletions(-) delete mode 100644 app-main/src/test/kotlin/kr/flab/wiki/app/testlib/SpringApi.kt diff --git a/app-main/src/main/kotlin/kr/flab/wiki/app/utils/JwtUtils.kt b/app-main/src/main/kotlin/kr/flab/wiki/app/utils/JwtUtils.kt index de546f6..fe87962 100644 --- a/app-main/src/main/kotlin/kr/flab/wiki/app/utils/JwtUtils.kt +++ b/app-main/src/main/kotlin/kr/flab/wiki/app/utils/JwtUtils.kt @@ -19,7 +19,6 @@ class JwtUtils private constructor() { * 추후에는 미리 생성한 후 설정파일(ex : application-*.yml)에서 읽어들일 예정입니다. */ private val KEY_PAIR: KeyPair = Keys.keyPairFor(SignatureAlgorithm.RS256) - private const val BEARER_LENGTH: Int = 7 fun generateJws(authentication: Authentication): String { val email = authentication.name @@ -73,7 +72,7 @@ class JwtUtils private constructor() { fun parseJws(token: String): String? { if (StringUtils.hasText(token) and token.startsWith("Bearer ")) { - return token.substring(BEARER_LENGTH, token.length) + return token.split(' ')[1] } return null } diff --git a/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt b/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt index 04986ee..523a32c 100644 --- a/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt +++ b/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt @@ -4,10 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.github.javafaker.Faker import org.hamcrest.Matchers.* import org.junit.jupiter.api.* -import org.junit.jupiter.api.extension.ExtendWith import org.mockito.Mockito.* import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.junit.jupiter.SpringExtension import io.restassured.builder.RequestSpecBuilder import io.restassured.config.LogConfig import io.restassured.config.RestAssuredConfig @@ -30,7 +28,6 @@ import javax.inject.Inject @Tag(TAG_TEST_E2E) @DisplayName("스프링 시큐리티와 JWT를 사용한 로그인 시나리오를 확인한다.") @Suppress("ClassName", "NonAsciiCharacters") // 테스트 표현을 위한 한글 사용 -@ExtendWith(SpringExtension::class) @SpringBootTest( properties = ["baseUri=http://localhost", "port=8080"], webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT @@ -156,7 +153,7 @@ class LoginWithSpringSecurityAndJwtTest { } @Test - fun `400 을 반환한다`() { + fun `오류가 발생한다 (HTTP 400)`() { Given { spec(requestSpecification) @@ -227,7 +224,7 @@ class LoginWithSpringSecurityAndJwtTest { inner class `유효하지 않으면` { @Test - fun `401을 반환한다`() { + fun `오류가 발생한다 (HTTP 403)`() { Given { spec(requestSpecification) } When { diff --git a/app-main/src/test/kotlin/kr/flab/wiki/app/testlib/SpringApi.kt b/app-main/src/test/kotlin/kr/flab/wiki/app/testlib/SpringApi.kt deleted file mode 100644 index d808bb2..0000000 --- a/app-main/src/test/kotlin/kr/flab/wiki/app/testlib/SpringApi.kt +++ /dev/null @@ -1,38 +0,0 @@ -package kr.flab.wiki.app.testlib - -import org.springframework.context.ApplicationContext -import org.springframework.stereotype.Component -import org.springframework.web.method.HandlerMethod -import org.springframework.web.servlet.mvc.method.RequestMappingInfo -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping -import java.util.* -import javax.inject.Inject - - -@Component -class SpringApi { - - @Inject - private lateinit var context: ApplicationContext - - private val random: Random = Random() - - fun getAuthenticatedApiPatternList(): List { - val requestMappingHandlerMapping = context - .getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping::class.java) - val map = requestMappingHandlerMapping - .handlerMethods - - return map.filter { (key: RequestMappingInfo?, value: HandlerMethod?) -> - !key.patternValues.contains("/login") && !key.patternValues.contains("/error") - }.map { (key: RequestMappingInfo?, value: HandlerMethod?) -> - key.patternValues.toString() - } - } - - fun getRandomAuthenticatedApiPattern(): String { - val patternList = getAuthenticatedApiPatternList() - return patternList[random.nextInt(patternList.size)] - } - -} From cc1f699e5f6ba6d1c49c3b6913fa5c8f3d470fca Mon Sep 17 00:00:00 2001 From: kms Date: Sat, 14 Aug 2021 02:55:22 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=EC=8A=A4=ED=94=84=EB=A7=81=20=EC=8B=9C?= =?UTF-8?q?=ED=81=90=EB=A6=AC=ED=8B=B0=EB=A5=BC=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=9C=20api=20=EC=82=AC=EC=9A=A9=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 주요 내용 1. 메서드 보안방식 (@PreAuthorize) 반영 및 글로벌 filter 보안 제거 2. rest assured http call 방식을 이용한 테스트 및 헬퍼 클래스 작성 Closes #66 --- .../app/api/user/loginUser/LoginUserApi.kt | 22 ++----------------- .../appconfig/security/WebSecurityConfig.kt | 3 ++- .../LoginWithSpringSecurityAndJwtTest.kt | 11 +++------- .../flab/wiki/app/testlib/LoginTestHelper.kt | 22 +++++++++++++++++++ 4 files changed, 29 insertions(+), 29 deletions(-) create mode 100644 app-main/src/test/kotlin/kr/flab/wiki/app/testlib/LoginTestHelper.kt diff --git a/app-main/src/main/kotlin/kr/flab/wiki/app/api/user/loginUser/LoginUserApi.kt b/app-main/src/main/kotlin/kr/flab/wiki/app/api/user/loginUser/LoginUserApi.kt index 748987e..eae3612 100644 --- a/app-main/src/main/kotlin/kr/flab/wiki/app/api/user/loginUser/LoginUserApi.kt +++ b/app-main/src/main/kotlin/kr/flab/wiki/app/api/user/loginUser/LoginUserApi.kt @@ -6,6 +6,7 @@ import kr.flab.wiki.app.components.authentication.LoginUserService import kr.flab.wiki.app.type.annotation.ApiHandler import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -23,28 +24,9 @@ class LoginUserApi( return ResponseEntity.ok().body(loginResponse) } + @PreAuthorize("isAuthenticated()") @GetMapping("/test") fun test(): ResponseEntity { return ResponseEntity.ok().body("test") } - - @GetMapping("/test1") - fun test1(): ResponseEntity { - return ResponseEntity.ok().body("test") - } - - @GetMapping("/test2") - fun test2(): ResponseEntity { - return ResponseEntity.ok().body("test") - } - - @GetMapping("/test3") - fun test3(): ResponseEntity { - return ResponseEntity.ok().body("test") - } - - @GetMapping("/test4") - fun test4(): ResponseEntity { - return ResponseEntity.ok().body("test") - } } diff --git a/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/WebSecurityConfig.kt b/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/WebSecurityConfig.kt index b47c47e..e3c2cab 100644 --- a/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/WebSecurityConfig.kt +++ b/app-main/src/main/kotlin/kr/flab/wiki/app/appconfig/security/WebSecurityConfig.kt @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Configuration import org.springframework.http.HttpMethod import org.springframework.security.authentication.AuthenticationProvider import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter @@ -19,6 +20,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic */ @Configuration @EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) class WebSecurityConfig( private val authenticationProvider: AuthenticationProvider, private val jwsAuthenticationFilter: JwsAuthenticationFilter, @@ -58,7 +60,6 @@ class WebSecurityConfig( .and() .authorizeRequests() .antMatchers(HttpMethod.POST, "/login").permitAll() - .anyRequest().authenticated() http.addFilterBefore( jwsAuthenticationFilter, diff --git a/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt b/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt index 523a32c..69c2059 100644 --- a/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt +++ b/app-main/src/test/kotlin/kr/flab/wiki/app/testcase/login/LoginWithSpringSecurityAndJwtTest.kt @@ -16,9 +16,8 @@ import io.restassured.module.kotlin.extensions.Then import io.restassured.module.kotlin.extensions.When import io.restassured.specification.RequestSpecification import kr.flab.wiki.TAG_TEST_E2E -import kr.flab.wiki.app.api.user.request.LoginRequest -import kr.flab.wiki.app.components.authentication.LoginUserService import kr.flab.wiki.app.components.authentication.UserAuthentication +import kr.flab.wiki.app.testlib.LoginTestHelper import kr.flab.wiki.core.testlib.user.Users import org.mockito.MockitoAnnotations import org.springframework.beans.factory.annotation.Value @@ -56,9 +55,6 @@ class LoginWithSpringSecurityAndJwtTest { @Inject private lateinit var objectMapper: ObjectMapper - @Inject - private lateinit var loginUserService: LoginUserService - @MockBean private lateinit var userAuthentication: UserAuthentication @@ -177,7 +173,6 @@ class LoginWithSpringSecurityAndJwtTest { inner class `인증이 필요한 API 는` { //테스트할 타겟 api uri - //private val targetApiUri = springApi.getRandomAuthenticatedApiPattern() private val targetApiUri = "/test" @Nested @@ -204,12 +199,12 @@ class LoginWithSpringSecurityAndJwtTest { @Test fun `API 를 정상수행한다`() { //정상적으로 로그인한 정보 - val loginResponse = loginUserService.login(LoginRequest(email, password)) + val loginResponse = LoginTestHelper.makeLoginResponse(requestSpecification, email, password) Given { spec(requestSpecification) //정상적으로 로그인한 정보에서 추출한 token을 헤더에 담아서 요청한다. - header("Authorization", "Bearer ${loginResponse?.token}") + header("Authorization", "Bearer ${loginResponse.token}") } When { get(targetApiUri) } Then { diff --git a/app-main/src/test/kotlin/kr/flab/wiki/app/testlib/LoginTestHelper.kt b/app-main/src/test/kotlin/kr/flab/wiki/app/testlib/LoginTestHelper.kt new file mode 100644 index 0000000..e6db802 --- /dev/null +++ b/app-main/src/test/kotlin/kr/flab/wiki/app/testlib/LoginTestHelper.kt @@ -0,0 +1,22 @@ +package kr.flab.wiki.app.testlib + +import io.restassured.RestAssured +import io.restassured.specification.RequestSpecification +import kr.flab.wiki.app.api.user.request.LoginRequest +import kr.flab.wiki.app.api.user.response.LoginResponse + +object LoginTestHelper { + + fun makeLoginResponse(requestSpecification: RequestSpecification, email: String, password: String): LoginResponse { + return RestAssured + .given() + .spec(requestSpecification) + .body(LoginRequest(email, password)) + .`when`() + .post("/login") + .then() + .extract() + .`as`(LoginResponse::class.java) + } + +}