Skip to content

Commit

Permalink
#170 refactor: user, cs domain 주석 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
chaerlo127 committed Sep 12, 2023
1 parent 3131e74 commit bb07dad
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class WebSecurityConfig(
http
.csrf { c -> c.disable() }
.cors { c -> c.disable() }
// frontend error 처리 401, 403
.exceptionHandling {
e ->
e.authenticationEntryPoint(jwtAuthenticationEntryPoint)
Expand Down
1 change: 0 additions & 1 deletion src/main/kotlin/com/psr/psr/global/jwt/JwtFilter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ class JwtFilter(private val jwtUtils: JwtUtils, private val redisService: RedisS
request.setAttribute("exception", BaseResponse<Any>(e.baseResponseCode))
}
filterChain.doFilter(request, response);

}

/**
Expand Down
32 changes: 23 additions & 9 deletions src/main/kotlin/com/psr/psr/global/jwt/utils/JwtUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import io.jsonwebtoken.*
import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.security.Keys
import io.jsonwebtoken.security.SecurityException
import mu.KotlinLogging
import org.springframework.beans.factory.annotation.Value
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
Expand Down Expand Up @@ -44,24 +43,30 @@ class JwtUtils(
* 토큰 생성
*/
fun createToken(authentication: Authentication, type: Type): TokenDto {
// authorities 생성
val authorities = authentication.authorities.stream()
.map { obj: GrantedAuthority -> obj.authority }
.collect(Collectors.joining(","))

val now = Date().time

// accessToken 생성
val accessToken: String = Jwts.builder()
.setSubject(authentication.name)
.claim(AUTHORIZATION_HEADER, authorities)
.setExpiration(Date(now + ACCESS_TOKEN_EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS512)
.compact()

// refreshToken 생성
val refreshToken: String = Jwts.builder()
.setExpiration(Date(now + REFRESH_TOKEN_EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS512)
.compact()

// redis 에 RefreshToken 저장
redisService.setValue(authentication.name, refreshToken, Duration.ofMillis(REFRESH_TOKEN_EXPIRE_TIME))
// return token
return TokenDto(BEARER_PREFIX + accessToken, BEARER_PREFIX + refreshToken, type.value)
}

Expand All @@ -72,15 +77,15 @@ class JwtUtils(
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token)
return true
} catch (e: SecurityException) {
} catch (e: SecurityException) { // 유효하지 않은 토큰 값인 경우
throw BaseException(BaseResponseCode.INVALID_TOKEN)
} catch (e: MalformedJwtException) {
} catch (e: MalformedJwtException) { // 잘못된 구조의 토큰인 경우
throw BaseException(BaseResponseCode.MALFORMED_TOKEN)
} catch (e: ExpiredJwtException) {
} catch (e: ExpiredJwtException) { // 토큰이 만료가 된 경우
throw BaseException(BaseResponseCode.EXPIRED_TOKEN)
} catch (e: UnsupportedJwtException) {
} catch (e: UnsupportedJwtException) { // 잘못된 형식의 토큰 값인 경우
throw BaseException(BaseResponseCode.UNSUPPORTED_TOKEN)
} catch (e: IllegalArgumentException) {
} catch (e: IllegalArgumentException) { // 토큰 값이 없는 경우
throw BaseException(BaseResponseCode.NULL_TOKEN)
}
}
Expand All @@ -90,15 +95,23 @@ class JwtUtils(
* 토큰 복호화
*/
fun getAuthentication(accessToken: String): Authentication {
// token에서 사용자 id 값 가져오기
val userId = getUserIdFromJWT(accessToken)
// user 불러오기
val userDetails: UserDetails = userDetailsService.loadUserById(userId)
return UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities)
}

/**
* token 에서 사용자 id 값 불러오기
*/
fun getUserIdFromJWT(token: String): Long {
return parseClaims(token).subject.toLong()
}

/**
* token 내 저장된 정보 불러오기
*/
private fun parseClaims(token: String): Claims {
return try {
Jwts.parserBuilder()
Expand All @@ -112,7 +125,7 @@ class JwtUtils(
}

/**
* 토큰 만료
* blacklist 토큰 만료
*/
fun expireToken(token: String, status: String){
val accessToken = token.replace(BEARER_PREFIX, "")
Expand All @@ -121,9 +134,10 @@ class JwtUtils(
}

/**
* Token 남은 시간 return
* black list를 위한 유효시간 없앤 후 return
*/
private fun getExpiration(token: String): Long {
// 유효시간 불러오기
val expiration = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).body.expiration
val now = Date().time
return (expiration.time - now)
Expand All @@ -137,7 +151,7 @@ class JwtUtils(
}

/**
* check Token in Redis DB
* Redis DB 에서 refreshToken 확인
*/
fun validateRefreshToken(userId: Long, refreshToken: String) {
val redisToken = redisService.getValue(userId.toString())
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/psr/psr/user/controller/UserController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ class UserController(
* 사용자 프로필 변경하기
*/
@PostMapping("/profile")
fun postProfile(@AuthenticationPrincipal userAccount: UserAccount, @RequestBody @Validated profileReq: ProfileReq) : BaseResponse<Any> {
userService.postProfile(userAccount.getUser(), profileReq)
fun patchProfile(@AuthenticationPrincipal userAccount: UserAccount, @RequestBody @Validated profileReq: ProfileReq) : BaseResponse<Any> {
userService.patchProfile(userAccount.getUser(), profileReq)
return BaseResponse(BaseResponseCode.SUCCESS)
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/psr/psr/user/dto/request/ProfileReq.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ data class ProfileReq(
message = "한글, 영어, 숫자만 입력해주세요. (10글자)"
)
val nickname: String,
val profileImgUrl: String? = null
val imgUrl: String
)
49 changes: 37 additions & 12 deletions src/main/kotlin/com/psr/psr/user/service/UserService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class UserService(
// 회원가입
@Transactional
fun signUp(signUpReq: SignUpReq): TokenDto {
// 중복 값 확인
// 카테고리 내 중복 값 확인
val categoryCheck = signUpReq.interestList.groupingBy { it }.eachCount().any { it.value > 1 }
if(categoryCheck) throw BaseException(INVALID_USER_INTEREST_COUNT)
// category 의 사이즈 확인
Expand All @@ -87,32 +87,34 @@ class UserService(
if(userRepository.existsByPhoneAndStatus(signUpReq.phone, ACTIVE_STATUS)) throw BaseException(EXISTS_PHONE)
if(userRepository.existsByNicknameAndStatus(signUpReq.nickname, ACTIVE_STATUS)) throw BaseException(EXISTS_NICKNAME)


// 암호화되지 않은 password 값 저장
// 암호화되지 않은 password 값 변수에 저장
val password = signUpReq.password
// password 암호화
val encodedPassword = passwordEncoder.encode(signUpReq.password)
signUpReq.password = encodedPassword
// user 저장
val user = userRepository.save(User.toEntity(signUpReq))
userInterestRepository.saveAll(UserInterest.toInterestListEntity(user, signUpReq))

// 사업자인경우
if (user.type == Type.ENTREPRENEUR){
if(signUpReq.entreInfo == null) throw BaseException(NOT_EMPTY_EID)
businessInfoRepository.save(BusinessInfo.toBusinessEntity(user, signUpReq))
}

// token 생성
return createToken(user, password)
}

// 로그인
@Transactional
fun login(loginReq: LoginReq) : TokenDto{
// 이메일이 일치 확인
val user = userRepository.findByEmailAndStatus(loginReq.email, ACTIVE_STATUS).orElseThrow{BaseException(NOT_EXIST_EMAIL)}
// 비밀번호 일치 확인
if(!passwordEncoder.matches(loginReq.password, user.password)) throw BaseException(INVALID_PASSWORD)
// 알림을 위한 사용자 디바이스 토큰 저장
if (loginReq.deviceToken != null) user.deviceToken = loginReq.deviceToken

// token 생성
return createToken(user, loginReq.password)
}

Expand All @@ -135,17 +137,22 @@ class UserService(

// 사용자 프로필 변경
@Transactional
fun postProfile(user: User, profileReq: ProfileReq) {
fun patchProfile(user: User, profileReq: ProfileReq) {
// 닉네임이 변경이 되었으면
if(user.nickname != profileReq.nickname) {
// todo: 코드 변경 필요 -> 자기 자신 닉네임 제외
if(userRepository.existsByNicknameAndStatus(profileReq.nickname, ACTIVE_STATUS)) throw BaseException(EXISTS_NICKNAME)
user.nickname = profileReq.nickname
}
if(user.imgUrl != profileReq.profileImgUrl) user.imgUrl = profileReq.profileImgUrl
// 프로필 이미지가 변경이 되었으면
if(user.imgUrl != profileReq.imgUrl) user.imgUrl = profileReq.imgUrl
// 변경된 값 저장
userRepository.save(user)
}

// 토큰 자동 토큰 만료 및 RefreshToken 삭제
fun blackListToken(user: User, request: HttpServletRequest, loginStatus: String) {
// header 에서 token 불러오기
val token = getHeaderAuthorization(request)
// 토큰 만료
jwtUtils.expireToken(token, loginStatus);
Expand All @@ -161,18 +168,26 @@ class UserService(

// 사업자 정보 확인 API
fun validateEid(userEidReq: UserEidReq) {
// 공공데이터 Open API 사용자 정보 불러오기
val eidInfo = getEidInfo(userEidReq)
// 검증을 할 수 없는 경우 (사업자를 찾지 못한 경우)
if(eidInfo.valid_cnt == null) throw BaseException(NOT_FOUND_EID)
// 정상 사업자가 아닌 경우
if(eidInfo.data[0].status!!.b_stt_cd != PAY_STATUS) throw BaseException(INVALID_EID)
}

// 공공데이터포털에서 사용자 불러오기
private fun getEidInfo(userEidReq: UserEidReq): BusinessListRes {
val url = EID_URL + serviceKey
// open api 정보 전달 객체 형태로 변경
val businesses = BusinessListReq.toUserEidList(userEidReq)
// json 형식으로 변경
val json = ObjectMapper().writeValueAsString(businesses)
// -> 이미 인코딩이 되어 있는 serviceKey이 재인코딩이 되지 않기 위해
val factory = DefaultUriBuilderFactory(url)
factory.encodingMode = DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY;
factory.encodingMode = DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY

// open api 접근
return WebClient.builder()
.uriBuilderFactory(factory)
.baseUrl(url)
Expand Down Expand Up @@ -227,6 +242,7 @@ class UserService(
// 비밀번호 재설정
@Transactional
fun resetPassword(passwordReq: ResetPasswordReq) {
// 사용자 이메일 있는 경우
val user = userRepository.findByEmailAndStatus(passwordReq.email, ACTIVE_STATUS).orElseThrow{BaseException(NOT_EXIST_EMAIL)}
if(user.phone != passwordReq.phone) throw BaseException(INVALID_PHONE)
if(passwordEncoder.matches(passwordReq.password, user.password)) throw BaseException(DUPLICATE_PASSWORD)
Expand All @@ -239,9 +255,13 @@ class UserService(
// 관심 목록 변경
@Transactional
fun patchWatchLists(user: User, userInterestListReq: UserInterestListDto) {
// 카테고리 내 중복 값 확인
val reqLists = userInterestListReq.interestList!!.map { i -> Category.getCategoryByValue(i.category) }
// category 의 사이즈 확인
if(reqLists.isEmpty() || reqLists.size > 3) throw BaseException(INVALID_USER_INTEREST_COUNT)
// 사용자 관심 목록 불러오기
val userWatchLists = userInterestRepository.findByUserAndStatus(user, ACTIVE_STATUS)
// string list로 변경
val categoryLists = userWatchLists.map { c -> c.category }

// 사용자 관심 목록에 최근 추가한 리스트(REQUEST) 관심 목록이 없다면? => 저장
Expand All @@ -258,7 +278,6 @@ class UserService(
// 최근 추가한 리스트(REQUEST)에 사용자 관심 목록이 없다면? => 삭제
userWatchLists.stream()
.forEach { interest ->
// todo: cascade 적용 후 모두 삭제 되었는지 확인 필요
if(!reqLists.contains(interest.category)) userInterestRepository.delete(interest)
}
}
Expand All @@ -273,16 +292,18 @@ class UserService(
fun checkValidPhone(validPhoneReq: ValidPhoneReq) {
val time = System.currentTimeMillis()
val url = FIRST_URL + MIDDLE_URL + serviceId + FINAL_URL
// -> 이미 인코딩이 되어 있는 serviceKey이 재인코딩이 되지 않기 위해
val factory = DefaultUriBuilderFactory(url)
factory.encodingMode = DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY
// 인증번호 생성
val smsKey = createSmsKey()

// open api 접근
WebClient.builder()
.uriBuilderFactory(factory)
.baseUrl(url)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(TIMESTAMP_HEADER, time.toString())
.defaultHeader(ACCESS_KEY_HEADER, accessKey)
.defaultHeader(ACCESS_KEY_HEADER, accessKey) // accessKey
.defaultHeader(SIGNATURE_HEADER, makeSignature(time))
.build().post()
.bodyValue(SMSReq.toSMSReqDto(validPhoneReq, smsKey, sendPhone))
Expand All @@ -292,11 +313,13 @@ class UserService(
}
.bodyToMono(String::class.java)
.block()
// 휴대폰 smsKey 만료시간
smsUtils.createSmsKey(validPhoneReq.phone, smsKey)
}

// 휴대폰 인증번호 조회
fun checkValidSmsKey(phone: String, smsKey: String) {
// 휴대폰 인증 번호 불러오기
val sms = smsUtils.getSmsKey(phone)
// 인증코드가 같지 않은 경우 예외처리 발생
if(sms != smsKey) throw BaseException(INVALID_SMS_KEY)
Expand All @@ -307,6 +330,7 @@ class UserService(
// 인증번호 확인
checkValidSmsKey(findIdPwReq.phone, findIdPwReq.smsKey)
val user: User = userRepository.findByNameAndPhoneAndStatus(findIdPwReq.name!!, findIdPwReq.phone, ACTIVE_STATUS) ?: throw BaseException(NOT_FOUND_USER)
// 사용자 이메일 전달
return EmailRes.toEmailResDto(user)
}

Expand All @@ -325,7 +349,7 @@ class UserService(
return PostNotiRes.toDto(user.notification)
}

// signature
// signature For post sms
private fun makeSignature(time: Long): String {
val message = StringBuilder()
.append(METHOD)
Expand All @@ -345,6 +369,7 @@ class UserService(
return Base64.encodeBase64String(rawHmac)
}

// 인증번호 생성
fun createSmsKey() : String{
return RandomStringUtils.random(5, false, true);
}
Expand Down

0 comments on commit bb07dad

Please sign in to comment.