Skip to content

#15,16 - Security 설정, 회원가입 개발 #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
eaef785
chore: Spring Security gradle 추가
sectionr0 Jul 13, 2024
e52037e
feat: User 도메인 정보 추가
sectionr0 Jul 13, 2024
fa58236
feat: PrincipalDetail 설정
sectionr0 Jul 13, 2024
8ee5950
feat: 시큐리티 필터 설정 및 JWT 개발
sectionr0 Jul 15, 2024
bf66b7c
feat: refreshToken 개발
sectionr0 Jul 15, 2024
3bff1a2
feat: JWT Provider 개발
sectionr0 Jul 15, 2024
da78e47
refactor: BaseEntity 변경
sectionr0 Jul 16, 2024
6846bd5
refactor: School 도메인 리펙토링
sectionr0 Jul 16, 2024
92f29c8
feat: School Repositroy, mapper 개발
sectionr0 Jul 16, 2024
66f6489
feat: Profile Repositroy, mapper 개발
sectionr0 Jul 16, 2024
1bc7c8e
refactor: Setting, FCM 도메인 수정
sectionr0 Jul 16, 2024
412af65
feat: UserConsent 도메인 추가
sectionr0 Jul 16, 2024
6efb850
refactor: Social 도메인 수정
sectionr0 Jul 16, 2024
8f61085
refactor: User 도메인 수정 및 Role 추가
sectionr0 Jul 16, 2024
8cd802b
feat: Auth Dto 설정
sectionr0 Jul 16, 2024
9c8c9ac
refactor: School 도메인 리팩토링
sectionr0 Jul 16, 2024
35659a6
refactor: RefreshToken create, update 설정 및 서비스 추가
sectionr0 Jul 16, 2024
e8056ed
feat: Redis 모듈 추가, 설정 및 AuthDate 저장
sectionr0 Jul 16, 2024
e17f041
refactor: DTO 폴더 경로 추가
sectionr0 Jul 16, 2024
adab3e5
feat: SecurityUtils 추가
sectionr0 Jul 16, 2024
bb46e8d
feat: AuthService 추가
sectionr0 Jul 16, 2024
30c01b2
feat: Auth Controller 추가 및 url 설정 변경
sectionr0 Jul 16, 2024
dbc670f
refactor: rebase 후 코드 리팩토링
sectionr0 Jul 16, 2024
b8f61d4
test: service Test 코드 작성
sectionr0 Jul 16, 2024
d71ec8b
fix: baseEntity notnull 조건 제거
sectionr0 Jul 16, 2024
1de8e82
fix: VoteServiceTest 에러 주석 처리
sectionr0 Jul 16, 2024
c4b4000
test: 주석 제거
kpeel5839 Jul 17, 2024
810f5af
chore: 서브모듈에 jwt 추가
kpeel5839 Jul 17, 2024
2acf2bd
refactor: gitmodules 수정 이후, 이전 backend-submodule 삭제
kpeel5839 Jul 17, 2024
aa52eac
refactor: submoudle 이름 config로 변경
kpeel5839 Jul 17, 2024
6d73bee
refactor: LocalDateTime.now() 모킹
kpeel5839 Jul 17, 2024
991d491
refactor: 401에러 예외 메시지 설정, cors 주소, delete에 트랜잭션 추가
sectionr0 Jul 17, 2024
652bf3f
refactor: api prefix 제거
sectionr0 Jul 17, 2024
8f9dc12
refactor: refreshToken test에 있는 createdAt 수정
sectionr0 Jul 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "app/src/main/resources/backend-submodule"]
path = app/src/main/resources/backend-submodule
url = https://github.com/yapp-wespot/backend-submodule.git
[submodule "app/src/main/resources/config"]
path = app/src/main/resources/config
url = https://github.com/yapp-wespot/config.git
9 changes: 9 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ dependencies {
implementation(project(":domain"))
implementation(project(":core"))
implementation(project(":infrastructure:mysql"))
implementation(project(":infrastructure:redis"))


// https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api
testImplementation("io.jsonwebtoken:jjwt-api:0.11.2")
testImplementation("io.jsonwebtoken:jjwt-impl:0.11.2")
testImplementation("io.jsonwebtoken:jjwt-jackson:0.11.2")

testImplementation("io.rest-assured:rest-assured")


}
71 changes: 71 additions & 0 deletions app/src/main/kotlin/com/wespot/auth/AuthController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.wespot.auth

import com.wespot.auth.dto.request.AuthLoginRequest
import com.wespot.auth.dto.request.RefreshTokenRequest
import com.wespot.auth.dto.request.SignUpRequest
import com.wespot.auth.dto.response.SignUpResponse
import com.wespot.auth.dto.response.TokenResponse
import com.wespot.auth.service.AuthService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/v1/auth")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자기야 이거 api 그냥 넣을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 ㅋㅋ 저거 안뺐네요,, 편한대로?!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단 빼시죵!

class AuthController(
private val authService: AuthService
) {

@PostMapping("/login")
fun signIn(
@RequestBody request: AuthLoginRequest
): Any {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

자기야 Function 내부에는 위, 아래 공백 안두어도 괜찮을 것 같아요!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위 아래 공백은, 지금 작업하시기에는 시간이 걸리니, 차차 제거해나가도록 하시죠!

val response = authService.socialAccess(request)

return if (response is SignUpResponse) {
ResponseEntity.status(HttpStatus.ACCEPTED).body(response)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거는 아직 사용자가 소셜로그인만 하고 회원가입을 안한 상태인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞아요 이 이후에 회원가입 진행하게돼요!

} else {
ResponseEntity.ok()
.body(response)
}

}

@PostMapping("/signup")
fun signUp(
@RequestBody request: SignUpRequest
): ResponseEntity<TokenResponse> {

val signUp = authService.signUp(request)

return ResponseEntity.ok()
.body(signUp)

}

@PostMapping("/reissue")
fun reissue(
@RequestBody request: RefreshTokenRequest
): ResponseEntity<TokenResponse> {

val response = authService.reIssueToken(request)

return ResponseEntity.ok()
.body(response)

}

@PostMapping("/revoke")
fun revoke(): ResponseEntity<Unit> {

authService.revoke()

return ResponseEntity.noContent().build()

}

}
76 changes: 76 additions & 0 deletions app/src/main/kotlin/com/wespot/config/security/CustomUrlFilter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.wespot.config.security

