-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
369 additions
and
24 deletions.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
src/main/kotlin/org/store/clothstar/common/config/CustomAuthenticationEntryPoint.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package org.store.clothstar.common.config | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import jakarta.servlet.ServletException | ||
import jakarta.servlet.http.HttpServletRequest | ||
import jakarta.servlet.http.HttpServletResponse | ||
import org.springframework.security.core.AuthenticationException | ||
import org.springframework.security.web.AuthenticationEntryPoint | ||
import org.springframework.stereotype.Component | ||
import org.store.clothstar.common.dto.MessageDTO | ||
import java.io.IOException | ||
|
||
@Component | ||
class CustomAuthenticationEntryPoint : AuthenticationEntryPoint { | ||
private val log = KotlinLogging.logger {} | ||
|
||
@Throws(IOException::class, ServletException::class) | ||
override fun commence( | ||
request: HttpServletRequest, | ||
response: HttpServletResponse, | ||
authException: AuthenticationException, | ||
) { | ||
log.error { "인증 실패 로직 실행" } | ||
response.status = HttpServletResponse.SC_UNAUTHORIZED | ||
response.characterEncoding = "UTF-8" | ||
response.contentType = "application/json" | ||
|
||
val messageDTO = MessageDTO( | ||
HttpServletResponse.SC_UNAUTHORIZED, | ||
"권한이 없습니다." | ||
) | ||
|
||
response.writer.write(ObjectMapper().writeValueAsString(messageDTO)) | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
src/main/kotlin/org/store/clothstar/common/config/CustomUserDetailsService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package org.store.clothstar.common.config | ||
|
||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
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.Service | ||
import org.store.clothstar.common.error.ErrorCode | ||
import org.store.clothstar.member.domain.Account | ||
import org.store.clothstar.member.domain.CustomUserDetails | ||
import org.store.clothstar.member.repository.AccountRepository | ||
|
||
@Service | ||
class CustomUserDetailsService( | ||
private val accountRepository: AccountRepository, | ||
) : UserDetailsService { | ||
private val log = KotlinLogging.logger {} | ||
|
||
@Throws(UsernameNotFoundException::class) | ||
override fun loadUserByUsername(email: String): UserDetails { | ||
log.info { "loadUserByUsername() 실행" } | ||
val account: Account = accountRepository.findByEmail(email) | ||
?: throw UsernameNotFoundException(ErrorCode.NOT_FOUND_ACCOUNT.message) | ||
|
||
return CustomUserDetails(account) | ||
} | ||
} |
122 changes: 122 additions & 0 deletions
122
src/main/kotlin/org/store/clothstar/common/config/LoginFilter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package org.store.clothstar.common.config | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import jakarta.servlet.FilterChain | ||
import jakarta.servlet.ServletException | ||
import jakarta.servlet.http.HttpServletRequest | ||
import jakarta.servlet.http.HttpServletResponse | ||
import org.springframework.http.HttpStatus | ||
import org.springframework.security.authentication.AuthenticationManager | ||
import org.springframework.security.authentication.BadCredentialsException | ||
import org.springframework.security.authentication.DisabledException | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken | ||
import org.springframework.security.core.Authentication | ||
import org.springframework.security.core.AuthenticationException | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter | ||
import org.store.clothstar.common.config.jwt.JwtUtil | ||
import org.store.clothstar.common.dto.MessageDTO | ||
import org.store.clothstar.member.domain.CustomUserDetails | ||
import org.store.clothstar.member.dto.request.MemberLoginRequest | ||
import java.io.IOException | ||
|
||
class LoginFilter( | ||
private val authenticationManager: AuthenticationManager, | ||
private val jwtUtil: JwtUtil, | ||
) : UsernamePasswordAuthenticationFilter() { | ||
init { | ||
setFilterProcessesUrl("/v1/members/login") | ||
} | ||
|
||
private val log = KotlinLogging.logger {} | ||
|
||
/** | ||
* 로그인 창에서 입력한 id, password를 받아서 | ||
* Authentication Manager에 던져 줘야 하는데 그 DTO역할을 하는 객체가 UsernamePasswordAuthenticationToken이다. | ||
* Authentication Manager에 전달하면 최종적으로 Authentication에 전달 된다. | ||
* return 하면 Authentication Manager에 던져진다. | ||
* | ||
* | ||
* AuthenticationManager에 던지기 위해서 주입을 받아야 한다. | ||
*/ | ||
@Throws(AuthenticationException::class) | ||
override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication { | ||
log.info { "로그인 진행" } | ||
|
||
try { | ||
val memberLoginRequest = ObjectMapper().readValue(request.inputStream, MemberLoginRequest::class.java) | ||
log.info { "login parameter memberLoginRequest: ${memberLoginRequest.toString()}" } | ||
|
||
val email = memberLoginRequest.email | ||
val password = memberLoginRequest.password | ||
|
||
val authTokenDTO = UsernamePasswordAuthenticationToken(email, password, null) | ||
return authenticationManager.authenticate(authTokenDTO) | ||
} catch (e: IOException) { | ||
throw RuntimeException(e) | ||
} | ||
} | ||
|
||
@Throws(IOException::class, ServletException::class) | ||
override fun successfulAuthentication( | ||
request: HttpServletRequest, | ||
response: HttpServletResponse, | ||
chain: FilterChain, | ||
authentication: Authentication, | ||
) { | ||
log.info { "로그인 성공" } | ||
val customUserDetails = authentication.principal as CustomUserDetails | ||
|
||
val account = customUserDetails.account | ||
log.info { "account: ${account.toString()}" } | ||
|
||
val accessToken = jwtUtil.createAccessToken(account) | ||
log.info { "생성 accessToken: Bearer $accessToken " } | ||
|
||
val refreshToken = jwtUtil.createRefreshToken(account) | ||
log.info { "생성 refreshToken: Bearer $refreshToken" } | ||
|
||
response.addHeader("Authorization", "Bearer $accessToken") | ||
response.addCookie(jwtUtil.createCookie("refreshToken", refreshToken)) | ||
response.status = HttpStatus.OK.value() | ||
response.characterEncoding = "UTF-8" | ||
response.contentType = "application/json" | ||
|
||
val messageDTO: MessageDTO = MessageDTO( | ||
HttpServletResponse.SC_OK, | ||
"로그인 성공 하였습니다." | ||
) | ||
|
||
response.writer.print(ObjectMapper().writeValueAsString(messageDTO)) | ||
} | ||
|
||
@Throws(IOException::class, ServletException::class) | ||
override fun unsuccessfulAuthentication( | ||
request: HttpServletRequest, | ||
response: HttpServletResponse, | ||
failed: AuthenticationException, | ||
) { | ||
log.info { "로그인 실패" } | ||
|
||
response.status = HttpServletResponse.SC_UNAUTHORIZED | ||
response.characterEncoding = "UTF-8" | ||
response.contentType = "application/json" | ||
|
||
val messageDTO: MessageDTO = MessageDTO( | ||
HttpServletResponse.SC_UNAUTHORIZED, | ||
errorMessage(failed), | ||
) | ||
|
||
response.writer.print(ObjectMapper().writeValueAsString(messageDTO)) | ||
} | ||
|
||
private fun errorMessage(failed: AuthenticationException): String? { | ||
return if (failed is BadCredentialsException) { | ||
"이메일 또는 비밀번호가 올바르지 않습니다. 다시 확인해주세요." | ||
} else if (failed is DisabledException) { | ||
"계정이 비활성화 되어있습니다. 이메일 인증을 완료해주세요" | ||
} else { | ||
null | ||
} | ||
} | ||
} |
81 changes: 80 additions & 1 deletion
81
src/main/kotlin/org/store/clothstar/common/config/SecurityConfiguration.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,95 @@ | ||
package org.store.clothstar.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.http.HttpMethod | ||
import org.springframework.security.authentication.AuthenticationManager | ||
import org.springframework.security.config.Customizer | ||
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.annotation.web.configurers.* | ||
import org.springframework.security.config.http.SessionCreationPolicy | ||
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.store.clothstar.common.config.jwt.JwtAuthenticationFilter | ||
import org.store.clothstar.common.config.jwt.JwtUtil | ||
|
||
@EnableWebSecurity | ||
@Configuration | ||
class SecurityConfiguration( | ||
private val authenticationConfiguration: AuthenticationConfiguration, | ||
private val jwtAuthenticationFilter: JwtAuthenticationFilter, | ||
private val jwtUtil: JwtUtil, | ||
) { | ||
@Bean | ||
fun passwordEncoder(): PasswordEncoder { | ||
return BCryptPasswordEncoder() | ||
} | ||
|
||
) { | ||
@Bean | ||
@Throws(Exception::class) | ||
fun authenticationManager(): AuthenticationManager { | ||
return authenticationConfiguration.authenticationManager | ||
} | ||
|
||
@Bean | ||
fun configure(): WebSecurityCustomizer { | ||
return WebSecurityCustomizer { web -> | ||
web.ignoring() | ||
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()) | ||
} | ||
} | ||
|
||
@Bean | ||
@Throws(java.lang.Exception::class) | ||
fun filterChain(http: HttpSecurity): SecurityFilterChain { | ||
http.cors { obj: CorsConfigurer<HttpSecurity> -> obj.disable() } | ||
.csrf { obj: CsrfConfigurer<HttpSecurity> -> obj.disable() } | ||
.httpBasic { obj: HttpBasicConfigurer<HttpSecurity> -> obj.disable() } | ||
.formLogin { obj: FormLoginConfigurer<HttpSecurity> -> obj.disable() } | ||
|
||
http.authorizeHttpRequests( | ||
Customizer { auth -> | ||
auth | ||
.requestMatchers( | ||
"/", "/login", "/userPage", "/sellerPage", "/adminPage", "/main", | ||
"/v1/members/login", "/signup", "/v1/members/email/**", "/v1/access", | ||
"/v1/categories/**", "/v1/products/**", "/v1/productLines/**", "/v2/productLines/**", | ||
"/productLinePagingSlice", "/productLinePagingOffset", | ||
"/v1/orderdetails", "/v1/orders", "membersPagingOffset", "membersPagingSlice", | ||
"/v1/orderdetails", "/v1/orders", "/v2/orders", "/v3/orders", "/v1/orders/list", | ||
"/v1/orders/list", "/ordersPagingOffset", "/ordersPagingSlice", "/v2/orders/list", | ||
"/v1/seller/orders/**", "/v1/seller/orders", "/v1/orders/**", "/v1/orderdetails/**", | ||
"/swagger-resources/**", "/swagger-ui/**", "/v3/api-docs/**", "/v1/members/auth/**" | ||
).permitAll() | ||
.requestMatchers(HttpMethod.POST, "/v1/members").permitAll() | ||
.requestMatchers(HttpMethod.POST, "/v1/sellers/**").authenticated() | ||
.requestMatchers("/seller/**", "/v1/sellers/**").hasRole("SELLER") | ||
.requestMatchers("/admin/**", "/v1/admin/**").hasRole("ADMIN") | ||
.requestMatchers("v2/members", "v3/members").hasRole("ADMIN") | ||
.requestMatchers(HttpMethod.GET, "/v1/members").hasRole("ADMIN") | ||
.anyRequest().authenticated() | ||
} | ||
) | ||
|
||
//JWT 토큰 인증 방식 사용하기에 session 유지 비활성화 | ||
http.sessionManagement { sessionManagement -> | ||
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) | ||
} | ||
|
||
//UsernamePasswordAuthenticationFilter 대신에 LoginFilter가 실행된다. | ||
//LoginFilter 이전에 jwtAhthenticationFilter가 실행된다. | ||
http.addFilterBefore(jwtAuthenticationFilter, LoginFilter::class.java) | ||
http.addFilterAt( | ||
LoginFilter(authenticationManager(), jwtUtil), | ||
UsernamePasswordAuthenticationFilter::class.java | ||
) | ||
|
||
return http.build() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
src/main/kotlin/org/store/clothstar/common/config/jwt/JwtController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package org.store.clothstar.common.config.jwt | ||
|
||
class JwtController( | ||
private val jwtUtil: JwtUtil, | ||
private val jwtService: JwtService, | ||
) { | ||
} |
36 changes: 36 additions & 0 deletions
36
src/main/kotlin/org/store/clothstar/common/config/jwt/JwtService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package org.store.clothstar.common.config.jwt | ||
|
||
import jakarta.servlet.http.Cookie | ||
import jakarta.servlet.http.HttpServletRequest | ||
import org.store.clothstar.common.error.ErrorCode | ||
import org.store.clothstar.common.error.exception.NotFoundMemberException | ||
import org.store.clothstar.member.repository.AccountRepository | ||
import org.store.clothstar.member.repository.MemberRepository | ||
import java.util.* | ||
|
||
class JwtService( | ||
private val jwtUtil: JwtUtil, | ||
private val memberRepository: MemberRepository, | ||
private val accountRepository: AccountRepository, | ||
) { | ||
fun getRefreshToken(request: HttpServletRequest): String? { | ||
if (request.cookies == null) { | ||
return null | ||
} | ||
|
||
return Arrays.stream(request.cookies) | ||
.filter { cookie: Cookie -> cookie.name == "refreshToken" } | ||
.findFirst() | ||
.map { cookie: Cookie -> cookie.value } | ||
.orElse(null) | ||
} | ||
|
||
fun getAccessTokenByRefreshToken(refreshToken: String): String { | ||
val accountId = jwtUtil.getAccountId(refreshToken) | ||
|
||
val account = accountRepository.findByAccountId(accountId) | ||
?: throw NotFoundMemberException(ErrorCode.NOT_FOUND_ACCOUNT) | ||
|
||
return jwtUtil.createAccessToken(account) | ||
} | ||
} |
Oops, something went wrong.