[Spring Security] 회원가입

주재완·2024년 7월 1일
0

Spring Security

목록 보기
3/3
post-thumbnail

Setup

우선 의존성으로 DB에 접근하는 ORM 기술인 JPA, 그리고 테스트 용으로 사용되는 h2 데이터베이스를 연결합니다. build.gradle에 다음과 같이 dependencies에 추가해줍니다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	runtimeOnly 'com.h2database:h2'
}

다음으로, application.properties에 다음과 같이 설정해줍니다.

spring.h2.console.enabled=true

spring.datasource.hikari.driver-class-name=org.h2.Driver
spring.datasource.hikari.jdbc-url=jdbc:h2:tcp://localhost/~/TestSecurity
spring.datasource.hikari.username=sa
spring.datasource.hikari.password=

server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
  • hikari는 driver, url, username, password를 통해 DB와 java를 연결할 수 있게 합니다. 또한 h2 데이터베이스의 초기 username은 sa, 비밀번호는 따로 없기에 다음과 같이 설정을 합니다.
  • 한글을 쓸 경우 encoding을 UTF-8로 설정해야합니다.
  • JPA는 별도 SQL문 작성 없이도 데이터를 DB에 저장 가능하게 해줍니다(물론 JPA 상의 여러 이슈들로 별도로 직접 쿼리를 쓰는 경우가 많긴 합니다)
    • 여기서 ddl-auto 옵션을 none으로 하게 된다면 나중에 Entity 클래스로 테이블을 계속해서 초기화해주는 것을 막을 수 있습니다.
    • 처음 테이블을 생성한다면 이 항목을 update로 바꾸어주면 됩니다.

FrontEnd

다음과 같이 회원가입을 위한 아주 간단한 UI를 구성해봅니다. join.mustache에 다음과 같이 작성합니다.

<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<form action="/joinProc" method="post" name="joinForm">
    <input type="text" name="username" placeholder="Username"/>
    <input type="password" name="password" placeholder="Password"/>
    <input type="submit" value="Join"/>
</form>
</body>
</html>

BackEnd

SecurityConfig

이에 따라 기존 config 파일에서 모두에게 허용할 url을 추가해줍니다.

...
	.requestMatchers("/", "/login", "/join", "/joinProc").permitAll()
...

UserEntity

회원가입시 회원명, 비밀번호를 받고, Role의 경우 비즈니스 로직 상에서 결정합니다. 따라서 다음과 같이 UserEntity 클래스를 작성해줍니다.

package com.example.testsecurity.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Setter
@Getter
public class UserEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(unique = true)
    private String username;

    private String password;

    private String role;
}

@Id는 해당 항목을 primary key로 설정을 해주고, @GeneratedValue를 통해서 자동으로 증가시켜줍니다. 이때, GenerationType.IDENTITY는 id 값을 비워두면 DB가 알아서 AUTO_INCREMENT, 즉 값을 증가시켜줍니다.

그리고 username은 겹치면 안되므로 unique 제약조건을 걸어줍니다.

JoinDTO

데이터를 DB로 전달하는 JoinDTO는 다음과 같습니다.

package com.example.testsecurity.dto;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class JoinDTO {

    private String username;
    private String password;
}

JoinController

MVC 패턴을 따름에 따라 Controller - Service - Repository - DB 로 진행이 됩니다.

package com.example.testsecurity.controller;

import com.example.testsecurity.dto.JoinDTO;
import com.example.testsecurity.service.JoinService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class JoinController {

    @Autowired
    private JoinService joinService;

    @GetMapping("/join")
    public String joinP() {
        return "join";
    }

    @PostMapping("/joinProc")
    public String joinProcess(JoinDTO joinDTO) {
        System.out.println(joinDTO.getUsername());
        joinService.joinProcess(joinDTO);
        return "redirect:/login";
    }
}

JoinService

package com.example.testsecurity.service;

import com.example.testsecurity.dto.JoinDTO;
import com.example.testsecurity.entity.UserEntity;
import com.example.testsecurity.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class JoinService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public void joinProcess(JoinDTO joinDTO) {

        // DB에 이미 동일한 username을 가진 회원이 존재하는가?
        boolean isUser = userRepository.existsByUsername(joinDTO.getUsername());
        if (isUser) {
            return;
        }

        UserEntity data = new UserEntity();

        data.setUsername(joinDTO.getUsername());
        data.setPassword(bCryptPasswordEncoder.encode(joinDTO.getPassword()));
        data.setRole("ROLE_USER");

        userRepository.save(data);
    }
}

서비스의 역할은 두가지입니다.

  • Repository와 연결 및 트랜잭션 처리
  • 비즈니스 로직 처리

따로 복잡한 트랜잭션에 대해서 지금 다루지는 않으므로, 로직을 보면, 비밀번호의 경우 그대로 저장하면 위험합니다. DBA가 맘먹으면 비밀번호 마음대로 열람도 가능할뿐더러, 보안상으로 좋지 않습니다. 따라서 Bcrypt 인코딩을 통해 비밀번호를 암호화합니다. 그리고 그 암호화 한 값을 DB에 저장해야됩니다.

그리고 Role의 경우에는 만약 USER라는 role을 주고 싶다면 반드시 ROLE_USER와 같이 지정해야됩니다. 관리자는 ROLE_ADMIN과 같이 주면 됩니다.

UserRepository

package com.example.testsecurity.repository;

import com.example.testsecurity.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<UserEntity, Integer> {
    boolean existsByUsername(String username);
}

여기까지 보면 따로 쿼리를 작성하지는 않았음을 볼 수 있습니다. 바로 JPA가 메소드명에 따라 자동으로 쿼리를 생성해서 DB에 보내주기 때문에 별도 쿼리를 작성하지 않았습니다.

service와 repository를 확인해보면 메소드 명으로만 쿼리를 자동으로 생성한다는 것을 알 수 있는데 이를 JPA의 쿼리 메소드라고 합니다.

profile
언제나 탐구하고 공부하는 개발자, 주재완입니다.

0개의 댓글

관련 채용 정보