Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/GSM-MSG/Bitgouel-Server i…
Browse files Browse the repository at this point in the history
…nto 34-feat/student-activity-information-add
  • Loading branch information
KimTaeO committed Oct 20, 2023
2 parents 81dd9d2 + 2a91dae commit 9d02509
Show file tree
Hide file tree
Showing 19 changed files with 242 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package team.msg.common.aop

import org.aspectj.lang.JoinPoint
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
import org.aspectj.lang.reflect.CodeSignature
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Component
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
import team.msg.common.logger.LoggerDelegator
import java.util.*
import kotlin.collections.HashMap
import kotlin.collections.HashSet

@Aspect
@Component
class HttpLoggingAspect {

private val log by LoggerDelegator()

@Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
fun onRequest() {}

@Around("onRequest()")
@Throws(Throwable::class)
fun logging(proceedingJoinPoint: ProceedingJoinPoint): Any? {
val request = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request
val ip = request.remoteAddr
val method = request.method
val uri = request.requestURI
val sessionId = request.requestedSessionId
val params = request.queryString
val contentType = request.contentType
val userAgent = request.getHeader("User-Agent")
val signature: MethodSignature = proceedingJoinPoint.signature as MethodSignature
val className = signature.declaringType.simpleName
val methodName = signature.name
val headerNames = request.headerNames
val headerSet: MutableSet<String> = HashSet()

while (headerNames.hasMoreElements()) {
val headerName = headerNames.nextElement()
headerSet.add(headerName)
}
val code = UUID.randomUUID()
log.info(
"At {}#{} [Request:{}] IP: {}, Session-ID: {}, URI: {}, Params: {}, Content-Type: {}, User-Agent: {}, Headers: {}, Parameters: {}, Code: {}",
className, methodName, method, ip, sessionId, uri, params, contentType, userAgent, headerSet, params(proceedingJoinPoint), code
)
val result = proceedingJoinPoint.proceed()
when (result) {
is ResponseEntity<*> -> {
log.info(
"At {}#{} [Response:{}] IP: {}, Session-ID: {}, Headers: {}, Response: {}, Status-Code: {}, Code: {}",
className, methodName, method, ip,sessionId, result.headers, result.body, result.statusCode, code
)
}

null -> {
log.info(
"At {}#{} [Response: null] IP: {}, Session-ID: {}, Code: {}",
className, methodName, ip, sessionId, code
)
}

else -> {
throw RuntimeException("유효하지 않은 Controller 반환 타입입니다.")
}
}
return result
}

private fun params(joinPoint: JoinPoint): Map<*,*>? {
val codeSignature = joinPoint.signature as CodeSignature
val parameterNames = codeSignature.parameterNames
val args = joinPoint.args
val params: MutableMap<String,Any> = HashMap()

for (i in parameterNames.indices) {
params[parameterNames[i]] = args[i]
}
return params
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package team.msg.common
package team.msg.common.init

import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.event.EventListener
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ class SecurityUtil(
) {
fun passwordEncode(password: String): String =
passwordEncoder.encode(password)

fun isPasswordMatch(currentPassword: String, encodedPassword: String): Boolean =
passwordEncoder.matches(currentPassword, encodedPassword)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package team.msg.domain.auth.exception

import team.msg.domain.auth.exception.constant.AuthErrorCode
import team.msg.global.error.exception.BitgouelException

class InvalidRefreshTokenException(
message: String
) : BitgouelException(message, AuthErrorCode.INVALID_TOKEN.status){
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package team.msg.domain.auth.exception

import team.msg.domain.auth.exception.constant.AuthErrorCode
import team.msg.global.error.exception.BitgouelException

class MisMatchPasswordException(
message: String
) : BitgouelException(message, AuthErrorCode.MISMATCH_PASSWORD.status)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package team.msg.domain.auth.exception

import team.msg.domain.auth.exception.constant.AuthErrorCode
import team.msg.global.error.exception.BitgouelException

class RefreshTokenNotFoundException(
message: String
) : BitgouelException(message, AuthErrorCode.REFRESH_TOKEN_NOT_FOUND.status)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package team.msg.domain.auth.exception

import team.msg.domain.auth.exception.constant.AuthErrorCode
import team.msg.global.error.exception.BitgouelException

class UnApprovedUserException(
message: String
) : BitgouelException(message, AuthErrorCode.UNAPPROVED_USER.status)
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@ enum class AuthErrorCode(
val status: Int
) {
ALREADY_EXIST_EMAIL("이미 가입된 이메일입니다.", 409),
ALREADY_EXIST_PHONE_NUMBER("이미 가입된 전화번호입니다.", 409)
ALREADY_EXIST_PHONE_NUMBER("이미 가입된 전화번호입니다.", 409),
MISMATCH_PASSWORD("일치하지 않는 비밀번호입니다.", 401),
UNAPPROVED_USER("아직 회원가입 대기 상태입니다.", 403),
INVALID_TOKEN("유효하지 않은 토큰입니다.", 401),
REFRESH_TOKEN_NOT_FOUND("존재하지 않는 리프레시 토큰입니다.", 404)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ interface AuthRequestMapper {
fun professorSignUpWebRequestToDto(webRequest: ProfessorSignUpWebRequest): ProfessorSignUpRequest
fun governmentSignUpWebRequestToDto(webRequest: GovernmentSignUpWebRequest): GovernmentSignUpRequest
fun companyInstructorSignUpWebRequestToDto(webRequest: CompanyInstructorSignUpWebRequest): CompanyInstructorSignUpRequest
fun loginWebRequestToDto(webRequest: LoginWebRequest): LoginRequest
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,13 @@ class AuthRequestMapperImpl : AuthRequestMapper {
clubName = webRequest.clubName,
company = webRequest.company
)

/**
* 로그인 Web Request 를 애플리케이션 영역에서 사용될 Dto 로 매핑합니다.
*/
override fun loginWebRequestToDto(webRequest: LoginWebRequest): LoginRequest =
LoginRequest(
email = webRequest.email,
password = webRequest.password
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package team.msg.domain.auth.presentation
import javax.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import team.msg.domain.auth.mapper.AuthRequestMapper
import team.msg.domain.auth.presentation.data.response.TokenResponse
import team.msg.domain.auth.presentation.data.web.*
import team.msg.domain.auth.service.AuthService

Expand Down Expand Up @@ -46,4 +49,16 @@ class AuthController(
authService.companyInstructorSignUp(authRequestMapper.companyInstructorSignUpWebRequestToDto(request))
return ResponseEntity.status(HttpStatus.CREATED).build()
}

@PostMapping("/login")
fun login(@RequestBody @Valid request: LoginWebRequest): ResponseEntity<TokenResponse> {
val response = authService.login(authRequestMapper.loginWebRequestToDto(request))
return ResponseEntity.ok(response)
}

@PatchMapping
fun reissueToken(@RequestHeader("RefreshToken") refreshToken: String): ResponseEntity<TokenResponse> {
val response = authService.reissueToken(refreshToken)
return ResponseEntity.ok(response)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package team.msg.domain.auth.presentation.data.request

data class LoginRequest(
val email: String,
val password: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package team.msg.domain.auth.presentation.data.web

import javax.validation.constraints.Email
import javax.validation.constraints.NotNull
import javax.validation.constraints.Pattern

data class LoginWebRequest(
@field:Email
@field:NotNull
val email: String,

@field:Pattern(regexp = "^(?=.*[A-Za-z0-9])[A-Za-z0-9!@#\\\\\$%^&*]{8,24}\$")
val password: String
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package team.msg.domain.auth.service

import team.msg.domain.auth.presentation.data.request.*
import team.msg.domain.auth.presentation.data.response.TokenResponse

interface AuthService {
fun studentSignUp(studentSignUpRequest: StudentSignUpRequest)
fun teacherSignUp(teacherSignUpRequest: TeacherSignUpRequest)
fun professorSignUp(professorSignUpRequest: ProfessorSignUpRequest)
fun governmentSignUp(governmentSignUpRequest: GovernmentSignUpRequest)
fun companyInstructorSignUp(companyInstructorSignUpRequest: CompanyInstructorSignUpRequest)
fun login(request: LoginRequest): TokenResponse
fun reissueToken(refreshToken: String): TokenResponse
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package team.msg.domain.auth.service

import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import team.msg.common.enum.ApproveStatus
import team.msg.common.util.SecurityUtil
import team.msg.domain.auth.exception.AlreadyExistEmailException
import team.msg.domain.auth.exception.AlreadyExistPhoneNumberException
import team.msg.domain.auth.exception.InvalidRefreshTokenException
import team.msg.domain.auth.exception.MisMatchPasswordException
import team.msg.domain.auth.exception.RefreshTokenNotFoundException
import team.msg.domain.auth.exception.UnApprovedUserException
import team.msg.domain.auth.presentation.data.request.*
import team.msg.domain.auth.presentation.data.response.TokenResponse
import team.msg.domain.auth.repository.RefreshTokenRepository
import team.msg.domain.club.exception.ClubNotFoundException
import team.msg.domain.club.model.Club
import team.msg.domain.club.repository.ClubRepository
Expand All @@ -25,8 +32,11 @@ import team.msg.domain.student.repository.StudentRepository
import team.msg.domain.teacher.model.Teacher
import team.msg.domain.teacher.repository.TeacherRepository
import team.msg.domain.user.enums.Authority
import team.msg.domain.user.exception.UserNotFoundException
import team.msg.domain.user.model.User
import team.msg.domain.user.repository.UserRepository
import team.msg.global.security.jwt.JwtTokenGenerator
import team.msg.global.security.jwt.JwtTokenParser
import java.util.*

@Service
Expand All @@ -39,11 +49,14 @@ class AuthServiceImpl(
private val teacherRepository: TeacherRepository,
private val professorRepository: ProfessorRepository,
private val governmentRepository: GovernmentRepository,
private val companyInstructorRepository: CompanyInstructorRepository
private val companyInstructorRepository: CompanyInstructorRepository,
private val jwtTokenGenerator: JwtTokenGenerator,
private val jwtTokenParser: JwtTokenParser,
private val refreshTokenRepository: RefreshTokenRepository
) : AuthService {

/**
* 학생 회원가입을 처리해주는 비지니스 로직입니다.
* 학생 회원가입을 처리하는 비지니스 로직입니다.
* @param StudentSignUpRequest
*/
@Transactional(rollbackFor = [Exception::class])
Expand Down Expand Up @@ -73,7 +86,7 @@ class AuthServiceImpl(
}

/**
* 취동샘 회원가입을 처리해주는 비지니스 로직입니다.
* 취동샘 회원가입을 처리하는 비지니스 로직입니다.
* @param TeacherSignUpRequest
*/
@Transactional(rollbackFor = [Exception::class])
Expand All @@ -97,7 +110,7 @@ class AuthServiceImpl(
}

/**
* 대학교수 회원가입을 처리해주는 비지니스 로직입니다.
* 대학교수 회원가입을 처리하는 비지니스 로직입니다.
* @param ProfessorSignUpRequest
*/
@Transactional(rollbackFor = [Exception::class])
Expand All @@ -122,9 +135,10 @@ class AuthServiceImpl(
}

/**
* 유관 기관 회원가입을 처리해주는 비지니스 로직입니다.
* 유관 기관 회원가입을 처리하는 비지니스 로직입니다.
* @param GovernmentSignUpRequest
*/
@Transactional(rollbackFor = [Exception::class])
override fun governmentSignUp(request: GovernmentSignUpRequest) {
val user = createUser(
request.email,
Expand All @@ -146,9 +160,10 @@ class AuthServiceImpl(
}

/**
* 기업 강사 회원가입을 처리해주는 비지니스 로직입니다.
* 기업 강사 회원가입을 처리하는 비지니스 로직입니다.
* @param CompanyInstructorSignUpRequest
*/
@Transactional(rollbackFor = [Exception::class])
override fun companyInstructorSignUp(request: CompanyInstructorSignUpRequest) {
val user = createUser(request.email, request.name, request.phoneNumber, request.password, Authority.ROLE_COMPANY_INSTRUCTOR)

Expand All @@ -164,7 +179,42 @@ class AuthServiceImpl(
}

/**
* 유저 생성과 검증을 처리해주는 private 메서드입니다.
* 로그인을 처리하는 비지니스 로직입니다.
* @param LoginRequest
*/
@Transactional(readOnly = true)
override fun login(request: LoginRequest): TokenResponse {
val user = userRepository.findByEmail(request.email)
?: throw UserNotFoundException("존재하지 않는 유저입니다.")

if (!securityUtil.isPasswordMatch(request.password, user.password))
throw MisMatchPasswordException("비말번호가 일치하지 않습니다. info : [ password = ${request.password} ]")

if (user.approveStatus == ApproveStatus.PENDING)
throw UnApprovedUserException("아직 회원가입 대기 중인 유저입니다. info : [ user = ${user.name} ]")

return jwtTokenGenerator.generateToken(user.id, user.authority)
}

/**
* 토큰 재발급을 처리하는 메서드입니다.
* @param refreshToken
*/
override fun reissueToken(refreshToken: String): TokenResponse {
val refreshToken = jwtTokenParser.parseRefreshToken(refreshToken)
?: throw InvalidRefreshTokenException("유효하지 않은 리프레시 토큰입니다. info : [ refreshToken = $refreshToken ]")

val token = refreshTokenRepository.findByIdOrNull(refreshToken)
?: throw RefreshTokenNotFoundException("존재하지 않는 리프레시 토큰입니다. info : [ refreshToken = $refreshToken ]")

val user = userRepository.findByIdOrNull(token.userId)
?: throw UserNotFoundException("존재하지 않는 유저입니다. info : [ userId = ${token.userId} ]")

return jwtTokenGenerator.generateToken(user.id, user.authority)
}

/**
* 유저 생성과 검증을 처리하는 private 메서드입니다.
* @param email, name, phoneNumber, password, authority
*/
private fun createUser(email: String, name: String, phoneNumber: String, password: String, authority: Authority): User {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class SecurityConfig(
.mvcMatchers(HttpMethod.POST, "/auth/professor").permitAll()
.mvcMatchers(HttpMethod.POST, "/auth/government").permitAll()
.mvcMatchers(HttpMethod.POST, "/auth/company-instructor").permitAll()
.mvcMatchers(HttpMethod.POST, "/auth/login").permitAll()
.mvcMatchers(HttpMethod.PATCH, "/auth").permitAll()

// activity
.mvcMatchers(HttpMethod.POST, "/activity").hasRole(STUDENT)
Expand Down
Loading

0 comments on commit 9d02509

Please sign in to comment.