Skip to content

Commit

Permalink
feat: Spring Security 를 리액티브환경으로 변경
Browse files Browse the repository at this point in the history
  • Loading branch information
van1164 committed Apr 25, 2024
1 parent 829d707 commit 8be2c32
Show file tree
Hide file tree
Showing 22 changed files with 703 additions and 366 deletions.
26 changes: 24 additions & 2 deletions src/main/kotlin/com/KY/KoreanYoutube/config/RedisConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import lombok.RequiredArgsConstructor
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.ReactiveRedisTemplate
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories
import org.springframework.data.redis.serializer.RedisSerializationContext
import org.springframework.data.redis.serializer.RedisSerializationContext.RedisSerializationContextBuilder
import org.springframework.data.redis.serializer.StringRedisSerializer

@Configuration(value = "redisConfig")
Expand All @@ -16,10 +20,10 @@ import org.springframework.data.redis.serializer.StringRedisSerializer
class RedisConfig {

@Value("\${spring.data.redis.host}")
lateinit var host : String
lateinit var host: String

@Value("\${spring.data.redis.port}")
var port : Int = 6379
var port: Int = 6379


@Bean
Expand All @@ -29,6 +33,13 @@ class RedisConfig {
return lettuceConnectionFactory
}

@Bean
fun reactiveRedisConnectionFactory(): ReactiveRedisConnectionFactory {
val lettuceConnectionFactory = LettuceConnectionFactory(host, port)
lettuceConnectionFactory.start()
return lettuceConnectionFactory
}

@Bean
fun redisTemplate(): RedisTemplate<String, String> {
val redisTemplate = RedisTemplate<String, String>()
Expand All @@ -38,4 +49,15 @@ class RedisConfig {
redisTemplate.afterPropertiesSet()
return redisTemplate
}

// @Bean
// fun reactiveRedisTemplate(): ReactiveRedisTemplate<String, String> {
// val serializationContext =
// RedisSerializationContext.newSerializationContext<String, String>()
// .key(StringRedisSerializer())
// .value(StringRedisSerializer())
// .build()
// return ReactiveRedisTemplate<String, String>(reactiveRedisConnectionFactory(), serializationContext)
//
// }
}
95 changes: 54 additions & 41 deletions src/main/kotlin/com/KY/KoreanYoutube/config/SecurityConfig.kt
Original file line number Diff line number Diff line change
@@ -1,69 +1,82 @@
package com.KY.KoreanYoutube.config

import com.KY.KoreanYoutube.security.JwtAuthenticationFilter
import com.KY.KoreanYoutube.security.OAuthSuccessHandler
import com.KY.KoreanYoutube.security.PrincipalOauthUserService
import com.KY.KoreanYoutube.security_reactive.JwtAuthenticationFilterReactive
import com.KY.KoreanYoutube.security_reactive.OAuthSuccessHandlerReactive
import com.KY.KoreanYoutube.security_reactive.PrincipalOauthUserServiceReactive
import mu.KotlinLogging
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
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.invoke
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.SecurityWebFiltersOrder
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher


val log = KotlinLogging.logger{}

@EnableWebSecurity
@Import(DataSourceAutoConfiguration::class)

@Configuration
@EnableReactiveMethodSecurity
@EnableWebFluxSecurity
class SecurityConfig(
val principalOauthUserService: PrincipalOauthUserService,
val oAuthSuccessHandler: OAuthSuccessHandler,
val jwtAuthenticationFilter: JwtAuthenticationFilter
val principalOauthUserService: PrincipalOauthUserServiceReactive,
val oAuthSuccessHandler: OAuthSuccessHandlerReactive,
val jwtAuthenticationFilter: JwtAuthenticationFilterReactive,
val clientRegistrationRepository: ReactiveClientRegistrationRepository
) {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http { // kotlin DSL
httpBasic { disable() }
csrf { disable() }
cors { }
authorizeRequests {
authorize("/api/v1/stream/done", permitAll)
authorize("/api/v1/stream/verify", permitAll)
authorize("/api/v1/stream/live/**",permitAll)
authorize("/api/v1/stream/**",authenticated)
authorize("/api/v1/upload/**",authenticated)
authorize("/**",permitAll)
fun filterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http.httpBasic {
it.disable()
}
oauth2Login {
loginPage = "/loginPage"
defaultSuccessUrl("/",true)
userInfoEndpoint {
userService = principalOauthUserService
}
authenticationSuccessHandler = oAuthSuccessHandler
.csrf {
it.disable()
}

exceptionHandling {
//authenticationEntryPoint = serverAuthenticationEntryPoint()
.authorizeExchange{
it.pathMatchers("/api/v1/stream/done").permitAll()
it.pathMatchers("/api/v1/stream/verify").permitAll()
it.pathMatchers("/api/v1/stream/live/**") .permitAll()
it.pathMatchers("/api/v1/stream/**").authenticated()
it.pathMatchers("/api/v1/upload/**").authenticated()
it.pathMatchers("/**").permitAll()
}
addFilterBefore<UsernamePasswordAuthenticationFilter>(jwtAuthenticationFilter)


}
.exceptionHandling{
it.authenticationEntryPoint(RedirectServerAuthenticationEntryPoint("/loginPage"))
}
.oauth2Login {
it.authenticationSuccessHandler(oAuthSuccessHandler)
it.authorizationRequestResolver(authorizationRequestResolver())
}
.addFilterBefore(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
return http.build()
}
@Bean
fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}

private fun authorizationRequestResolver(): ServerOAuth2AuthorizationRequestResolver {
val authorizationRequestMatcher: ServerWebExchangeMatcher = PathPatternParserServerWebExchangeMatcher(
"/oauth2/authorization/{registrationId}"
)

return DefaultServerOAuth2AuthorizationRequestResolver(
clientRegistrationRepository, authorizationRequestMatcher
)
}


// private fun serverAuthenticationEntryPoint(): AuthenticationEntryPoint? {
// return AuthenticationEntryPoint { request,response, authEx: AuthenticationException ->
// val requestPath = request.pathInfo
Expand Down
34 changes: 17 additions & 17 deletions src/main/kotlin/com/KY/KoreanYoutube/config/WebConfig.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package com.KY.KoreanYoutube.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("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(false)
.maxAge(3600)
}
}
//
//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("/**")
// .allowedOrigins("*")
// .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
// .allowedHeaders("*")
// .allowCredentials(false)
// .maxAge(3600)
// }
//}
68 changes: 68 additions & 0 deletions src/main/kotlin/com/KY/KoreanYoutube/config/WebFluxConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.KY.KoreanYoutube.config

import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.config.CorsRegistry
import org.springframework.web.reactive.config.EnableWebFlux
import org.springframework.web.reactive.config.ViewResolverRegistry
import org.springframework.web.reactive.config.WebFluxConfigurer
import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine
import org.thymeleaf.spring6.SpringWebFluxTemplateEngine
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver
import org.thymeleaf.spring6.view.reactive.ThymeleafReactiveViewResolver
import org.thymeleaf.templatemode.TemplateMode


@Configuration
@EnableWebFlux
class WebFluxConfig(
private var ctx : ApplicationContext
) : ApplicationContextAware, WebFluxConfigurer{

override fun setApplicationContext(applicationContext: ApplicationContext) {
ctx = applicationContext
}
@Bean
fun thymeleafTemplateResolver(): SpringResourceTemplateResolver {
val resolver = SpringResourceTemplateResolver()
resolver.setApplicationContext(this.ctx)
resolver.prefix = "classpath:/templates/"
resolver.suffix = ".html"
resolver.templateMode = TemplateMode.HTML
resolver.isCacheable = false
resolver.checkExistence = false
return resolver
}

@Bean
fun thymeleafTemplateEngine(): ISpringWebFluxTemplateEngine {
val templateEngine = SpringWebFluxTemplateEngine()
templateEngine.setTemplateResolver(thymeleafTemplateResolver())
return templateEngine
}

@Bean
fun thymeleafChunkedAndDataDrivenViewResolver(): ThymeleafReactiveViewResolver {
val viewResolver = ThymeleafReactiveViewResolver()
viewResolver.templateEngine = thymeleafTemplateEngine()
viewResolver.responseMaxChunkSizeBytes = 8192
return viewResolver
}

override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.viewResolver(thymeleafChunkedAndDataDrivenViewResolver())
}

override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(false)
.maxAge(3600)
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.KY.KoreanYoutube.dto

data class VideoDetailResponseDTO()
63 changes: 36 additions & 27 deletions src/main/kotlin/com/KY/KoreanYoutube/main/MainController.kt
Original file line number Diff line number Diff line change
@@ -1,52 +1,61 @@
package com.KY.KoreanYoutube.main

import com.KY.KoreanYoutube.config.log
import com.KY.KoreanYoutube.security.JwtTokenProvider
import com.KY.KoreanYoutube.security.logger
import com.KY.KoreanYoutube.user.UserService
import jakarta.servlet.http.HttpServletRequest
import com.KY.KoreanYoutube.user.UserR2DBCService
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus.MOVED_PERMANENTLY
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseBody
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.onErrorReturn
import java.net.URI

@Controller
@RequestMapping("/")
class MainController(
val mainService: MainService,
val jwtTokenProvider: JwtTokenProvider,
val userService: UserService
val userService: UserR2DBCService
) {

@GetMapping("/")
fun mainPage(model : Model,@RequestParam(required = false) token : String?): String {
val mainData = mainService.getMainPage()
model.addAllAttributes(mainData)
if(!token.isNullOrEmpty()){
logger.info { "++++++++++++++++++++++++++++++로그인됨" }
val name = jwtTokenProvider.getAuthentication(token).name
val user = userService.findByUserId(name)
if(user !=null){
model.addAttribute("user",user.name)
model.addAttribute("jwt",token)
model.addAttribute("isLogined", "true")
}
else{
model.addAttribute("user","null")
model.addAttribute("isLogined", "false")
}
}
else{
model.addAttribute("user","null")
model.addAttribute("isLogined", "false")
}
return "main"

fun mainPage(model : Model,@RequestParam(required = false) token : String?): Mono<String> {
logger.info { "TESTCCCCCCCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }
logger.info{token}
return mainService.getMainPage()
.doOnNext {mainData->
model.addAllAttributes(mainData)
logger.info{mainData}
}
.doOnNext {
checkNotNull(token)
check(token.isNotEmpty())
}
.flatMap {
val name = jwtTokenProvider.getAuthentication(token!!).name
userService.findByUserId(name)
}.doOnNext {user->
checkNotNull(user)
}
.doOnNext {user->
model.addAttribute("user",user.name)
model.addAttribute("jwt",token)
model.addAttribute("isLogined", "true")
}
.doOnError {
logger.info { "NULL" }
model.addAttribute("user","null")
model.addAttribute("isLogined", "false")
}
.thenReturn("main")
.onErrorReturn("main")
}

@GetMapping("/access/google")
Expand Down
Loading

0 comments on commit 8be2c32

Please sign in to comment.