From b931f039811a8a444acffb976ca489b59f99a225 Mon Sep 17 00:00:00 2001 From: Hanbin Lee Date: Sun, 6 Nov 2022 21:20:45 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[#20]=20feat:=20JWT=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.gradle.kts 설정에 JWT 라이브러리 내용 추가 - SecurityConfig formLogin 내용 삭제 --- build.gradle.kts | 5 +++++ buildSrc/src/main/kotlin/Version.kt | 1 + .../io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt | 2 -- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a54abf1..f023df2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,6 +52,11 @@ subprojects { runtimeOnly("com.h2database:h2") // H2 implementation("org.flywaydb:flyway-core:${Version.Deps.flywayCore}") // flyway + // JWT + implementation("io.jsonwebtoken:jjwt-api:${Version.Deps.Jwt}") + runtimeOnly("io.jsonwebtoken:jjwt-impl:${Version.Deps.Jwt}") + runtimeOnly("io.jsonwebtoken:jjwt-jackson:${Version.Deps.Jwt}") + // Logging implementation("io.github.microutils:kotlin-logging:${Version.Deps.kotlinLogging}") diff --git a/buildSrc/src/main/kotlin/Version.kt b/buildSrc/src/main/kotlin/Version.kt index cac6a44..71f68ec 100644 --- a/buildSrc/src/main/kotlin/Version.kt +++ b/buildSrc/src/main/kotlin/Version.kt @@ -10,5 +10,6 @@ object Version { object Deps { const val flywayCore = "7.15.0" const val kotlinLogging = "2.1.21" + const val Jwt = "0.11.5" } } \ No newline at end of file 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 4f6b177..bda3aa4 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 @@ -10,7 +10,6 @@ import org.springframework.security.config.annotation.authentication.configurati 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 @@ -35,7 +34,6 @@ class SecurityConfig { fun filterChain(http: HttpSecurity): SecurityFilterChain { return http .csrf().disable() - .formLogin().disable() .authorizeRequests() .antMatchers("/auth/members/sign-up").permitAll() From 5082e555c7dda6aae46089871a13d691ad1fd772 Mon Sep 17 00:00:00 2001 From: Hanbin Lee Date: Sun, 20 Nov 2022 17:25:09 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[#20]=20feat:=20JWT=EB=A5=BC=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=EC=9D=B8=EC=A6=9D=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=84=B8=EC=8A=A4=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 ApiAuthenticationFilter 관련 설정 제거 - RestController 통한 인증 프로세스 jwt token 반환 개발 --- .../common/config/SecurityConfig.kt | 37 +++---------- .../security/ApiAuthenticationFilter.kt | 53 ------------------- .../security/ApiAuthenticationProvider.kt | 4 +- .../security}/UserDetailsServiceImpl.kt | 18 +++---- .../ApiAuthenticationFailureHandler.kt | 42 --------------- .../ApiAuthenticationSuccessHandler.kt | 43 --------------- .../dongnecafe/controller/AuthController.kt | 26 +++++---- .../dongnecafe/model/TokenResponse.kt | 5 ++ .../dongnecafe/service/AuthService.kt | 18 +++++++ .../src/main/resources/application-local.yml | 3 ++ .../dongnecafe/security/JwtTokenUtils.kt | 38 +++++++++++++ .../dongnecafe/security/SecurityUser.kt | 10 ++++ .../src/main/resources/application-local.yml | 3 ++ .../src/test/resources/application.yml | 6 ++- 14 files changed, 116 insertions(+), 190 deletions(-) delete mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt rename dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/{service => common/security}/UserDetailsServiceImpl.kt (68%) delete mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationFailureHandler.kt delete mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationSuccessHandler.kt create mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/model/TokenResponse.kt create mode 100644 dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/AuthService.kt create mode 100644 dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/security/JwtTokenUtils.kt create mode 100644 dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/security/SecurityUser.kt 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 bda3aa4..8ecd601 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,35 +1,19 @@ 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.config.http.SessionCreationPolicy 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 { - @Autowired - lateinit var authenticationConfiguration: AuthenticationConfiguration - - @Autowired - lateinit var apiAuthenticationSuccessHandler: AuthenticationSuccessHandler - - @Autowired - lateinit var apiAuthenticationFailureHandler: AuthenticationFailureHandler - @Bean fun filterChain(http: HttpSecurity): SecurityFilterChain { return http @@ -37,11 +21,15 @@ class SecurityConfig { .authorizeRequests() .antMatchers("/auth/members/sign-up").permitAll() + .antMatchers("/auth/authenticate").permitAll() .antMatchers("/test").hasRole("USER") // 임시 인가 테스트용 .anyRequest().authenticated() - .and() - .addFilterBefore(apiAuthenticationFilter(), UsernamePasswordAuthenticationFilter::class.java) + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 토큰 방식(세션 불필요) + + .and() .build() } @@ -57,15 +45,4 @@ class SecurityConfig { 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 deleted file mode 100644 index a44a00b..0000000 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationFilter.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.beaniejoy.dongnecafe.common.security - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import io.beaniejoy.dongnecafe.domain.member.model.request.SignInRequest -import org.springframework.http.HttpMethod -import org.springframework.http.MediaType -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.core.Authentication -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter -import org.springframework.security.web.util.matcher.AntPathRequestMatcher -import org.springframework.util.StringUtils -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -class ApiAuthenticationFilter(requestMatcher: AntPathRequestMatcher) : - AbstractAuthenticationProcessingFilter(requestMatcher) { - - private val objectMapper = jacksonObjectMapper() - - override fun attemptAuthentication( - request: HttpServletRequest, - response: HttpServletResponse, - ): Authentication { - if (isValidRequest(request).not()) { - throw IllegalStateException("request is not supported. check request method and content-type") - } - - 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()) { - throw IllegalArgumentException("Email & Password are not empty!!") - } - - UsernamePasswordAuthenticationToken(it.email, it.password) - } - - return authenticationManager.authenticate(token) - } - - private fun isValidRequest(request: HttpServletRequest): Boolean { - if (request.method != HttpMethod.POST.name) { - return false - } - - if (request.contentType != MediaType.APPLICATION_JSON_VALUE) { - return false - } - - return true - } -} \ No newline at end of file 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 0a87acf..6990e3d 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 @@ -1,5 +1,6 @@ package io.beaniejoy.dongnecafe.common.security +import io.beaniejoy.dongnecafe.security.SecurityUser import mu.KLogging import org.springframework.security.authentication.AuthenticationProvider import org.springframework.security.authentication.BadCredentialsException @@ -26,14 +27,13 @@ class ApiAuthenticationProvider( val email = authentication.name val password = authentication.credentials as String? - val user = userDetailsService.loadUserByUsername(email) + val user = userDetailsService.loadUserByUsername(email) as SecurityUser if (!passwordEncoder.matches(password, user.password)) { throw BadCredentialsException("Input password does not match stored password") } logger.info { "User password ${user.password}" } - // password null로 반환 return UsernamePasswordAuthenticationToken(user, null, user.authorities) } diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/UserDetailsServiceImpl.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/UserDetailsServiceImpl.kt similarity index 68% rename from dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/UserDetailsServiceImpl.kt rename to dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/UserDetailsServiceImpl.kt index ac293f9..970cc3f 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/UserDetailsServiceImpl.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/UserDetailsServiceImpl.kt @@ -1,15 +1,15 @@ -package io.beaniejoy.dongnecafe.service +package io.beaniejoy.dongnecafe.common.security import io.beaniejoy.dongnecafe.domain.member.entity.Member import io.beaniejoy.dongnecafe.domain.member.repository.MemberRepository import io.beaniejoy.dongnecafe.error.MemberDeactivatedException +import io.beaniejoy.dongnecafe.security.SecurityUser import mu.KLogging import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.core.userdetails.User -import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Transactional @Component("userDetailsService") class UserDetailsServiceImpl( @@ -17,22 +17,22 @@ class UserDetailsServiceImpl( ) : UserDetailsService { companion object: KLogging() - override fun loadUserByUsername(email: String): UserDetails { + @Transactional(readOnly = true) + override fun loadUserByUsername(email: String): SecurityUser { return memberRepository.findByEmail(email)?.let { logger.info { "[LOAD MEMBER] email: ${it.email}, role: ${it.roleType}, activated: ${it.activated}" } createSecurityUser(it) } ?: throw UsernameNotFoundException(email) } - private fun createSecurityUser(member: Member): User { + private fun createSecurityUser(member: Member): SecurityUser { if (member.activated.not()) { throw MemberDeactivatedException(member.email) } - return User( - /* username = */ member.email, - /* password = */ member.password, - /* authorities = */ listOf(SimpleGrantedAuthority(member.roleType.name)) + return SecurityUser( + member = member, + authorities = listOf(SimpleGrantedAuthority(member.roleType.name)) ) } } \ No newline at end of file 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 deleted file mode 100644 index a129ac0..0000000 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationFailureHandler.kt +++ /dev/null @@ -1,42 +0,0 @@ -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 deleted file mode 100644 index b57fc47..0000000 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/handler/ApiAuthenticationSuccessHandler.kt +++ /dev/null @@ -1,43 +0,0 @@ -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/controller/AuthController.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt index 29ffd60..37dcfce 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt @@ -1,8 +1,9 @@ package io.beaniejoy.dongnecafe.controller +import io.beaniejoy.dongnecafe.security.JwtTokenUtils import io.beaniejoy.dongnecafe.domain.member.model.request.SignInRequest -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder +import io.beaniejoy.dongnecafe.model.TokenResponse +import io.beaniejoy.dongnecafe.service.AuthService import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping @@ -11,13 +12,18 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/auth") class AuthController( - private val authenticationManagerBuilder: AuthenticationManagerBuilder + private val authService: AuthService, + private val jwtTokenUtils: JwtTokenUtils ) { -// @PostMapping("/authenticate") -// fun signIn(@RequestBody signInRequest: SignInRequest) { -// val authenticationToken = -// UsernamePasswordAuthenticationToken(signInRequest.email, signInRequest.password) -// -// val authenticate = authenticationManagerBuilder.`object`.authenticate(authenticationToken) -// } + @PostMapping("/authenticate") + fun signIn(@RequestBody signInRequest: SignInRequest): TokenResponse { + val authentication = authService.signIn( + email = signInRequest.email, + password = signInRequest.password + ) + + val accessToken = jwtTokenUtils.createToken(authentication) + + return TokenResponse(accessToken) + } } \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/model/TokenResponse.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/model/TokenResponse.kt new file mode 100644 index 0000000..f256376 --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/model/TokenResponse.kt @@ -0,0 +1,5 @@ +package io.beaniejoy.dongnecafe.model + +data class TokenResponse( + val accessToken: String +) \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/AuthService.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/AuthService.kt new file mode 100644 index 0000000..d6c3f45 --- /dev/null +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/AuthService.kt @@ -0,0 +1,18 @@ +package io.beaniejoy.dongnecafe.service + +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration +import org.springframework.security.core.Authentication +import org.springframework.stereotype.Service + +@Service +class AuthService(authenticationConfiguration: AuthenticationConfiguration) { + private val authenticationManager: AuthenticationManager = authenticationConfiguration.authenticationManager + + fun signIn(email: String, password: String): Authentication { + val authenticationToken = UsernamePasswordAuthenticationToken(email, password) + + return authenticationManager.authenticate(authenticationToken) + } +} \ No newline at end of file diff --git a/dongne-account-api/src/main/resources/application-local.yml b/dongne-account-api/src/main/resources/application-local.yml index 0a4ee95..58a0493 100644 --- a/dongne-account-api/src/main/resources/application-local.yml +++ b/dongne-account-api/src/main/resources/application-local.yml @@ -3,3 +3,6 @@ spring: url: jdbc:mysql://localhost:3306/dongne?autoreconnect=true&characterEncoding=utf8&serverTimezone=Asia/Seoul username: root password: beaniejoy # TODO 추후 보안에 대해 생각해보기 +jwt: + secret_key: aG9wZS15b3UtYWx3YXlzLWJlLWhhcHB5LXRoaXMteWVhcgo= + expiration_time: 86400 \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/security/JwtTokenUtils.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/security/JwtTokenUtils.kt new file mode 100644 index 0000000..69cf1e6 --- /dev/null +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/security/JwtTokenUtils.kt @@ -0,0 +1,38 @@ +package io.beaniejoy.dongnecafe.security + +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.SignatureAlgorithm +import io.jsonwebtoken.security.Keys +import mu.KLogging +import org.springframework.beans.factory.annotation.Value +import org.springframework.security.core.Authentication +import org.springframework.stereotype.Component +import java.security.Key +import java.util.* + +@Component +class JwtTokenUtils( + @Value("\${jwt.secret_key}") + private val secretKey: String, + @Value("\${jwt.expiration_time}") + private val expirationTime: Long +) { + private val key: Key = Keys.hmacShaKeyFor(secretKey.toByteArray()) + + companion object: KLogging() + + fun createToken(authentication: Authentication): String { + val authenticatedMember = (authentication.principal as SecurityUser).member + val nowTime = Date().time + val expirationDate = Date(nowTime + this.expirationTime) + + return Jwts.builder() + .setSubject(authenticatedMember.email) + .claim("memberId", authenticatedMember.id) + .claim("email", authenticatedMember.email) + .claim("roles", authentication.authorities.joinToString(",") { it.authority }) + .signWith(key, SignatureAlgorithm.HS256) + .setExpiration(expirationDate) + .compact() + } +} \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/security/SecurityUser.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/security/SecurityUser.kt new file mode 100644 index 0000000..17a705d --- /dev/null +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/security/SecurityUser.kt @@ -0,0 +1,10 @@ +package io.beaniejoy.dongnecafe.security + +import io.beaniejoy.dongnecafe.domain.member.entity.Member +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.userdetails.User + +class SecurityUser( + val member: Member, + authorities: Collection +) : User(member.email, member.password, authorities) \ No newline at end of file diff --git a/dongne-service-api/src/main/resources/application-local.yml b/dongne-service-api/src/main/resources/application-local.yml index 0a4ee95..58a0493 100644 --- a/dongne-service-api/src/main/resources/application-local.yml +++ b/dongne-service-api/src/main/resources/application-local.yml @@ -3,3 +3,6 @@ spring: url: jdbc:mysql://localhost:3306/dongne?autoreconnect=true&characterEncoding=utf8&serverTimezone=Asia/Seoul username: root password: beaniejoy # TODO 추후 보안에 대해 생각해보기 +jwt: + secret_key: aG9wZS15b3UtYWx3YXlzLWJlLWhhcHB5LXRoaXMteWVhcgo= + expiration_time: 86400 \ No newline at end of file diff --git a/dongne-service-api/src/test/resources/application.yml b/dongne-service-api/src/test/resources/application.yml index 61ea27c..71426a6 100644 --- a/dongne-service-api/src/test/resources/application.yml +++ b/dongne-service-api/src/test/resources/application.yml @@ -10,4 +10,8 @@ spring: logging: level: org.hibernate.SQL: debug - org.hibernate.type: trace # 실제 sql 쿼리의 parameter 값을 확인하고자 함 \ No newline at end of file + org.hibernate.type: trace # 실제 sql 쿼리의 parameter 값을 확인하고자 함 + +jwt: + secret_key: ZG9uZ25lLWNhZmUtcHJvamVjdC1rZXktZm9yLXRlc3QtY29kZQo + expiration_time: 60 \ No newline at end of file