[Kotlin]

[Kotlin + Spring Security + JWT] 회원가입 로그인 기능 구현 (1)

미냠 2023. 10. 15. 20:58
반응형

로그인은 사용자 인증을 통해서 사용자의 정보를 보호하고 사용자 인증을 목적으로 한다

Server에서 Client가 누구인지 알 수가 없기 때문에 사용자 인증 단계를 거쳐야 한다.

보안에는 인증과 인가로 나누어 생각해볼 수 있는데,

인증 : 사용자의 본인확인

인가 : 사용자 별 접근 권한

의 의미를 담고 있다

그래서 kotlin 공부로 첫번째 프로젝트는 Spring Security 기반의 회원가입 로그인 기능을 구현해볼 생각이다

해당 프로젝트는 application.properties 에 8080 포트로 서비스를 빌드시켰으며

db는 mysql를 사용했다

기본적으로 패키지 구성은 controller, service, repository, dto, entity 로 나눌 수 있다

여기서 DAO/Mapper 는 repository와 동일 기능을 한다고 생각하면 된다.

추가로 위의 그림에서는 mybatis로 DB와 통신했다면 아래의 프로젝트는 JPA를 활용했다

그래서 repository에서 Entity를 통해 DB와 통신한다

(JPA 공부가 필요할 것 같다고 느꼇다! JPA에 관련된 이론적인 내용은 추후에 다룰 것이다 ㅠ)

이번 프로젝트의 패키지 구성은 아래와 같이 구성했다.

  • common : 공통 모듈
    • annotation : 사용자 생성 어노테이션
    • dto : 공통 사용 dto
    • exception : 예외 처리
    • status : 사용할 status
  • member : 회원정보 관련 기능
    • controller : Request End Point
    • dto : 회원정보 관련 dto
    • entity : 회원정보 관련 entity
    • repository : 회원정보 관련 Repository
    • service : 비지니스 로직

Enum Class 구현

 

먼저 enum class 를 이용하여 공통적으로 사용하는 코드를 관리하려고 한다

desc 값에 따라 어떤 코드값으로 데이터를 저장하는 지를 보여준다.

(변경되지 않을 값들로 구성되어야 하는 것이 좋을 것으로 보인다)

package com.example.springsecuritytest.common.status

// enum type 으로 코드값 제한

enum class Gender(val desc: String ) {

MAN("남"),

WOMAN("여")

}

DTO 구현

DTO의 경우, val 타입으로 읽기 가능으로 구성했다

package com.example.springsecuritytest.member.dto

import com.example.springsecuritytest.common.status.Gender

import java.time.LocalDate

data class MemberDtoRequest (

val id: Long?,

val loginId: String,

val password: String,

val name: String,

val birthDate: LocalDate,

val gender: Gender,

val email: String,

)

Entity 구현

entitiy는 DB와 직접적인 데이터를 주고 받는 쪽이라고 생각하면 된다

로그인을 위해서는 로그인 아이디가 중복되지 않아야 하기 때문에 loginId 값을 unique column으로 생성했다

package com.example.springsecuritytest.member.entity

import com.example.springsecuritytest.common.status.Gender

import jakarta.persistence.*

import java.time.LocalDate

// Client로 부터 받은 DTO 정보를 DB에 저장하기 위한 Entity 생성

@Entity

@Table(

// loginId 중복 불허를 위해 unique column 으로 지정

uniqueConstraints = [UniqueConstraint(name = "uk_member_login_id", columnNames = ["loginId"])]

)

class Member(

@Id

@GeneratedValue(strategy = GenerationType.AUTO)

var id: Long? = null,

@Column(nullable = false, length = 30, updatable = false)

val loginId: String,

@Column(nullable = false, length = 100)

val password: String,

@Column(nullable = false, length = 10)

val name: String,

@Column(nullable = false)

@Temporal(TemporalType.DATE) // 날짜형만 입력 가능

val birthDate: LocalDate,

@Column(nullable = false, length = 5)

@Enumerated(EnumType.STRING) // enum class code insert

val gender: Gender,

@Column(nullable = false, length = 30)

val email: String,

)

Repostitory 구현

repostiory는 ID 중복 검사를 위한 로직을 추가했다

 

package com.example.springsecuritytest.member.repository

import com.example.springsecuritytest.member.entity.Member

import org.springframework.data.jpa.repository.JpaRepository

interface MemberRepository : JpaRepository<Member, Long> {

// LoginId로 회원정보 조회

fun findByLoginId(loginId: String): Member?

}

Service 구현

service 로직은 먼저 ID 중복 검사를 통해서 회원가입이 가능한지 확인하는 로직을 넣었고

위의 조건에 해당하지 않으면 해당 값들을 dto에 넣어 DB에 저장했다.

여기에 transactional annotation을 추가했는데 이것은 간단히 말해서 Roll Back 과 같은 기능이라고 생각하면 된다

모든 서비스에는 예상하지 못한 오류가 발생하기 마련인데

transaction annotation을 추가해줌으로서 해당 로직에서 오류가 발생하면, 그 안에 속해있던 모든 로직을 Roll Back 시킨다고 생각하면 된다

즉 모든 작업들이 성공적으로 동작해야만 최종적으로 DB에 반영하게끔 처리해주는 것으로 생각하면 된다

package com.example.springsecuritytest.member.service

import com.example.springsecuritytest.member.dto.MemberDtoRequest

import com.example.springsecuritytest.member.entity.Member

import com.example.springsecuritytest.member.repository.MemberRepository

import jakarta.transaction.Transactional

import org.springframework.stereotype.Service

@Transactional

@Service

class MemberService(private val memberRepository: MemberRepository) {

/*

* 회원가입

* */

fun signUp(memberDtoRequest: MemberDtoRequest): String {

// ID 중복 검사

var member: Member? = memberRepository.findByLoginId(memberDtoRequest.loginId)

if (member != null) {

return "이미 등록된 ID 입니다."

}

member = Member(

null,

memberDtoRequest.loginId,

memberDtoRequest.password,

memberDtoRequest.name,

memberDtoRequest.birthDate,

memberDtoRequest.gender,

memberDtoRequest.email

)

memberRepository.save(member)

return "회원가입이 완료되었습니다."

}

}

Controller 구현

마지막으로 End Point를 호출하는 controller이다.

post /api/member/signup 을 호출할 경우 service 의 signUp fun이 동작하게 된다

package com.example.springsecuritytest.member.controller

import com.example.springsecuritytest.member.dto.MemberDtoRequest

import com.example.springsecuritytest.member.service.MemberService

import org.springframework.web.bind.annotation.PostMapping

import org.springframework.web.bind.annotation.RequestBody

import org.springframework.web.bind.annotation.RequestMapping

import org.springframework.web.bind.annotation.RestController

@RequestMapping("/api/member")

@RestController

class MemberController(private val memberService: MemberService) {

/*

* 회원가입

* */

@PostMapping("/signup")

fun signUp(@RequestBody memberDtoRequest: MemberDtoRequest): String {

return memberService.signUp(memberDtoRequest)

}

}

이렇게 구현한 것을 post man을 이용해서 테스트 해보겠다

성공적으로 구현된 것을 확인할 수 있다

db에도 정상적으로 insert 되었다

이제 회원가입을 구현했으니 로그인 기능을 구현해보도록 하겠다

다음장에!

반응형