Skip to content
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
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ dependencies {
// env
implementation("io.github.cdimascio:dotenv-kotlin:6.4.0")

implementation("io.ktor:ktor-server-auth:$logback_version") // Ktor 버전에 맞게 조정
implementation("io.ktor:ktor-server-auth-jwt:$logback_version")
implementation("com.auth0:java-jwt:4.2.1") // 최신 버전으로 조정

implementation("com.h2database:h2:$h2_version")
implementation("io.ktor:ktor-server-openapi")
implementation("io.ktor:ktor-server-auth-jvm")
Expand Down
13 changes: 10 additions & 3 deletions src/main/kotlin/example/com/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import io.ktor.server.application.*
import io.ktor.server.netty.*
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import example.com.models.Users
import example.com.routes.authRoutes
import example.com.routes.userRoutes
import io.github.cdimascio.dotenv.dotenv
import io.ktor.server.auth.*
import io.ktor.server.routing.*
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
Expand Down Expand Up @@ -52,12 +55,16 @@ fun Application.configureDatabase() {

// 데이터베이스 테이블 생성
transaction {
SchemaUtils.create(UserService.Users) // Users 테이블 생성
SchemaUtils.drop(Users)
SchemaUtils.create(Users) // Users 테이블 생성
}
}

fun Application.configureRouting() {
routing {
userRoutes() // 유저 관련 라우팅 추가
authenticate("auth-jwt") { // jwt 인증이 필요한 라우팅
userRoutes() // 유저 관련 라우팅 추가
}
authRoutes() // 로그인, 회원가입 라우팅 추가
}
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/example/com/dto/Credentials.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package example.com.dto

import kotlinx.serialization.Serializable

@Serializable
data class Credentials(val name: String, val password: String)
6 changes: 6 additions & 0 deletions src/main/kotlin/example/com/dto/UserResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package example.com.dto

import kotlinx.serialization.Serializable

@Serializable
data class UserResponse(val id: Int, val name: String, val email: String)
6 changes: 4 additions & 2 deletions src/main/kotlin/example/com/models/Users.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import kotlinx.serialization.Serializable
object Users : Table() {
val id = integer("id").autoIncrement()
val name = varchar("name", 50)
val age = integer("age")
val email = varchar("email", 50)
val password = varchar("password", 64) // 비밀번호 필드 추가

override val primaryKey = PrimaryKey(id)
}

// 데이터 클래스 정의 (직렬화를 위해 Serializable 사용)
@Serializable
data class User(val id: Int? = null, val name: String, val age: Int)
data class User(val id: Int? = null, val name: String, val email: String, val password: String)
52 changes: 0 additions & 52 deletions src/main/kotlin/example/com/plugins/Databases.kt

This file was deleted.

26 changes: 26 additions & 0 deletions src/main/kotlin/example/com/plugins/Security.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
package example.com.plugins

import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import io.ktor.server.auth.jwt.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.http.*
import io.ktor.server.auth.*

fun Application.configureSecurity() {
install(Authentication) {
jwt("auth-jwt") {
realm = "ktor-sample"
verifier(
JWT
.require(Algorithm.HMAC256("secret"))
.withAudience("ktor-audience")
.withIssuer("ktor-issuer")
.build()
)
validate { credential ->
if (credential.payload.getClaim("name").asString().isNotEmpty()) {
JWTPrincipal(credential.payload)
} else null
}
challenge { _, _ ->
call.respond(HttpStatusCode.Unauthorized, "Token is not valid or has expired")
}
}
}
}
62 changes: 0 additions & 62 deletions src/main/kotlin/example/com/plugins/UsersSchema.kt

This file was deleted.

121 changes: 121 additions & 0 deletions src/main/kotlin/example/com/routes/AuthRoutes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package example.com.routes

import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import example.com.dto.Credentials
import example.com.dto.UserResponse
import example.com.models.User
import example.com.models.Users
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction

fun Route.authRoutes() {
route("/users") {
// POST - 유저 생성
post {
val user = call.receive<User>()
transaction {
Users.insert {
it[name] = user.name
it[email] = user.email
it[password] = user.password // 비밀번호 저장
}
}
call.respond(HttpStatusCode.Created, "User added successfully")
}

// POST - 로그인
post("/login") {
val credentials = call.receive<Credentials>()

// 데이터베이스에서 유저 조회
val user = transaction {
Users.select { Users.name eq credentials.name }
.map { User(it[Users.id], it[Users.name], it[Users.email], it[Users.password]) }
.singleOrNull()
}

if (user != null && user.password == credentials.password) { // 비밀번호 검증
val token = JWT.create()
.withAudience("ktor-audience")
.withIssuer("ktor-issuer")
.withClaim("name", credentials.name)
.sign(Algorithm.HMAC256("secret"))

call.respond(hashMapOf("token" to token))
} else {
call.respond(HttpStatusCode.Unauthorized, "Invalid credentials")
}
}
// GET - 모든 유저 조회
get {
val users = transaction {
Users.selectAll().map {
UserResponse(it[Users.id], it[Users.name], it[Users.email]) // 비밀번호를 제외하고 응답
}
}
call.respond(users)
}

// GET - 특정 유저 조회
get("/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respond(HttpStatusCode.BadRequest, "Invalid user ID")
return@get
}

val user = transaction {
Users.select { Users.id eq id }
.map { UserResponse(it[Users.id], it[Users.name], it[Users.email]) }
.singleOrNull()
}

if (user == null) {
call.respond(HttpStatusCode.NotFound, "User not found")
} else {
call.respond(user)
}
}
}

// GET - 모든 유저 조회
get {
val users = transaction {
Users.selectAll().map {
UserResponse(it[Users.id], it[Users.name], it[Users.email]) // 비밀번호를 제외하고 응답
}
}
call.respond(users)
}

// GET - 특정 유저 조회
get("/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respond(HttpStatusCode.BadRequest, "Invalid user ID")
return@get
}

val user = transaction {
Users.select { Users.id eq id }
.map { UserResponse(it[Users.id], it[Users.name], it[Users.email]) }
.singleOrNull()
}

if (user == null) {
call.respond(HttpStatusCode.NotFound, "User not found")
} else {
call.respond(user)
}
}

}
Loading