Skip to content

Commit

Permalink
Merge pull request #14 from PSR-Co/feat/#12-signup
Browse files Browse the repository at this point in the history
[feat] 회원가입 API 생성
  • Loading branch information
chaerlo127 authored Jul 26, 2023
2 parents 6105c29 + 8fecba1 commit 7f55ffb
Show file tree
Hide file tree
Showing 20 changed files with 235 additions and 32 deletions.
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
}

// 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,
val message: String?
)
17 changes: 16 additions & 1 deletion src/main/kotlin/com/psr/psr/global/exception/BaseResponseCode.kt
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
2 changes: 1 addition & 1 deletion src/main/kotlin/com/psr/psr/global/jwt/UserDetailsImpl.kt
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)
}
}
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>{
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)
}
}
}
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,

@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
4 changes: 4 additions & 0 deletions src/main/kotlin/com/psr/psr/user/repository/UserRepository.kt
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

0 comments on commit 7f55ffb

Please sign in to comment.