Skip to content
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

[feat] 회원가입 API 생성 #14

Merged
merged 7 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 14 additions & 3 deletions src/main/kotlin/com/psr/psr/global/Constant.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
package com.psr.psr.global

class Constant {
companion object JWT{
const val AUTHORIZATION_HEADER = "Authorization"
const val BEARER_PREFIX: String = "Bearer "
class JWT{
companion object JWT{
const val AUTHORIZATION_HEADER = "Authorization"
const val BEARER_PREFIX: String = "Bearer "
}
}

class User{
companion object User{
// 비밀번호 (숫자, 문자, 특수문자 포함 8~15자리 이내)
const val PASSWORD_VALIDATION = "^.*(?=^.{8,15}\$)(?=.*\\d)(?=.*[a-zA-Z])(?=.*[!@#\$%^&+=]).*\$"
const val EMAIL_VALIDATION = "^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}\$"
const val PHONE_VALIDATION = "^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})\$"
}
}

}
13 changes: 9 additions & 4 deletions src/main/kotlin/com/psr/psr/global/config/WebSecurityConfig.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.psr.psr.global.config

import com.psr.psr.global.jwt.UserDetailsServiceImpl
import com.psr.psr.global.jwt.exception.JwtAccessDeniedHandler
import com.psr.psr.global.jwt.exception.JwtAuthenticationEntryPoint
import com.psr.psr.global.jwt.utils.JwtUtils
Expand All @@ -9,16 +8,22 @@ import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
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.util.matcher.AntPathRequestMatcher

@Configuration
@EnableWebSecurity
class WebSecurityConfig(
private val userDetailsService: UserDetailsServiceImpl,
private val jwtUtils: JwtUtils,
private val jwtAuthenticationEntryPoint: JwtAuthenticationEntryPoint,
private val jwtAccessDeniedHandler: JwtAccessDeniedHandler
) {
@Bean
fun PasswordEncoder() : PasswordEncoder {
return BCryptPasswordEncoder()
}

@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
Expand All @@ -35,8 +40,8 @@ class WebSecurityConfig(
// token 없이 사용이 가능한 api url 작성
.authorizeHttpRequests { c ->
c.requestMatchers("/global").permitAll()
c.requestMatchers("/users/login").permitAll()
c.requestMatchers("/users/signup").permitAll()
c.requestMatchers(AntPathRequestMatcher("/users/login")).permitAll()
c.requestMatchers(AntPathRequestMatcher("/users/signup")).permitAll()
c.anyRequest().authenticated()
}
.apply(JwtSecurityConfig(jwtUtils))
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/psr/psr/global/dto/BaseResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class BaseResponse<T> {
constructor(result: T) {
this.code = BaseResponseCode.SUCCESS.status.value()
this.message = BaseResponseCode.SUCCESS.message
this.data = data
this.data = result
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이 부분 변경 됐어요 !
result 가 constructor에 반영이 안돼서 !

}

// fail
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/psr/psr/global/exception/BaseRes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package com.psr.psr.global.exception
import org.springframework.http.HttpStatus

data class BaseRes(
val status: HttpStatus,
val status: Int,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요 부분도 에러 발생하면 400 에러가 나야하는데 BAD_REQUEST 라고 return 되길래,
숫자로 변경해뒀습니당

val message: String?
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,22 @@ enum class BaseResponseCode(status: HttpStatus, message: String) {
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰 값입니다."),
UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "잘못된 형식의 토큰 값입니다."),
MALFORMED_TOKEN(HttpStatus.UNAUTHORIZED, "잘못된 구조의 토큰 값입니다."),
EXPIRED_TOKEN(HttpStatus.FORBIDDEN, "만료된 토큰 값입니다.");
EXPIRED_TOKEN(HttpStatus.FORBIDDEN, "만료된 토큰 값입니다."),

// user
INVALID_EMAIL(HttpStatus.BAD_REQUEST, "올바르지 않은 이메일 형식입니다."),
INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "올바르지 않은 비밀번호 형식입니다."),
INVALID_PHONE(HttpStatus.BAD_REQUEST, "올바르지 않은 휴대폰 형식입니다."),
EXISTS_PHONE(HttpStatus.BAD_REQUEST, "이미 가입되어 있는 휴대폰 번호입니다."),
EXISTS_EMAIL(HttpStatus.BAD_REQUEST, "이미 가입되어 있는 이메일입니다."),
EXISTS_NICKNAME(HttpStatus.BAD_REQUEST, "이미 가입되어 있는 닉네임입니다."),

// User - type
INVALID_USER_TYPE_NAME(HttpStatus.BAD_REQUEST, "올바르지 않은 사용자 역할입니다."),
INVALID_USER_CATEGORY(HttpStatus.BAD_REQUEST, "올바르지 않은 사용자 카테고리입니다."),

// User - category
INVALID_USER_INTEREST_COUNT(HttpStatus.BAD_REQUEST, "사용자 관심 주제는 1개이상, 3개 이하여야하며, 중복된 값이 포함되어 있지 않아야 합니다");

val status: HttpStatus = status
val message: String = message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ class ExceptionHandler {
@ExceptionHandler(BaseException::class)
protected fun handleBaseException(e: BaseException): ResponseEntity<BaseRes>{
return ResponseEntity.status(e.baseResponseCode.status)
.body(BaseRes(e.baseResponseCode.status, e.baseResponseCode.message))
.body(BaseRes(e.baseResponseCode.status.value(), e.baseResponseCode.message))
}
}
4 changes: 2 additions & 2 deletions src/main/kotlin/com/psr/psr/global/jwt/JwtFilter.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.psr.psr.global.jwt

import com.psr.psr.global.Constant.JWT.AUTHORIZATION_HEADER
import com.psr.psr.global.Constant.JWT.BEARER_PREFIX
import com.psr.psr.global.Constant.JWT.JWT.AUTHORIZATION_HEADER
import com.psr.psr.global.Constant.JWT.JWT.BEARER_PREFIX
import com.psr.psr.global.dto.BaseResponse
import com.psr.psr.global.exception.BaseException
import com.psr.psr.global.jwt.utils.JwtUtils
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class UserDetailsImpl(val user:User) :UserDetails {
val grantedAuthority = SimpleGrantedAuthority(user.type.name)
return mutableListOf(grantedAuthority)
}

fun getUserId() : Long = user.id!!
override fun getPassword(): String = user.password

override fun getUsername(): String = user.id.toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ 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.springframework.transaction.annotation.Transactional

@Service
class UserDetailsServiceImpl(private val userRepository: UserRepository) :UserDetailsService {
override fun loadUserByUsername(username: String?): UserDetails {
val user:User = userRepository.findByIdOrNull(username?.toLong() ?: 0L) ?: throw UsernameNotFoundException("사용자 id를 찾을 수 없습니다.")
return UserDetailsImpl(user)
}

@Transactional
fun loadUserById(id: Long): UserDetails {
val user = userRepository.findById(id).orElseThrow { UsernameNotFoundException("User not found with id : $id") }
return UserDetailsImpl(user)
}
chaerlo127 marked this conversation as resolved.
Show resolved Hide resolved
}
4 changes: 3 additions & 1 deletion src/main/kotlin/com/psr/psr/global/jwt/dto/TokenRes.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package com.psr.psr.global.jwt.dto

data class TokenRes(val accessToken: String, val refreshToken: String)
import com.psr.psr.user.entity.Type

data class TokenRes(val accessToken: String, val refreshToken: String, val type: String)
19 changes: 11 additions & 8 deletions src/main/kotlin/com/psr/psr/global/jwt/utils/JwtUtils.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.psr.psr.global.jwt.utils

import com.psr.psr.global.Constant.JWT.BEARER_PREFIX
import com.psr.psr.global.Constant.JWT.AUTHORIZATION_HEADER
import com.psr.psr.global.Constant.JWT.JWT.BEARER_PREFIX
import com.psr.psr.global.Constant.JWT.JWT.AUTHORIZATION_HEADER
import com.psr.psr.global.exception.BaseException
import com.psr.psr.global.exception.BaseResponseCode
import com.psr.psr.global.jwt.UserDetailsServiceImpl
import com.psr.psr.global.jwt.dto.TokenRes
import com.psr.psr.user.entity.Type
import io.jsonwebtoken.*
import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.security.Keys
Expand Down Expand Up @@ -39,7 +40,7 @@ class JwtUtils(
/**
* 토큰 생성
*/
fun createToken(authentication: Authentication): TokenRes {
fun createToken(authentication: Authentication, type: Type): TokenRes {
val authorities = authentication.authorities.stream()
.map { obj: GrantedAuthority -> obj.authority }
.collect(Collectors.joining(","))
Expand All @@ -57,7 +58,7 @@ class JwtUtils(
.signWith(key, SignatureAlgorithm.HS512)
.compact()

return TokenRes(BEARER_PREFIX + accessToken, BEARER_PREFIX + refreshToken)
return TokenRes(BEARER_PREFIX + accessToken, BEARER_PREFIX + refreshToken, type.value)
}

/**
Expand Down Expand Up @@ -85,13 +86,15 @@ class JwtUtils(
* 토큰 복호화
*/
fun getAuthentication(accessToken: String): Authentication {
val claims = parseClaims(accessToken)
// todo: 에러 추가
if (claims[AUTHORIZATION_HEADER] == null) logger.info("토큰 복호화 error")
val userDetails: UserDetails = userDetailsService.loadUserByUsername(claims.subject)
val userId = getUserIdFromJWT(accessToken)
val userDetails: UserDetails = userDetailsService.loadUserById(userId)
return UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities)
}

fun getUserIdFromJWT(token: String): Long {
return parseClaims(token).subject.toLong()
}

private fun parseClaims(token: String): Claims {
return Jwts.parserBuilder()
.setSigningKey(key)
Expand Down
15 changes: 13 additions & 2 deletions src/main/kotlin/com/psr/psr/user/controller/UserController.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package com.psr.psr.user.controller

import com.psr.psr.global.dto.BaseResponse
import com.psr.psr.global.exception.BaseResponseCode
import com.psr.psr.global.jwt.dto.TokenRes
import com.psr.psr.user.dto.SignUpReq
import com.psr.psr.user.service.UserService
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/users")
class UserController(
private val userService: UserService
) {
/**
* 회원가입
*/
@PostMapping("/signup")
@ResponseBody
fun signUp (@RequestBody signUpReq: SignUpReq) : BaseResponse<TokenRes>{
chaerlo127 marked this conversation as resolved.
Show resolved Hide resolved
return BaseResponse(userService.signUp(signUpReq))
}
}
40 changes: 40 additions & 0 deletions src/main/kotlin/com/psr/psr/user/dto/SignUpReq.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.psr.psr.user.dto

import com.psr.psr.user.entity.*
import java.util.stream.Collector
import java.util.stream.Collectors




data class SignUpReq (
val email: String,
var password: String,
val type: String,
val phone: String,
val imgKey: String,
val nickname: String,
val marketing: Boolean,
val notification: Boolean,
val interestList: List<UserInterestReq>
) {
fun toEntity(): User {
return User(email = email,
password = password,
type = Type.getTypeByName(type),
phone = phone,
imgKey = imgKey,
provider = Provider.LOCAL,
marketing = marketing,
notification = notification,
nickname = nickname)
}

fun toInterestEntity(user: User): List<UserInterest> {
return interestList.stream()
.map { i ->
UserInterest(category = Category.getCategoryByName(i.category),
user = user)
}.collect(Collectors.toList())
}
}
12 changes: 12 additions & 0 deletions src/main/kotlin/com/psr/psr/user/dto/UserInterestReq.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.psr.psr.user.dto

import com.psr.psr.user.entity.Category

data class UserInterestReq (
val category: String
){
fun checkInterestCategory() : Category{
return Category.getCategoryByName(category)
}

}
12 changes: 11 additions & 1 deletion src/main/kotlin/com/psr/psr/user/entity/Category.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.psr.psr.user.entity

import com.psr.psr.global.exception.BaseException
import com.psr.psr.global.exception.BaseResponseCode

enum class Category(val value: String) {
BROADCAST_PRODUCT("방송가능 상품소싱"),
SHOW_HOST_ADVERTISE("쇼호스트 구인"),
Expand All @@ -9,5 +12,12 @@ enum class Category(val value: String) {
VIDEO_EDITING("영상편집"),
INSTRUCTOR_MATCHING("강사매칭"),
SNS_MARKETING("SNS 마케팅"),
PROMOTION_DESIGN("홍보물 디자인")
PROMOTION_DESIGN("홍보물 디자인");

companion object {
fun getCategoryByName(name: String): Category {
return enumValues<Category>().find { it.value == name }
?: throw BaseException(BaseResponseCode.INVALID_USER_CATEGORY)
}
}
Comment on lines +17 to +22
Copy link
Member

Choose a reason for hiding this comment

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

감사.❤️‍🔥

}
13 changes: 12 additions & 1 deletion src/main/kotlin/com/psr/psr/user/entity/Type.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package com.psr.psr.user.entity

import com.psr.psr.global.exception.BaseException
import com.psr.psr.global.exception.BaseResponseCode


enum class Type(val value: String) {
GENERAL("일반"),
ENTREPRENEUR("사업자"),
SHOW_HOST("쇼호스트"),
MANAGER("관리자")
MANAGER("관리자");

companion object {
fun getTypeByName(name: String): Type {
return enumValues<Type>().find { it.value == name }
?: throw BaseException(BaseResponseCode.INVALID_USER_TYPE_NAME)
}
}
}
6 changes: 3 additions & 3 deletions src/main/kotlin/com/psr/psr/user/entity/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import jakarta.persistence.*
import org.jetbrains.annotations.NotNull

@Entity
data class User(
class User(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long,
var id: Long? = null,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

찾아보니까 kotlin은 따로 builder가 없대요 !
객체 생성할 때 필수적이지 않은 경우에는 이렇게 null 일 수 있다고 적어놔야하더라구요
아니면 필수 값으로 착각하여 에러 발생함니당

Copy link
Member

Choose a reason for hiding this comment

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

팁줍줍


@NotNull
@Column(length = 100)
Expand All @@ -29,7 +29,7 @@ data class User(
@Column(length = 15)
var phone:String,

var imgKey: String,
var imgKey: String? = null,

@NotNull
@Enumerated(EnumType.STRING)
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/psr/psr/user/entity/UserInterest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull
@Entity
data class UserInterest(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long,
var id: Long? = null,

@ManyToOne
@JoinColumn(nullable = false, name = "user_id")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package com.psr.psr.user.repository
import com.psr.psr.user.entity.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.util.*

@Repository
interface UserRepository: JpaRepository<User, Long> {
fun existsByNickname(nickname: String): Boolean
fun existsByPhone(phone: String): Boolean
fun existsByEmail(nickname: String): Boolean
}
Loading
Loading