출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1/dashboard

 

스프링과 JPA 기반 웹 애플리케이션 개발 - 인프런

이 강좌에서 여러분은 실제로 운영 중인 서비스를 스프링, JPA 그리고 타임리프를 비롯한 여러 자바 기반의 여러 오픈 소스 기술을 사용하여 웹 애플리케이션을 개발하는 과정을 학습할 수 있습

www.inflearn.com

 

 

Constraint validation - Developer guides | MDN

The creation of web forms has always been a complex task. While marking up the form itself is easy, checking whether each field has a valid and coherent value is more difficult, and informing the user about the problem may become a headache. HTML5 introduc

developer.mozilla.org

 

Bulid (Ctrl + F9)으로 서버 재시작을 안해도 변경 사항이 적용됨

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"><!--thymeleaf 사용하기 위한 xmlnamespace 설정-->
<head>
    <meta charset="UTF-8">
    <title>StudyOlle</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
    <!--style은 head에 위치시켜 rendering 적용시켜 렌더링 될 때 스타일 적용시켜 읽히고-->
    <style>
        .container {
            max-width: 100%:
        }
    </style>
</head>
<body class="bg-light">
    <nav class="navbar navbar-expand-sm navbar-dark bg-dark">
        <a class="navbar-brand" href="/" th:href="@{/}"><!--thymeleaf로 렌더링 할때 href값을 이 값을 쓴다.-->
        <img src="/images/logo_sm.png" width="30" height="30">
        </a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <form th:action="@{/search/study}" class="form-inline" method="get">
                        <input class="form-control mr-sm-2" name="keyword" type="search" placeholder="스터디 찾기">
                    </form>
                </li>
            </ul>

            <ul class="navbar-nav .justify-content-end">
                <li class="nav-item">
                    <a class="nav-link" href="#" th:href="@{/login}">로그인</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#" th:href="@{/signup}">가입</a>
                </li>
            </ul>
        </div>
    </nav>

    <div class="container">
        <div class="py-5 text-center">
            <h2>계정 만들기</h2>
        </div>
        <div class="row justify-content-center">
            <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="devjun" 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="your@email.com" 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>

        <footer th:fragment="footer">
            <div class="row justify-content-center">
                <img class="mb-2" src="/images/logo_kr.png" alt="" width="100">
                <small class="d-block mb-3 text-muted">&copy; 2021</small>
            </div>
        </footer>
    </div>



<!--자바스크립트는 로딩할때 시간이 걸릴 수가 있어서 위쪽 블럭에 넣어두면 로딩하느라 끊겨서 렌더링이 늦어진다.-->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>

    <script type="application/javascript">
    (function () {
        'use strict';

        window.addEventListener('load', function () {
            // Fetch all the forms we want to apply custom Bootstrap validation styles to
            var forms = document.getElementsByClassName('needs-validation');

            // Loop over them and prevent submission
            Array.prototype.filter.call(forms, function (form) {
                form.addEventListener('submit', function (event) {
                    if (form.checkValidity() === false) {
                        event.preventDefault();
                        event.stopPropagation();
                    }
                    form.classList.add('was-validated')
                }, false)
            })
        }, false)
    }())
</script>
</body>
</html>

thymeleaf로 렌더링 할때, 하지 않을때 둘 다 동작 하도록 설정 ex) href = # , th:href =@{login}

th:object="${signUpForm}" 에러

->SignUpForm Class

package com.studyolle.account;

import lombok.Data;

@Data
public class SignUpForm {
    private String nickname;

    private String email;

    private String password;
}

-> AccountController Class (model.addAttribute)

package com.studyolle.account;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class AccountController {

    @GetMapping("/sign-up")
    public String signUpForm(Model model) {
        model.addAttribute("signUpForm", new SignUpForm());
        return "account/sign-up";
    }
}

이미지 설정을 했음에도 올라가지 않은 이유 -> SpringSecurity

package com.studyolle.config;


import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //EnableWebSecurity -> 직접 스프링 시큐리티 설정을 하겠다는 어노테이션
    // websecurity 설정을 좀 손쉽게 하려면 WebSecurityConfigurerAdapter를 상속

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 원하는 특정 요청들을 authorize 체크를 하지 않도록 걸러낼 수 있음
        http.authorizeRequests()
                .mvcMatchers("/", "/login", "/sign-up", "/check-email", "/check-email-token",
                "/email-login", "/check-email-login", "/login-link").permitAll()       // mvcMatchers로 해당 링크는 모두 허용한다.
                .mvcMatchers(HttpMethod.GET, "/profile/*").permitAll()      // profile 링크는 GET만 허용한다.
                .anyRequest().authenticated();                                          // 그 외 링크는 모두 인증해야 쓸 수 있다.

    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
                //PathRequest하는것중 static한 Resource에 위치한 것들은 SpringSecurity적용하지 말아라.
    }
}

 

아무리 justify-content-end를 수정해봐도 끝쪽으로 붙지 않는다.

내일 다시 봐야겠다.

-> Bootstrap이 2021-02-16일 기준 5.0이 default로 되어있어서 해당 cdn코드를 복붙해서 적용했는데

강의기준으로 되어있는 4.4.1버전으로 변경했더니 되었다.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
=>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">