로그인까지의 순서도 APP→ index.html→
pom.xml 의존성 주입
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="<http://maven.apache.org/POM/4.0.0>" xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
xsi:schemaLocation="<http://maven.apache.org/POM/4.0.0> <https://maven.apache.org/xsd/maven-4.0.0.xsd>">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<!--만들때 프로젝트 이름이다 그룹아이디-->
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.studyolle</groupId>
<artifactId>studyolle</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>studyolle</name>
<description>Study management web service</description>
<properties>
<java.version>11</java.version>
</properties>
<!--의존성을 모아둔곳이다.-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.6</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>0.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.13.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.13.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.8.0</version>
<configuration>
<nodeVersion>v4.6.0</nodeVersion>
<workingDirectory>src/main/resources/static</workingDirectory>
</configuration>
<executions>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<phase>generate-resources</phase>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
spring.profiles.active=local
# 개발할 때에만 create-drop 또는 update를 사용하고 운영 환경에서는 validate를 사용합니다.
spring.jpa.hibernate.ddl-auto=create-drop
#create-drop 컴파일일할때 다 지우고 새로 만든느 역활을 해준다.
# 개발시 SQL 로깅을 하여 어떤 값으로 어떤 SQL이 실행되는지 확인합니다.
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# 톰캣 기본 요청 사이즈는 2MB 입니다. 그것보다 큰 요청을 받고 싶은 경우에 이 값을 조정해야 합니다.
server.tomcat.max-http-form-post-size=5MB
# 웹 서버 호스트
app.host=http://localhost:8080
# HTML <FORM>에서 th:method에서 PUT 또는 DELETE를 사용해서 보내는 _method를 사용해서 @PutMapping과 @DeleteMapping으로 요청을 맵핑.
spring.mvc.hiddenmethod.filter.enabled=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
spring.datasource.username=postgres
spring.datasource.password=ctew2011
spring.mail.host=smtp.gmail.com
spring.mail.port=587
# 본인 gmail 계정으로 바꾸세요.
[email protected]
# 위에서 발급받은 App 패스워드로 바꾸세요.
spring.mail.password=jsxtgzwirzbvctix
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.starttls.enable=true
package com.studyolle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="<http://www.thymeleaf.org>">
<!--여기서 fragments에 헤드를 배치를 한다.-->
<head th:replace="fragments.html :: head"></head>
<body class="bg-light">
<!--여기서 fragments에 메인을 배치를 한다.-->
<div th:replace="fragments.html :: main-nav"></div>
<section class="jumbotron text-center">
<div class="container">
<h1>스터디올래</h1>
<p class="lead text-muted">
태그와 지역 기반으로 스터디를 찾고 참여하세요.<br/>
스터디 모임 관리 기능을 제공합니다.
</p>
<p>
[<a th:href="@{/sign-up}" class="btn btn-primary my-2">](<https://shaded-parakeet-7d2.notion.site/f0bb7390e114459fb963c6a67734e7bd>)회원 가입</a>
</p>
</div>
</section>
<div class="container">
<div class="row justify-content-center pt-3">
<div th:replace="fragments.html :: study-list (studyList=${studyList})"></div>
</div>
</div>
<div th:replace="fragments.html :: footer"></div>
<div th:replace="fragments.html :: date-time"></div>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="<http://www.thymeleaf.org>">
<head th:replace="fragments.html :: head"></head>
<body class="bg-light">
<div th:replace="fragments.html :: main-nav"></div>
<div class="container">
<div class="py-5 text-center">
<h2>계정 만들기</h2>
</div>
<div class="row justify-content-center">
<!--URL 주소@링크표시 //메서드가 post이니 post를 찾는다.-->
<form class="needs-validation col-sm-6" action="#"
th:action="@{/sign-up}" th:object="${signUpForm}" method="post" novalidate>
<div class="form-group">
<label for="nickname">닉네임</label>
<input id="nickname" type="text" th:field="*{nickname}" class="form-control"
placeholder="whiteship" aria-describedby="nicknameHelp" required minlength="3" maxlength="20">
<small id="nicknameHelp" class="form-text text-muted">
공백없이 문자와 숫자로만 3자 이상 20자 이내로 입력하세요. 가입후에 변경할 수 있습니다.
</small>
<small class="invalid-feedback">닉네임을 입력하세요.</small>
<small class="form-text text-danger" th:if="${#fields.hasErrors('nickname')}" th:errors="*{nickname}">Nickname Error</small>
</div>
<div class="form-group">
<label for="email">이메일</label>
<input id="email" type="email" th:field="*{email}" class="form-control"
placeholder="[email protected]" aria-describedby="emailHelp" required>
<small id="emailHelp" class="form-text text-muted">
스터디올래는 사용자의 이메일을 공개하지 않습니다.
</small>
<small class="invalid-feedback">이메일을 입력하세요.</small>
<small class="form-text text-danger" th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Email Error</small>
</div>
<div class="form-group">
<label for="password">패스워드</label>
<input id="password" type="password" th:field="*{password}" class="form-control"
aria-describedby="passwordHelp" required minlength="8" maxlength="50">
<small id="passwordHelp" class="form-text text-muted">
8자 이상 50자 이내로 입력하세요. 영문자, 숫자, 특수기호를 사용할 수 있으며 공백은 사용할 수 없습니다.
</small>
<small class="invalid-feedback">패스워드를 입력하세요.</small>
<small class="form-text text-danger" th:if="${#fields.hasErrors('password')}" th:errors="*{password}">Password Error</small>
</div>
<div class="form-group">
<button class="btn btn-primary btn-block" type="submit"
aria-describedby="submitHelp">가입하기</button>
<small id="submitHelp" class="form-text text-muted">
<a href="#">약관</a>에 동의하시면 가입하기 버튼을 클릭하세요.
</small>
</div>
</form>
</div>
<div class="fragments.html :: footer"></div>
</div>
<script th:replace="fragments.html :: form-validation"></script>
</body>
</html>
package com.studyolle.modules.account;
import com.studyolle.modules.account.form.SignUpForm;
import com.studyolle.modules.account.validator.SignUpFormValidator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
@Controller
/*컨트롤러어노테이션은 initbinder를 가질수 있다.*/
/*nonnull이나 final의 생성자를 생성한다. 생성자는 객체에 값이나 초기화를 위해서이다.*/
@RequiredArgsConstructor
public class AccountController {
private final SignUpFormValidator signUpFormValidator;
private final AccountService accountService;
private final AccountRepository accountRepository;
//인잇바인더는 컨트롤러 친구 객체 검증을 위한
@InitBinder("signUpForm")
/*WebDataBinder는 인스턴스를 초기화를 시킬때 쓴다.*/
/*signUpFormValidator*/
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.addValidators(signUpFormValidator);
}
//get일때
@GetMapping("/sign-up")
public String signUpForm(Model model) {
/*모델에 담긴 값을 */
model.addAttribute(new SignUpForm());
return "account/sign-up";
} //모델에 signForm을 담는다.
//post일때
@PostMapping("/sign-up")
//포스트일떄 맵핑을 시킨다.
//@valid가 init을실행을
public String signUpSubmit(@Valid SignUpForm signUpForm, Errors errors) {
if (errors.hasErrors()) {
return "account/sign-up";
}
/*account서비스에 가서 signform을 하고 acoount에 저장*/
Account account = accountService.processNewAccount(signUpForm);
accountService.login(account);
/* /로 가세요 */
return "redirect:/";
}
@GetMapping("/check-email-token")
public String checkEmailToken(String token, String email, Model model) {
Account account = accountRepository.findByEmail(email);
String view = "account/checked-email";
if (account == null) {
model.addAttribute("error", "wrong.email");
return view;
}
if (!account.isValidToken(token)) {
model.addAttribute("error", "wrong.token");
return view;
}
accountService.completeSignUp(account);
model.addAttribute("numberOfUser", accountRepository.count());
model.addAttribute("nickname", account.getNickname());
return view;
}
@GetMapping("/check-email")
public String checkEmail(@CurrentAccount Account account, Model model) {
model.addAttribute("email", account.getEmail());
return "account/check-email";
}
@GetMapping("/resend-confirm-email")
public String resendConfirmEmail(@CurrentAccount Account account, Model model) {
if (!account.canSendConfirmEmail()) {
model.addAttribute("error", "인증 이메일은 1시간에 한번만 전송할 수 있습니다.");
model.addAttribute("email", account.getEmail());
return "account/check-email";
}
accountService.sendSignUpConfirmEmail(account);
return "redirect:/";
}
@GetMapping("/profile/{nickname}")
public String viewProfile(@PathVariable String nickname, Model model, @CurrentAccount Account account) {
Account accountToView = accountService.getAccount(nickname);
model.addAttribute(accountToView);
model.addAttribute("isOwner", accountToView.equals(account));
return "account/profile";
}
@GetMapping("/email-login")
public String emailLoginForm() {
return "account/email-login";
} /*여기로 간다.*/
@PostMapping("/email-login")
public String sendEmailLoginLink(String email, Model model, RedirectAttributes attributes) {
Account account = accountRepository.findByEmail(email);
if (account == null) {
model.addAttribute("error", "유효한 이메일 주소가 아닙니다.");
return "account/email-login";
}
if (!account.canSendConfirmEmail()) {
model.addAttribute("error", "이메일 로그인은 1시간 뒤에 사용할 수 있습니다.");
return "account/email-login";
}
accountService.sendLoginLink(account);
attributes.addFlashAttribute("message", "이메일 인증 메일을 발송했습니다.");
return "redirect:/email-login";
}
@GetMapping("/login-by-email")
public String loginByEmail(String token, String email, Model model) {
Account account = accountRepository.findByEmail(email);
String view = "account/logged-in-by-email";
if (account == null || !account.isValidToken(token)) {
model.addAttribute("error", "로그인할 수 없습니다.");
return view;
}
accountService.login(account);
return view;
}
}