지난 글에 인증받은 사용자와 그렇지 않은 사용자의 요청에 대해 차이를 두는 시큐리티 세팅까지 구현하였고
이번에는 회원가입을 구현합니다.
localhost로 접속해 '가입하기' 버튼을 누르면 회원가입 창으로 변합니다.
현재는 회원가입 기능이 되지 않습니다.
src/main/webapp/WEB/INF/views/auth
경로의 'signup.jsp' 파일을 엽니다.
그 안에서 회원가입 인풋을 찾아 아래와 같이 form class 옆에 '태그 속성'을 추가합니다.
<form class="login__input" action="/auth/signup" method="post">
그러면 아래 네 건의 데이터(가입정보)를 요청하면 데이터베이스에 인서트하는데 이를 위해 'POST' 요청을 합니다.
이제 컨트롤러를 추가합니다. 'com.cos.photogramstart' 아래 'web' 패키지를 하나 추가합니다.
package com.cos.photogramstart.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller // 1. IoC 등록, 2. 파일을 리턴함.
public class AuthController {
@GetMapping("/auth/signin")
public String signinForm() {
return "auth/signin";
}
}
이렇게 컨트롤러를 추가하고 저장하면 - 오류가 납니다.
이유는 기존에 만들었던 'ViewControllerTest'에 이 도메인이 등록돼있기 때문입니다.
이 컨트롤러는 테스트용으로 사용한 것이라 이 클래스 위의 '@Constroller'를 주석처리 합니다.
그리고 다시 새로 만든 컨트롤러에 아래 코드를 추가합니다.
이때 코드를 복붙하면서 수정할 부분을 빼먹진 않았는지 확인해야합니다. 저는 하나 안고쳐서 오류가 났습니다.
@GetMapping("/auth/signup")
public String signupForm() {
return "auth/signup";
}
다시 아래의 코드를 추가합니다. 위에서는 'GET' 요청으로 회원가입 페이지를 응답했지만
아래에서는 'POST' 요청으로 회원가입이 완료되면 로그인 페이지로 응답하게 됩니다.
@PostMapping("/auth/signup")
public String signup() {
return "auth/signin"; // 회원가입 성공하면 로그인 페이지로 이동.
}
이제 회원가입 페이지에서 정보를 입력하고 [가입] 버튼을 누르면 창이 넘어가긴 하는데 아직 403 오류창이 나타납니다.
[가입하기]를 누르면 '/auth/signup'으로 이동하고 여기서 '가입'을 누르면 '/auth/signin'으로 리턴됩니다. > 되어야합니다.
지금 그렇지 않은 이유는 'CSRF 토큰'이 활성화 되어있기 때문입니다.
클라이언트가 폼에 데이터를 넣고 서버로 전송하면
서버를 시큐리티가 감싸고 있다가 데이터를 CSRF 토큰
검사를 합니다.
사용자가 [가입하기]를 누르면 클라이언트는 서버에 '/auth/signup'을 요청하고
서버는 'signup.jsp' 파일로 응답합니다. 이때 시큐리티가 이 파일에 CSRF 토큰을 심어놓습니다.
그러면 사용자가 데이터를 넣을 폼(Input Tag)에 임의의 난수값이 생기게 됩니다.
그리고 사용자가 데이터를 넣어 [가입]을 눌러 요청하면 서버는 CSRF 토큰이 있는지 검사합니다.
아래 캡쳐화면처럼 다른 경로로 데이터를 보내오는지,
서버가 보낸 페이지에 정상적으로 데이터를 넣어 요청이 다시 오는지,
즉 정상적인 사용자인지 아닌지를 확인하는 것입니다.
여기서는 CSRF 토큰을 비활성화해 사용하지 않을겁니다.
지난 글에서 사용자 인증 접근 구별에 대해 작성한 코드에 아래 코드를 추가합니다. 👉 지난 글 참고
http.csrf().disable();
이제 서버는 사용자가 포스트맨을 사용하든 정상적인 페이지에서 접근하든 상관하지 않습니다.
이제 가입정보를 기입하고 [가입]을 누르면 로그인창으로 이동합니다.
다만 아직 입력한 데이터를 어딘가에 저장하지는 않습니다.
그런데, POST 요청 방식이라 그런걸까요 ?
두 번째 캡쳐화면을 보면 분명 로그인 창이 맞는데, 도메인은 '/auth/signup' 입니다.
개발자도구 네트워크에서도 signup 요청, 응답이 잘 되었다고 나타납니다.
web 패키지 이하 두 개의 패키지를 생성합니다. 경로는 'web/dto/auth'이 됩니다.
여기에 'SignupDto' 클래스를 추가합니다. 'Dto'는 요청, 응답 두 가지가 있는데 여기에서는 요청하는 것입니다.
DTO
는 통신을 할 때 필요한 데이터를 담아두는 오브젝트입니다.
여기서는 회원가입 시 입력하는 데이터를 담도록 합니다.
package com.cos.photogramstart.web.dto.auth;
import lombok.Data;
@Data // Getter, Setter 를 만들어주는 어노테이션
public class SignupDto {
private String username;
private String password;
private String email;
private String name;
}
이제 'AuthController'로 가서 컨트롤러 안쪽에 'Logger'를 추가하고 'signup' 메서드(POST)를 수정합니다.
'log.info()'는 문자열만 받을 수 있습니다.
@PostMapping("/auth/signup")
public String signup(SignupDto signupDto) {
log.info(signupDto.toString());
return "auth/signin";
}
다시 회원가입을 시도합니다. 틀에 입력한 데이터가 콘솔창에 표시되었습니다!
이렇게 전송한 데이터를 서버에서 받을 수 있게 되었습니다. 이제는 DB에 INSERT 해야합니다.
이를 위해서는 모델이 필요합니다.
com.cos.photogramstart 안에 domain.user 패키지를 만들고 안에 User 클래스를 생성합니다.
package com.cos.photogramstart.domain.user;
import java.time.LocalDateTime;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// JPA ( Java Persistence API - 자바로 데이터를 영구적으로 저장(DB)할 수 있는 API 제공)
@AllArgsConstructor // 전체 생성자
@NoArgsConstructor // 빈 생성자
@Data // Getter, Setter
@Entity // DB에 테이블을 생성해줌
public class User {
@Id // primary 키가 없어서 오류가 난다.. ? 난 왜 안나지.? 아무튼 프라이머리 키가 되어줍니다.
@GeneratedValue(strategy = GenerationType.IDENTITY) // 번호 증가 전략이 데이터베이스를 따라간다.
private int id;
private String username;
private String password;
private String name;
private String website;
private String email;
private String phone;
private String gender;
private String profileImageUrl;
private String role;
private LocalDateTime createDate;
@PrePersist // 데이터베이스에 값이 인서트되기 직전에 이 어노테이션을 걸어주면 실행되도록 합니다.
public void createDate() {
this.createDate = LocalDateTime.now();
}
}
이제 'mariadb'를 확인합니다. 여기서 DB에 위의 테이블들이 생겨나야 하지만
저는 테이블 생성이 되지 않습니다. application.yml 파일에서 경로나 포트번호 등등
이것저것 구글링해서 바꿔보고 서버 재시동, DB 껐다 켜보고 다 하고 있는데 되지 않습니다.
원인을 찾았습니다. 처음 이 프로젝트를 시작할 때 추가한 코드 한 줄이 원인이었습니다. 👉 지난 글
처음에 분명 앱이 실행되지 않아 서버 구동이 안되었고 DB 연동 문제가 있어서
블로그 참고하여 '@SpringBootApplication'에 아래 코드를 추가했고 문제가 해결되었는데,
(exclude={DataSourceAutoConfiguration.class})
이제와서 DB에 테이블을 만드는 등 연동시키려고 하니 이 코드가 문제가 되는 것이었습니다.
@SpringBootApplication
어노테이션은 스프링 부트의 가장 기본적인 설정을 선언하는데,
이 어노테이션을 command
키를 누른채로 클릭하면 아래와 같은 인터페이스 코드를 볼 수 있습니다.
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.repository.Repository;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
이 어노테이션이 크게 3가지의 기능을 가지고 있다는데 솔직히 지금의 저는 이게 어디가지 관여하는지 잘 모르겠고요.
다만 이름만 봐서는 데이터 소스를 자동으로 설정해주는 기능을 제외하는 코드를 지웠더니
웹서버를 통해 데이터베이스에 테이블을 생성할 수 있게 되었습니다.
아마 분명히 저와 같은 경로로 따라가다가 저와 같이 막히는 분들이 계실까 싶어 일단 해결 방식을 공유합니다.
그리고 앱이 정상적으로 구동하면 아래와 같은 쿼리문이 콘솔창에 추가로 나타납니다.
Hibernate: drop table if exists User
Hibernate: create table User (id integer not null auto_increment, bio varchar(255), createDate datetime(6), email varchar(255), gender varchar(255), name varchar(255), password varchar(255), phone varchar(255), profileImageUrl varchar(255), role varchar(255), username varchar(255), website varchar(255), primary key (id)) engine=InnoDB