diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt index 2d94751..4f6b177 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt @@ -1,24 +1,35 @@ package io.beaniejoy.dongnecafe.common.config import io.beaniejoy.dongnecafe.common.security.ApiAuthenticationFilter +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.security.servlet.PathRequest import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.HttpMethod +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration 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.WebSecurityCustomizer +import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.crypto.factory.PasswordEncoderFactories import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.AuthenticationFailureHandler +import org.springframework.security.web.authentication.AuthenticationSuccessHandler import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.util.matcher.AntPathRequestMatcher @Configuration @EnableWebSecurity class SecurityConfig { - @Bean - fun passwordEncoder(): PasswordEncoder { - return PasswordEncoderFactories.createDelegatingPasswordEncoder() - } + @Autowired + lateinit var authenticationConfiguration: AuthenticationConfiguration + + @Autowired + lateinit var apiAuthenticationSuccessHandler: AuthenticationSuccessHandler + + @Autowired + lateinit var apiAuthenticationFailureHandler: AuthenticationFailureHandler @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain { @@ -28,6 +39,7 @@ class SecurityConfig { .authorizeRequests() .antMatchers("/auth/members/sign-up").permitAll() + .antMatchers("/test").hasRole("USER") // 임시 인가 테스트용 .anyRequest().authenticated() .and() .addFilterBefore(apiAuthenticationFilter(), UsernamePasswordAuthenticationFilter::class.java) @@ -35,10 +47,27 @@ class SecurityConfig { .build() } + @Bean + fun webSecurityCustomizer(): WebSecurityCustomizer { + return WebSecurityCustomizer { web -> + web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()) + web.ignoring().antMatchers("/error") + } + } + + @Bean + fun passwordEncoder(): PasswordEncoder { + return PasswordEncoderFactories.createDelegatingPasswordEncoder() + } + @Bean fun apiAuthenticationFilter(): ApiAuthenticationFilter { return ApiAuthenticationFilter( AntPathRequestMatcher("/auth/authenticate", HttpMethod.POST.name) - ) + ).apply { + this.setAuthenticationManager(authenticationConfiguration.authenticationManager) + this.setAuthenticationSuccessHandler(apiAuthenticationSuccessHandler) + this.setAuthenticationFailureHandler(apiAuthenticationFailureHandler) + } } } \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt index 24a6bca..7a52e47 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt @@ -23,6 +23,7 @@ class ApiAuthenticationFilter(requestMatcher: AntPathRequestMatcher) : request: HttpServletRequest, response: HttpServletResponse, ): Authentication { + log.info { "[API Filter] attempt to authenticate" } if (isPostMethod(request).not()) { val errorMsg = "Authentication is not supported (only support for POST method)" log.error { errorMsg } @@ -30,6 +31,7 @@ class ApiAuthenticationFilter(requestMatcher: AntPathRequestMatcher) : } val signInRequest = objectMapper.readValue(request.reader, SignInRequest::class.java) + request.setAttribute("email", signInRequest.email) val token = signInRequest.let { if (StringUtils.hasText(it.email).not() || StringUtils.hasText(it.password).not()) { @@ -40,7 +42,9 @@ class ApiAuthenticationFilter(requestMatcher: AntPathRequestMatcher) : UsernamePasswordAuthenticationToken(it.email, it.password) } - return authenticationManager.authenticate(token) + val authenticate = authenticationManager.authenticate(token) + logger.info("attempt authentication ${authenticate.principal}") + return authenticate } private fun isPostMethod(request: HttpServletRequest): Boolean { diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt index 1deda11..0a87acf 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt @@ -31,8 +31,10 @@ class ApiAuthenticationProvider( throw BadCredentialsException("Input password does not match stored password") } + logger.info { "User password ${user.password}" } + // password null로 반환 - return UsernamePasswordAuthenticationToken(email, null, user.authorities) + return UsernamePasswordAuthenticationToken(user, null, user.authorities) } override fun supports(authentication: Class<*>): Boolean { diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationFailureHandler.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationFailureHandler.kt new file mode 100644 index 0000000..a129ac0 --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationFailureHandler.kt @@ -0,0 +1,42 @@ +package io.beaniejoy.dongnecafe.common.security.handler + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import io.beaniejoy.dongnecafe.common.security.model.AuthenticationResult +import io.beaniejoy.dongnecafe.domain.member.model.request.SignInRequest +import mu.KLogging +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.security.core.AuthenticationException +import org.springframework.security.web.authentication.AuthenticationFailureHandler +import org.springframework.stereotype.Component +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +@Component +class ApiAuthenticationFailureHandler : AuthenticationFailureHandler { + private val objectMapper = jacksonObjectMapper() + + companion object : KLogging() + + override fun onAuthenticationFailure( + request: HttpServletRequest, + response: HttpServletResponse, + exception: AuthenticationException, + ) { + val email = request.getAttribute("email") as String + logger.error { "[AUTH FAILED] $email" } + + response.apply { + this.status = HttpStatus.UNAUTHORIZED.value() + this.contentType = MediaType.APPLICATION_JSON_VALUE + } + + objectMapper.writeValue( + response.writer, + AuthenticationResult( + email = email, + msg = "authentication failed" + ) + ) + } +} \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationSuccessHandler.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationSuccessHandler.kt new file mode 100644 index 0000000..b57fc47 --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationSuccessHandler.kt @@ -0,0 +1,43 @@ +package io.beaniejoy.dongnecafe.common.security.handler + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import io.beaniejoy.dongnecafe.common.security.model.AuthenticationResult +import mu.KLogging +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.security.core.Authentication +import org.springframework.security.core.userdetails.User +import org.springframework.security.web.authentication.AuthenticationSuccessHandler +import org.springframework.stereotype.Component +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +@Component +class ApiAuthenticationSuccessHandler :AuthenticationSuccessHandler { + private val objectMapper = jacksonObjectMapper() + + companion object: KLogging() + + override fun onAuthenticationSuccess( + request: HttpServletRequest, + response: HttpServletResponse, + authentication: Authentication, + ) { + val user = authentication.principal as User + logger.info { "[AUTH SUCCESS] email: ${user.username}, authorities: ${user.authorities}" } + + response.apply { + this.status = HttpStatus.OK.value() + this.contentType = MediaType.APPLICATION_JSON_VALUE + } + + objectMapper.writeValue( + response.writer, + AuthenticationResult( + email = user.username, + authorities = user.authorities, + msg = "authentication success" + ) + ) + } +} \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/model/AuthenticationResult.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/model/AuthenticationResult.kt new file mode 100644 index 0000000..b5498f1 --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/model/AuthenticationResult.kt @@ -0,0 +1,11 @@ +package io.beaniejoy.dongnecafe.common.security.model + +import com.fasterxml.jackson.annotation.JsonInclude +import org.springframework.security.core.GrantedAuthority + +data class AuthenticationResult( + val email: String, + @JsonInclude(JsonInclude.Include.NON_EMPTY) + val authorities: Collection = listOf(), + val msg: String +) \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/TestController.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/TestController.kt new file mode 100644 index 0000000..b171393 --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/TestController.kt @@ -0,0 +1,13 @@ +package io.beaniejoy.dongnecafe.controller + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class TestController { + + @GetMapping("/test") + fun test(): String { + return "authorize OK!" + } +} \ No newline at end of file diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt new file mode 100644 index 0000000..9dcf6be --- /dev/null +++ b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt @@ -0,0 +1,34 @@ +package io.beaniejoy.dongnecafe.common.config + +import org.springframework.boot.autoconfigure.security.servlet.PathRequest +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +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.WebSecurityCustomizer +import org.springframework.security.web.SecurityFilterChain + +@Configuration +@EnableWebSecurity +class SecurityConfig { + @Bean + fun filterChain(http: HttpSecurity): SecurityFilterChain { + return http + .csrf().disable() + .formLogin().disable() + + .authorizeRequests() + .anyRequest().authenticated() // 임시 허용 + + .and() + .build() + } + + @Bean + fun webSecurityCustomizer(): WebSecurityCustomizer { + return WebSecurityCustomizer { web -> + web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()) + web.ignoring().antMatchers("/error") + } + } +} \ No newline at end of file diff --git a/dongne-service-api/src/main/resources/application.yml b/dongne-service-api/src/main/resources/application.yml index 4c4880c..eea384a 100644 --- a/dongne-service-api/src/main/resources/application.yml +++ b/dongne-service-api/src/main/resources/application.yml @@ -13,7 +13,7 @@ spring: show-sql: false flyway: baseline-on-migrate: true - baseline-version: 0 + baseline-version: "000" locations: classpath:db/migration,classpath:db/seed devtools: livereload: