Skip to main content

03F. ํšŒ์›๊ฐ€์ž…

About 4 minJavaSpringAWScrashcoursejavajdkjdk8streamspringspringframeworkspringbootawsaws-ec2

03F. ํšŒ์›๊ฐ€์ž… ๊ด€๋ จ


3-06. ํšŒ์›๊ฐ€์ž…

์ ํ”„ ํˆฌ ์Šคํ”„๋ง๋ถ€ํŠธ - WikiDocs

pahkey/sbb3 - 3-06open in new window

์ด๋ฒˆ์—๋Š” SBB์— ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด ๋ณด์ž.

ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด ๋ณด์•˜๋‹ค๋ฉด ์›น ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ๊ฑฐ์˜ ๋งˆ์Šคํ„ฐํ–ˆ๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋งŒํผ ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์€ ์›น ์‚ฌ์ดํŠธ์—์„œ ํ•ต์‹ฌ ์ค‘์˜ ํ•ต์‹ฌ์ด๋ผ ํ•  ์ˆ˜ ์žˆ๋‹ค.


ํšŒ์› ์ •๋ณด๋ฅผ ์œ„ํ•œ ์—”ํ‹ฐํ‹ฐ

์ง€๊ธˆ๊นŒ์ง€๋Š” ์งˆ๋ฌธ, ๋‹ต๋ณ€ ์—”ํ‹ฐํ‹ฐ๋งŒ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด ์ด์ œ ํšŒ์› ์ •๋ณด๋ฅผ ์œ„ํ•œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ํšŒ์› ์ •๋ณด ์—”ํ‹ฐํ‹ฐ์—๋Š” ์ตœ์†Œํ•œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์†์„ฑ์ด ํ•„์š”ํ•˜๋‹ค.

์†์„ฑ์„ค๋ช…
username์‚ฌ์šฉ์ž ์ด๋ฆ„ (์‚ฌ์šฉ์ž ID)
password๋น„๋ฐ€๋ฒˆํ˜ธ
email์ด๋ฉ”์ผ

User ๋„๋ฉ”์ธ

๊ทธ๋ฆฌ๊ณ  ํšŒ์›์€ ์งˆ๋ฌธ, ๋‹ต๋ณ€ ๋„๋ฉ”์ธ์ด ์•„๋‹ˆ๋ฏ€๋กœ user๋ผ๋Š” ๋„๋ฉ”์ธ์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด com.mysite.sbb.user ํŒจํ‚ค์ง€๋ฅผ ์ƒ์„ฑํ•˜์ž.

06_1
06_1

SiteUser ์—”ํ‹ฐํ‹ฐ

๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž๋ฅผ ๊ด€๋ฆฌํ•  SiteUser ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋‹ค์Œ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/user/SiteUser.java

package com.mysite.sbb.user;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class SiteUser {

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

    @Column(unique = true)
    private String username;

    private String password;

    @Column(unique = true)
    private String email;
}

Question, Answer ์—”ํ‹ฐํ‹ฐ์™€ ๋™์ผํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ SiteUser ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. ์—”ํ‹ฐํ‹ฐ๋ช…์„ User ๋Œ€์‹  SiteUser๋กœ ํ•œ ์ด์œ ๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์— ์ด๋ฏธ User ํด๋ž˜์Šค๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋ฌผ๋ก  ํŒจํ‚ค์ง€๋ช…์ด ๋‹ฌ๋ผ User๋ผ๋Š” ์ด๋ฆ„์„ ์‚ฌ์šฉํ• ์ˆ˜ ์žˆ์ง€๋งŒ ํŒจํ‚ค์ง€ ์˜ค์šฉ์œผ๋กœ ์ธํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ• ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ด ์ฑ…์—์„œ๋Š” User ๋Œ€์‹  SiteUser๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋ช…๋ช…ํ•˜์˜€๋‹ค.

๊ทธ๋ฆฌ๊ณ  username, email ์†์„ฑ์—๋Š” @Column(unique = true) ์ฒ˜๋Ÿผ unique = true๋ฅผ ์ง€์ •ํ–ˆ๋‹ค. unique = true๋Š” ์œ ์ผํ•œ ๊ฐ’๋งŒ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•œ๋‹ค. ์ฆ‰, ๊ฐ’์„ ์ค‘๋ณต๋˜๊ฒŒ ์ €์žฅํ•  ์ˆ˜ ์—†์Œ์„ ๋œปํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•ด์•ผ username๊ณผ email์— ๋™์ผํ•œ ๊ฐ’์ด ์ €์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค.

SiteUser ํ…Œ์ด๋ธ”

SiteUser ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  H2 ์ฝ˜์†”์— ์ ‘์†ํ•˜์—ฌ ํ…Œ์ด๋ธ”์ด ์ž˜ ๋งŒ๋“ค์–ด์กŒ๋Š”์ง€ ํ™•์ธํ•ด ๋ณด์ž.

06_2
06_2

SITE_USER ํ…Œ์ด๋ธ”๊ณผ ์ปฌ๋Ÿผ๋“ค ๊ทธ๋ฆฌ๊ณ  unique๋กœ ์„ค์ •ํ•œ ์†์„ฑ๋“ค๋กœ ์ธํ•ด ์ƒ๊ธด UK_๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ธ๋ฑ์Šค๋“ค์ด ๋ณด์ผ ๊ฒƒ์ด๋‹ค.


User ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ์™€ ์„œ๋น„์Šค

์‚ฌ์šฉ์ž ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ค€๋น„๋˜์—ˆ์œผ๋‹ˆ ์ด์ œ User ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ์™€ User ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์ž.

User ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ

๋‹ค์Œ๊ณผ ๊ฐ™์ด UserRepository๋ฅผ ์ž‘์„ฑํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/user/UserRepository.java

package com.mysite.sbb.user;

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

public interface UserRepository extends JpaRepository<SiteUser, Long> {
}

SiteUser์˜ PK์˜ ํƒ€์ž…์€ Long์ด๋‹ค. ๋”ฐ๋ผ์„œ JpaRepository<SiteUser, Long>์ฒ˜๋Ÿผ ์‚ฌ์šฉํ–ˆ๋‹ค.

User ์„œ๋น„์Šค

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด UserService๋ฅผ ์ž‘์„ฑํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/user/UserService.java

package com.mysite.sbb.user;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;

    public SiteUser create(String username, String email, String password) {
        SiteUser user = new SiteUser();
        user.setUsername(username);
        user.setEmail(email);
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        user.setPassword(passwordEncoder.encode(password));
        this.userRepository.save(user);
        return user;
    }
}

User ์„œ๋น„์Šค์—๋Š” User ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ User ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” create ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ์ด ๋•Œ ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ๋ณด์•ˆ์„ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ ์•”ํ˜ธํ™”ํ•˜์—ฌ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค. ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•ด ์‹œํ๋ฆฌํ‹ฐ์˜ BCryptPasswordEncoder ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•”ํ˜ธํ™”ํ•˜์—ฌ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ €์žฅํ–ˆ๋‹ค.

BCryptPasswordEncoder๋Š” BCrypt ํ•ด์‹ฑ ํ•จ์ˆ˜(BCrypt hashing function)๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ BCryptPasswordEncoder ๊ฐ์ฒด๋ฅผ ์ง์ ‘ new๋กœ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ์‹๋ณด๋‹ค๋Š” PasswordEncoder ๋นˆ(bean)์œผ๋กœ ๋“ฑ๋กํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์•”ํ˜ธํ™” ๋ฐฉ์‹์„ ๋ณ€๊ฒฝํ•˜๋ฉด BCryptPasswordEncoder๋ฅผ ์‚ฌ์šฉํ•œ ๋ชจ๋“  ํ”„๋กœ๊ทธ๋žจ์„ ์ผ์ผ์ด ์ฐพ์•„์„œ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

PasswordEncoder๋Š” BCryptPasswordEncoder์˜ ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.

PasswordEncoder ๋นˆ(bean)์„ ๋งŒ๋“œ๋Š” ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ @Configuration์ด ์ ์šฉ๋œ SecurityConfig์— @Bean ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด SecurityConfig๋ฅผ ์ˆ˜์ •ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/SecurityConfig.java

// (... ์ƒ๋žต ...)
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// (... ์ƒ๋žต ...)

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
                .requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
            .csrf((csrf) -> csrf
                .ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
            .headers((headers) -> headers
                .addHeaderWriter(new XFrameOptionsHeaderWriter(
                    XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
        ;
        return http.build();
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

ย 
ย 



















ย 
ย 
ย 
ย 

์ด๋ ‡๊ฒŒ PasswordEncoder๋ฅผ @Bean์œผ๋กœ ๋“ฑ๋กํ•˜๋ฉด UserService๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ• ์ˆ˜ ์žˆ๋‹ค.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/user/UserService.java

package com.mysite.sbb.user;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public SiteUser create(String username, String email, String password) {
        SiteUser user = new SiteUser();
        user.setUsername(username);
        user.setEmail(email);
        user.setPassword(passwordEncoder.encode(password));
        this.userRepository.save(user);
        return user;
    }
}


ย 









ย 





ย 




BCryptPasswordEncoder ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•˜์—ฌ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ PasswordEncoder ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋„๋ก ์ˆ˜์ •ํ–ˆ๋‹ค.


ํšŒ์›๊ฐ€์ž… ํผ

๊ทธ๋ฆฌ๊ณ  ํšŒ์› ๊ฐ€์ž…์„ ์œ„ํ•œ ํผ ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•˜์ž. ๋‹ค์Œ์ฒ˜๋Ÿผ UserCreateForm์„ ๋งŒ๋“ค์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/user/UserCreateForm.java

package com.mysite.sbb.user;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserCreateForm {
    @Size(min = 3, max = 25)
    @NotEmpty(message = "์‚ฌ์šฉ์žID๋Š” ํ•„์ˆ˜ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.")
    private String username;

    @NotEmpty(message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.")
    private String password1;

    @NotEmpty(message = "๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ์€ ํ•„์ˆ˜ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.")
    private String password2;

    @NotEmpty(message = "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.")
    @Email
    private String email;
}

username์€ ํ•„์ˆ˜ํ•ญ๋ชฉ์ด๊ณ  ๊ธธ์ด๊ฐ€ 3-25 ์‚ฌ์ด์—ฌ์•ผ ํ•œ๋‹ค๋Š” ๊ฒ€์ฆ์กฐ๊ฑด์„ ์„ค์ •ํ–ˆ๋‹ค. @Size๋Š” ํผ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์‹œ ๋ฌธ์ž์—ด์˜ ๊ธธ์ด๊ฐ€ ์ตœ์†Œ๊ธธ์ด(min)์™€ ์ตœ๋Œ€๊ธธ์ด(max) ์‚ฌ์ด์— ํ•ด๋‹นํ•˜๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค. password1๊ณผ password2๋Š” "๋น„๋ฐ€๋ฒˆํ˜ธ"์™€ "๋น„๋ฐ€๋ฒˆํ˜ธํ™•์ธ"์— ๋Œ€ํ•œ ์†์„ฑ์ด๋‹ค. ๋กœ๊ทธ์ธ ํ• ๋•Œ๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ•œ๋ฒˆ๋งŒ ํ•„์š”ํ•˜์ง€๋งŒ ํšŒ์›๊ฐ€์ž…์‹œ์—๋Š” ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ •ํ™•ํ•œ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด 2๊ฐœ์˜ ํ•„๋“œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๊ทธ๋ฆฌ๊ณ  email ์†์„ฑ์—๋Š” @Email ์• ๋„ˆํ…Œ์ด์…˜์ด ์ ์šฉ๋˜์—ˆ๋‹ค. @Email์€ ํ•ด๋‹น ์†์„ฑ์˜ ๊ฐ’์ด ์ด๋ฉ”์ผํ˜•์‹๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.


ํšŒ์›๊ฐ€์ž… ์ปจํŠธ๋กค๋Ÿฌ

์ด์ œ ์‚ฌ์šฉ์ž ์—”ํ‹ฐํ‹ฐ์™€ ์„œ๋น„์Šค ๊ทธ๋ฆฌ๊ณ  ํผ์ด ์ค€๋น„๋˜์—ˆ์œผ๋‹ˆ ํšŒ์›๊ฐ€์ž…์„ ์œ„ํ•œ User ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/user/UserController.java

package com.mysite.sbb.user;

import jakarta.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
@RequestMapping("/user")
public class UserController {

    private final UserService userService;

    @GetMapping("/signup")
    public String signup(UserCreateForm userCreateForm) {
        return "signup_form";
    }

    @PostMapping("/signup")
    public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "signup_form";
        }

        if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
            bindingResult.rejectValue("password2", "passwordInCorrect", 
                    "2๊ฐœ์˜ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
            return "signup_form";
        }

        userService.create(userCreateForm.getUsername(), 
                userCreateForm.getEmail(), userCreateForm.getPassword1());

        return "redirect:/";
    }
}

/user/signup URL์ด GET์œผ๋กœ ์š”์ฒญ๋˜๋ฉด ํšŒ์› ๊ฐ€์ž…์„ ์œ„ํ•œ ํ…œํ”Œ๋ฆฟ์„ ๋ Œ๋”๋งํ•˜๊ณ  POST๋กœ ์š”์ฒญ๋˜๋ฉด ํšŒ์›๊ฐ€์ž…์„ ์ง„ํ–‰ํ•˜๋„๋ก ํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํšŒ์› ๊ฐ€์ž…์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ1๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ2๊ฐ€ ๋™์ผํ•œ์ง€๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ๋งŒ์•ฝ 2๊ฐœ์˜ ๊ฐ’์ด ์ผ์น˜ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ์—๋Š” bindingResult.rejectValue๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ํ–ˆ๋‹ค. bindingResult.rejectValue์˜ ๊ฐ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” bindingResult.rejectValue(ํ•„๋“œ๋ช…, ์˜ค๋ฅ˜์ฝ”๋“œ, ์—๋Ÿฌ๋ฉ”์‹œ์ง€)๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ ์—ฌ๊ธฐ์„œ ์˜ค๋ฅ˜์ฝ”๋“œ๋Š” ์ผ๋‹จ "passwordInCorrect"๋กœ ์ •์˜ํ–ˆ๋‹ค.

๋Œ€ํ˜• ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋ฒˆ์—ญ๊ณผ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ์˜ค๋ฅ˜์ฝ”๋“œ๋ฅผ ์ž˜ ์ •์˜ํ•˜์—ฌ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.


ํšŒ์›๊ฐ€์ž… ํ…œํ”Œ๋ฆฟ

์ด์–ด์„œ ํšŒ์›๊ฐ€์ž… ํ…œํ”Œ๋ฆฟ์„ ์ž‘์„ฑํ•˜์ž. ๋‹ค์Œ์ฒ˜๋Ÿผ signup_form.html ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/resources/templates/signup_form.html

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
    <div class="my-3 border-bottom">
        <div>
            <h4>ํšŒ์›๊ฐ€์ž…</h4>
        </div>
    </div>
    <form th:action="@{/user/signup}" th:object="${userCreateForm}" method="post">
        <div th:replace="~{form_errors :: formErrorsFragment}"></div>
        <div class="mb-3">
            <label for="username" class="form-label">์‚ฌ์šฉ์žID</label>
            <input type="text" th:field="*{username}" class="form-control">
        </div>
        <div class="mb-3">
            <label for="password1" class="form-label">๋น„๋ฐ€๋ฒˆํ˜ธ</label>
            <input type="password" th:field="*{password1}" class="form-control">
        </div>
        <div class="mb-3">
            <label for="password2" class="form-label">๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ</label>
            <input type="password" th:field="*{password2}" class="form-control">
        </div>
        <div class="mb-3">
            <label for="email" class="form-label">์ด๋ฉ”์ผ</label>
            <input type="email" th:field="*{email}" class="form-control">
        </div>
        <button type="submit" class="btn btn-primary">ํšŒ์›๊ฐ€์ž…</button>
    </form>
</div>
</html>

ํšŒ์›๊ฐ€์ž…์„ ์œ„ํ•œ "์‚ฌ์šฉ์ž ID", "๋น„๋ฐ€๋ฒˆํ˜ธ", "๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ", "์ด๋ฉ”์ผ"์— ํ•ด๋‹น๋˜๋Š” input ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. <ํšŒ์›๊ฐ€์ž…> ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ํผ ๋ฐ์ดํ„ฐ๊ฐ€ POST ๋ฐฉ์‹์œผ๋กœ /user/signup/ URL๋กœ ์ „์†ก๋œ๋‹ค.


๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฐ”์— ํšŒ์›๊ฐ€์ž… ๋งํฌ ์ถ”๊ฐ€ํ•˜๊ธฐ

์ด์ œ ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๋งํฌ๋ฅผ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฐ”์— ์ถ”๊ฐ€ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/resources/templates/navbar.html

<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
    <div class="container-fluid">
        <a class="navbar-brand" href="/">SBB</a>
        <!-- (... ์ƒ๋žต ...) -->
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                    <a class="nav-link" href="#">๋กœ๊ทธ์ธ</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" th:href="@{/user/signup}">ํšŒ์›๊ฐ€์ž…</a>
                </li>
            </ul>
        </div>
    </div>
</nav>









ย 
ย 
ย 





ํšŒ์›๊ฐ€์ž… ์‹คํ–‰ํ•ด ๋ณด๊ธฐ

์ด์ œ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฐ”์˜ "ํšŒ์›๊ฐ€์ž…" ๋งํฌ๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์ด ๋‚˜์˜จ๋‹ค.

06_3
06_3

์ž…๋ ฅ๊ฐ’ ์ค‘์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ, ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ์„ ๋‹ค๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๊ณ  <ํšŒ์›๊ฐ€์ž…>์„ ๋ˆ„๋ฅด๋ฉด ๊ฒ€์ฆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ํ™”๋ฉด์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•ด ์ค„ ๊ฒƒ์ด๋‹ค.

06_4
06_4

์ด์ฒ˜๋Ÿผ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์—๋Š” ํ•„์ˆซ๊ฐ’ ๊ฒ€์ฆ, ์ด๋ฉ”์ผ ๊ทœ์น™ ๊ฒ€์ฆ ๋“ฑ์ด ์ ์šฉ๋˜์–ด ์žˆ๋‹ค. ์˜ฌ๋ฐ”๋ฅธ ์ž…๋ ฅ๊ฐ’์œผ๋กœ ํšŒ์›๊ฐ€์ž…์„ ์™„๋ฃŒํ•˜๋ฉด ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋  ๊ฒƒ์ด๋‹ค.


ํšŒ์›๊ฐ€์ž… ๋ฐ์ดํ„ฐ ํ™•์ธํ•ด ๋ณด๊ธฐ

H2 ์ฝ˜์†”์—์„œ ๋‹ค์Œ์˜ SQL์„ ์‹คํ–‰ํ•˜์—ฌ ๋ฐ”๋กœ ์•ž ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์ณ ๋งŒ๋“  ํšŒ์› ์ •๋ณด๋ฅผ ํ™•์ธํ•ด ๋ณด์ž.

SELECT * FROM SITE_USER 
06_5
06_5

์ถ•ํ•˜ํ•œ๋‹ค. ์ด์ œ SBB ์„œ๋น„์Šค์— ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋˜์—ˆ๋‹ค.


์ค‘๋ณต ํšŒ์›๊ฐ€์ž…

์ด๋ฒˆ์—๋Š” ์ด๋ฏธ ๊ฐ€์ž…ํ•œ ๋™์ผํ•œ ์‚ฌ์šฉ์žID, ๋˜๋Š” ๋™์ผํ•œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋กœ ํšŒ์›๊ฐ€์ž…์„ ์ง„ํ–‰ํ•ด ๋ณด์ž. ์•„๋งˆ๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋‹ค.

06_6
06_6

๋™์ผํ•œ ์‚ฌ์šฉ์žID ๋˜๋Š” ๋™์ผํ•œ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋กœ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ์€ unique=true ์„ค์ •์œผ๋กœ ์ธํ•ด ํ—ˆ์šฉ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์€ ๋‹น์—ฐํ•˜๋‹ค. ํ•˜์ง€๋งŒ ํ™”๋ฉด์— ์ด๋ ‡๊ฒŒ 500 ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์€ ์ข‹์ง€ ์•Š๋‹ค. ๋”ฐ๋ผ์„œ ํšŒ์›๊ฐ€์ž…์‹œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฒ˜๋ฆฌํ•ด ์ฃผ์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/user/UserController.java

package com.mysite.sbb.user;

// (... ์ƒ๋žต ...)
import org.springframework.dao.DataIntegrityViolationException;
// (... ์ƒ๋žต ...)
public class UserController {

    // (... ์ƒ๋žต ...)

    @PostMapping("/signup")
    public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "signup_form";
        }

        if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
            bindingResult.rejectValue("password2", "passwordInCorrect", 
                    "2๊ฐœ์˜ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
            return "signup_form";
        }

        try {
            userService.create(userCreateForm.getUsername(), 
                    userCreateForm.getEmail(), userCreateForm.getPassword1());
        }catch(DataIntegrityViolationException e) {
            e.printStackTrace();
            bindingResult.reject("signupFailed", "์ด๋ฏธ ๋“ฑ๋ก๋œ ์‚ฌ์šฉ์ž์ž…๋‹ˆ๋‹ค.");
            return "signup_form";
        }catch(Exception e) {
            e.printStackTrace();
            bindingResult.reject("signupFailed", e.getMessage());
            return "signup_form";
        }

        return "redirect:/";
    }
}



ย 

















ย 


ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 




์‚ฌ์šฉ์žID ๋˜๋Š” ์ด๋ฉ”์ผ ์ฃผ์†Œ๊ฐ€ ๋™์ผํ•  ๊ฒฝ์šฐ์—๋Š” DataIntegrityViolationException์ด ๋ฐœ์ƒํ•˜๋ฏ€๋กœ DataIntegrityViolationException ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ "์ด๋ฏธ ๋“ฑ๋ก๋œ ์‚ฌ์šฉ์ž์ž…๋‹ˆ๋‹ค."๋ผ๋Š” ์˜ค๋ฅ˜๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๋„๋ก ํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ์˜ค๋ฅ˜์˜ ๊ฒฝ์šฐ์—๋Š” ํ•ด๋‹น ์˜ค๋ฅ˜์˜ ๋ฉ”์‹œ์ง€(e.getMessage())๋ฅผ ์ถœ๋ ฅํ•˜๋„๋ก ํ–ˆ๋‹ค.

bindingResult.reject(์˜ค๋ฅ˜์ฝ”๋“œ, ์˜ค๋ฅ˜๋ฉ”์‹œ์ง€)๋Š” ํŠน์ • ํ•„๋“œ์˜ ์˜ค๋ฅ˜๊ฐ€ ์•„๋‹Œ ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜๋ฅผ ๋“ฑ๋กํ• ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ์ˆ˜์ •ํ•˜๊ณ  ๋‹ค์‹œ ๋™์ผํ•œ ์‚ฌ์šฉ์ž๋กœ ํšŒ์›๊ฐ€์ž…์„ ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์ƒ์ ์ธ ์˜ค๋ฅ˜๋ฅผ ํ‘œ์‹œํ•˜๋Š” ํ™”๋ฉด์„ ๋ณผ์ˆ˜ ์žˆ๋‹ค.

06_7
06_7

์ด์ฐฌํฌ (MarkiiimarK)
Never Stop Learning.