import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ProblemDetail
import org.springframework.stereotype.Component
import org.springframework.util.AntPathMatcher
import org.springframework.web.filter.OncePerRequestFilter
import java.net.URI
import kotlin.text.Charsets.UTF_8

@Component
class CustomUrlFilter(
private val objectMapper: ObjectMapper
) : OncePerRequestFilter() {

private val antPathMatcher = AntPathMatcher()

private val validUrlPatterns = listOf(
"/health",
"/",
"/api/v1/auth/reissue",
"/api/v1/auth/login",
"/api/v1/auth/signup",
"/api/v1/auth/revoke",
)
Comment on lines +23 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앞으로 여기다가 url 추가하면 되겠네요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞아요 근데 이것도 조금 불편할 것 같기도 해서 개선해보는거 나중에 시도해볼게요!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네네!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뭔가 메타데이터 가져와서 하는 방법이 있을 것 같은데

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아니면 리플렉션으로도 가능할 것 같기도 하고오옹


override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {

if (!isValidUrl(request)) {
handleInvalidUrl(request, response)
return
}
filterChain.doFilter(request, response)

}

private fun isValidUrl(request: HttpServletRequest): Boolean {

val requestUri = request.requestURI

return validUrlPatterns.any { antPathMatcher.match(it, requestUri) }

}

private fun handleInvalidUrl(
request: HttpServletRequest,
response: HttpServletResponse
) {

response.contentType = MediaType.APPLICATION_JSON_VALUE
response.characterEncoding = UTF_8.name()
response.status = HttpStatus.NOT_FOUND.value()

val body = objectMapper.writeValueAsString(
ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND,
NoSuchFieldException("잘못된 URL입니다.").message!!,
).apply {
type = URI.create("/errors/not-found")
instance = URI.create(request.requestURI)
}
)

response.writer.write(body)

}
}
Comment on lines +54 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

키야 좋네요 👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.wespot.config.security

import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ProblemDetail
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.stereotype.Component
import java.net.URI
import kotlin.text.Charsets.UTF_8

@Component
class JwtAccessDeniedHandler(
private val objectMapper: ObjectMapper
) : AccessDeniedHandler {
override fun handle(
request: HttpServletRequest,
response: HttpServletResponse,
exception: AccessDeniedException
) {

response.contentType = MediaType.APPLICATION_JSON_VALUE
response.characterEncoding = UTF_8.name()
response.status = HttpStatus.FORBIDDEN.value()

val body = objectMapper.writeValueAsString(
ProblemDetail.forStatusAndDetail(
HttpStatus.UNAUTHORIZED,
AccessDeniedException("자신의 것만 가능합니다").message!!,
).apply {
type = URI.create("/errors/forbidden")
instance = URI.create(request.requestURI)
}
)
response.writer.write(body)

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.wespot.config.security

import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ProblemDetail
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.stereotype.Component
import java.net.URI
import kotlin.text.Charsets.UTF_8

@Component
class JwtAuthenticationEntryPoint(
private val objectMapper: ObjectMapper
) : AuthenticationEntryPoint {
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException?
) {

response.contentType = MediaType.APPLICATION_JSON_VALUE
response.characterEncoding = UTF_8.name()
response.status = HttpStatus.UNAUTHORIZED.value()

val body = objectMapper.writeValueAsString(
ProblemDetail.forStatusAndDetail(
HttpStatus.UNAUTHORIZED,
BadCredentialsException("").message!!,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외 메시지 추가해야할 것 같아요!

).apply {
type = URI.create("/errors/unauthenticated")
instance = URI.create(request.requestURI)
}
)
response.writer.write(body)

}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.wespot.config.security

import com.wespot.auth.JwtTokenInfo
import com.wespot.auth.port.`in`.AuthenticationUseCase
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component
import org.springframework.util.StringUtils
import org.springframework.web.filter.OncePerRequestFilter

@Component
class JwtAuthenticationFilter(
private val authenticationUseCase: AuthenticationUseCase,
private val jwtAuthenticationEntryPoint: JwtAuthenticationEntryPoint,
) : OncePerRequestFilter() {

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val token = extractToken(request)
if (token != null && StringUtils.hasText(token)) {
try {
authenticateUserByToken(token)
} catch (e: BadCredentialsException) {
jwtAuthenticationEntryPoint.commence(request, response, authException = null)
return
}
}
filterChain.doFilter(request, response)
}

private fun authenticateUserByToken(token: String) {
try {
val authentication = authenticationUseCase.getAuthentication(token)
SecurityContextHolder.getContext().authentication = authentication
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이게 컨텍스트에 넣는거군요

} catch (e: BadCredentialsException) {
SecurityContextHolder.clearContext()
}
}

private fun extractToken(request: HttpServletRequest): String? {
val bearerToken = request.getHeader(JwtTokenInfo.AUTHORIZATION_HEADER)
return if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(JwtTokenInfo.BEARER_TYPE)) {
bearerToken.substring(JwtTokenInfo.BEARER_TYPE.length).trim()
} else null
}
}
Loading