[SpringBoot] 게시판 프로젝트 - 1

Hyeonseok Jeong·2023년 9월 23일
0
post-thumbnail

이번글은 이전 Servlet&JSP 로 만든 간단한 토이프젝과 SpringBoot로 한 토이 프젝 이후 무엇보다 중요한 기초를 기반으로 게시판 프로젝트를 시작하였다.

해당 글은 게시판 프로젝트의 복습용 + 회고록이다.

사용한 기술 스택

  • Spring Boot 3
  • Spring Security
  • Spring JPA
  • Java
  • MySQL
  • Thymeleaf
  • LomBok
  • HTML
  • CSS
  • Javascript

구현한 기능

  • 스프링 시큐리티를 이용한 로그인 기능
  • 스프링 시큐리티를 이용한 회원가입 기능
  • 게시판 리스트
  • 게시판 디테일 (다음글, 이전글 링크 구현)
  • 게시글 생성 & 다중 파일 업로드 기능 구현
  • 파일 다운로드 기능
  • 게시글 수정
  • 검색 기능
  • 페이지 네이션 기능
  • 댓글 생성
  • 댓글 삭제

오늘의 구현

  • 로그인
  • 회원가입

로그인 & 회원가입

프젝을 시작할때 무엇을 먼저 해야하는가 라는 고민이 있었다.
그러다가 든 생각은 모르겠으면 유저가 처음 접하는거 먼저 구현하면 되는게 아닌가라는 생각에 로그인과 회원가입의 코드를 먼저 작성하였다.

  1. 먼저 로그인을 구현하기 위해 Spring Security를 gradle의 Dependency에 추가해 주었다. (여기서 함정아닌 함정이 기다리고 있었다)
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
  1. 이후 로그인을 구현하기 위해 User Entity를 구현
package com.mysite.board.siteUser;


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

import java.time.LocalDateTime;

@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;

    private LocalDateTime createUserDateTime;

}
  • 여기서 username 과 password 등과 같이 JPA에서 정해진 이름을 정해진 역할에 사용하도록 하는 것들이 있으니 이해하도록 하자
  1. 구현한 Entity를 사용하기위해 (DTO) Repository 코드 작성
package com.mysite.board.siteUser;

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

import java.util.List;
import java.util.Optional;

public interface UserRepository extends JpaRepository<SiteUser, Long> {
    Optional<SiteUser> findByUsername(String username);
}
  1. 코드 수정에 용이하기 위해 Controller 단에서 전체 처리하는 것이 아닌 Service 로직 구현
package com.mysite.board.siteUser;


import com.mysite.board.DataNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Optional;

@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.setCreateUserDateTime(LocalDateTime.now());
        user.setPassword(this.passwordEncoder.encode(password));
        this.userRepository.save(user);
        return user;
    }

    public SiteUser getUser (String username) {
        Optional<SiteUser> _siteUser = this.userRepository.findByUsername(username);
        if(_siteUser.isPresent()) {
            return _siteUser.get();
        } else {
            throw new DataNotFoundException("user not found");
        }
    }

}
  • findByUsername()를 사용한 getUser()의 경우 이후 만들 기능들에 대해서 사용하기 위해 구현하였으며 create는 회원가입을 위한 로직이다.

user.setPassword(this.passwordEncoder.encode(password))
의 경우 @Bean 애노테이션을 이용해 만들어둔 PasswordEncoder를 통해서 패스워드를 암호화하여 DB에 저장하기위해 사용한 코드이다.

@Bean의 경우 SecurityConfig 클래스에 구현 (SecurityConfig 클래스는 스프링 시큐리티 설정을 하기위한 클래스이다)

@Bean
    AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
  1. 서비스 로직을 사용하여 Controller 로직 구현
package com.mysite.board.siteUser;


import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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;

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

    private final UserService userService;

    @GetMapping("/login")
    public String login () {
        return "signIn_form";
    }

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

    @PostMapping("/signup")
    public String signup (Model model, UserCreateForm userCreateForm, BindingResult bindingResult) {

        if(userCreateForm.getUsername().isEmpty() || userCreateForm.getEmail().isEmpty() || userCreateForm.getPassword1().isEmpty()) {
            System.out.println("회원가입 폼은 전체 필수 입력해야 합니다.");
            return "signup_form";
        }

        if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
            System.out.println("패스워드가 일치하지 않습니다.");
            return "signup_form";
        }

        try {
            this.userService.create(userCreateForm.getUsername(), userCreateForm.getEmail(), userCreateForm.getPassword1() );
        } catch (DataIntegrityViolationException e) {
            System.out.println("이미 등록된 사용자 입니다.");
            bindingResult.reject("alreadyUser", "이미 등록된 사용자입니다.");
            e.printStackTrace();
            return "signup_form";
        } catch (Exception e) {
            e.printStackTrace();
            return "signup_form";
        }


        return "redirect:/";
    }

}

유저 컨트롤러에서 처음 Validation 라이브러리를 이전 토이 프로젝트때 사용했는데 이번 프로젝트에서는 직접 구현해보고싶어서 따로 사용하지 않고 view에서 처리하게끔 구현해 보았다.

회원가입 당시에 모든 폼 입력사항들을 입력해야하는 부분과 패스워드 부분의 경우 확인이 제대로 되지 않는경우 다시 회원가입 폼으로 돌아가게되고
또한 이미 DB에 등록된 사용자인 경우에도 회원가입 폼으로 돌아가도록 구현하였다.
(마지막 유저 생성 부분은 bindingResult로 스프링에서 제공하는 예외처리로 구현하였다. - 사실 이렇게 된거면 다른부분들도 이런식으로 예외 처리를 했어야했나 싶다....)

그리고 회원가입 폼 부분과 패스워드 확인 부분을 view단에서 마무리 작업을 하였다.

<!-- signup_form tag start -->
      <form class="signup-form-box" th:action="@{/user/signup}" th:object="${userCreateForm}" method="post">
        <div class="err">
          <div class="id-err errI">아이디를 입력해주세요</div>
          <div class="pwd-err errI">비밀번호를 입력해주세요</div>
          <div class="pwd-err2 errI">
            비밀번호는 대,소문자 특수문자가 포함된 8자 이상이어야 합니다
          </div>
          <div class="pwd-check-err errI">비밀번호가 일치하지 않습니다</div>
          <div class="email-err errI">올바른 이메일 형식을 적어주세요</div>
        </div>
        <div class="signup-input-wrapper">
          <div class="box">
            <label for="username" class="label id-label">아이디</label>
            <input
              autofocus
              type="text"
              th:field="*{username}"
              class="signup-input id"
              placeholder="아이디를 입력해주세요"
            />
          </div>
          <div class="box">
            <label for="password1" class="label pwd-label">비밀번호</label>
            <input
              type="password"
              th:field="*{password1}"
              class="signup-input pwd"
              placeholder="비밀번호를 입력해주세요"
            />
          </div>
          <div class="box">
            <label for="password2" class="label pwd-check-label"
              >비밀번호 확인</label
            >
            <input
              type="password"
              th:field="*{password2}"
              class="signup-input pwd-check"
              placeholder="비밀번호를 다시 입력해주세요"
            />
          </div>
          <div class="box">
            <label for="email" class="label email-label">이메일</label>
            <input
              type="email"
              th:field="*{email}"
              class="signup-input email"
              placeholder="이메일을 입력해주세요"
            />
          </div>
        </div>
        <div class="signup-submit-wrapper">
          <input
            class="btn signup-btn"
            type="submit"
            name="signup"
            value="확인"
          />
          <a class="btn cancle-btn" th:href="@{/user/login}">취소</a>
        </div>
      </form>
      <!-- signup_form tag end -->
const id = document.querySelector(".id")
const pwd = document.querySelector(".pwd")
const pwdCh = document.querySelector(".pwd-check")
const email = document.querySelector(".email")
const signUpBtn = document.querySelector(".signup-btn")
// err document
const idErr = document.querySelector(".id-err")
const pwdErr = document.querySelector(".pwd-err")
const pwdErr2 = document.querySelector(".pwd-err2")
const pwdChErr = document.querySelector(".pwd-check-err")
const emailErr = document.querySelector(".email-err")


// 정규식
const pwdExp = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,15}$/;
const emailExp = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;

// input status
let idState = false;
let pwdState = false;
let pwdChState = false;
let emailState = false;

// function
// button style function
const signUpBtnStyle = () => {
    signUpBtn.addEventListener("mouseover", () => {
        signUpBtn.style.cursor = "pointer";
        signUpBtn.style.backgroundColor = "#a3a1a1";
        signUpBtn.style.color = "#e3dede";
        signUpBtn.style.transition = "all 0.3s ease-in-out";
    });
    signUpBtn.addEventListener("mouseout", () => {
        signUpBtn.style.cursor = "pointer";
        signUpBtn.style.backgroundColor = "white";
        signUpBtn.style.color = "#6e6e6e";
        signUpBtn.style.transition = "all 0.3s ease-in-out";
    });
};

const signUpDisableBtnStyle = () => {
    signUpBtn.addEventListener("mouseover", () => {
        signUpBtn.style.cursor = "default";
        signUpBtn.style.backgroundColor = "white";
        signUpBtn.style.color = "#6e6e6e";
        signUpBtn.style.transition = "all 0.3s ease-in-out";
    });
    signUpBtn.addEventListener("mouseout", () => {
        signUpBtn.style.cursor = "default";
        signUpBtn.style.backgroundColor = "white";
        signUpBtn.style.color = "#6e6e6e";
        signUpBtn.style.transition = "all 0.3s ease-in-out";
    });
};

// 확인 버튼 함수
const btnActHandler = () => {
    if (idState && pwdState && pwdChState && emailState) {
        signUpBtn.disabled = false;
        signUpBtnStyle();
    } else {
        signUpBtn.disabled = true;
        signUpDisableBtnStyle();
    }
}

window.onload = () => {
    btnActHandler();
}

id.addEventListener("keyup", (e) => {
    if (e.target.value != "") {
        idState = true;
        idErr.style.display = "none";
    } else {
        idState = false;
        idErr.style.display = "block";
    }
    btnActHandler();
})

let pwdTemp = "";
let pwdChTemp = "";
pwd.addEventListener("keyup", (e) => {
    pwdTemp = e.target.value;
    if (pwdExp.test(pwdTemp)) {
        pwdState = true;
        pwdErr2.style.display = "none";
    } else {
        pwdState = false;
        pwdErr2.style.display = "block";
    }

    if (pwdTemp == "") {
        pwdState = false;
        pwdErr.style.display = "block";
    } else {
        pwdState = true;
        pwdErr.style.display = "none";
    }

    // password check
    if (pwdChTemp == pwdTemp) {
        pwdChState = true;
        pwdChErr.style.display = "none";
    } else if (pwdChTemp != pwdTemp || e.target.value == "") {
        pwdChState = false;
        pwdChErr.style.display = "block";
    }

    btnActHandler();
})

pwdCh.addEventListener("keyup", (e) => {
    pwdChTemp = e.target.value;
    if (pwdChTemp == pwdTemp) {
        pwdChState = true;
        pwdChErr.style.display = "none";
    } else if (pwdChTemp != pwdTemp || e.target.value == "") {
        pwdChState = false;
        pwdChErr.style.display = "block";
    }
    btnActHandler();
})

email.addEventListener("keyup", (e) => {
    if (emailExp.test(e.target.value)) {
        emailState = true;
        emailErr.style.display = "none";
    } else {
        emailState = false;
        emailErr.style.display = "block";
    }
    btnActHandler();
})

view에서 마무리 작업을 각각의 keyup 이밴트를 통해서 조건들을 통과하지않으면 에러 매세지가 출력되고 확인 버튼은 비활성화 되어있다. 이후 조건을 만족하게 되면 에러 매세지가 사라지게되고 버튼이 활성화되어 회원가입 절차가 진행된다.

  1. 이후 클라이언트가 회원가입을 성공적으로 마친후 로그인을 할경우
    위에서 언급한 SecurityConfig 클래스에서 설정한대로 로그인 시도가 되게되고 뷰에서는 로그인, 회원가입 버튼 대신 로그아웃 버튼이 나타나게 된다.
    (그리고 여기서 버그아닌 버그가 나타났다...)

사실 버그라기에는 지식의 부족으로 일어난 일이기에 한참을 구글링을 해도 원인이 안나오는 이유가 있었다.

우선 SecurityConfig 파일을 먼저 보자면

package com.mysite.board;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
                        .requestMatchers(new AntPathRequestMatcher("/**")).permitAll()
                )
                .formLogin((formLogin) -> formLogin
                        .loginPage("/user/login")
                        .defaultSuccessUrl("/")
                )
                .logout((logout) -> logout
                        .logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
                        .logoutSuccessUrl("/user/login")
                        .invalidateHttpSession(true)
                )
        ;

        return http.build();
    }

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

    @Bean
    AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
}

코드를 봐보자면 SecurityFilterChain을 구현하여 HttpSecurity설정하였다.
@EnableWebSecurity는 모든 요청 URL이 스프링 시큐리티의 제어를 받도록 만드는 애너테이션이다. 해당 애너테이션을 사용하면 내부적으로 SpringSecurityFilterChain이 동작하여 URL 필터가 적용된다.

authorizeHttpRequests설정에서 new AntPathRequestMatcher(/**)로 사용자가 요청한 요청정보를 확인하여 요청정보 Url이 /으로 시작하는지 확인한다.(/의 경우 모든 url)
요청한다면 다음단계로(인증처리) 진행되고, 일치하지 않는다면 다음 필터로 진행된다.(chain.doFilter)
나는 모든 인가를 .permitAll()을 통해서 무조건 접근을 허용하였다.
( + 스프링 시큐리티의 공부가 시급하다.. )

이후 로그인처리를 위해 (스프링은 로그인이 내부적으로 처리되며 해당 로그인 처리를 위해 어느 URL에서 로그인을 처리하는지 설정해 주어야한다.) formLogin 설정과 로그아웃을 위해 logout설정을 해주었다. .formLogin .logout
추가적으로 .invalidateHttpSesstion(true)는 브라우저를 종료하지않고 로그아웃 실행시 로그인 하였던 정보를 삭제하기 위해 설정한 코드이다.

사실 이 코드들만 보면 어떤 부분이 문제가 되서 로그인 처리를 하게되면 보이면 안될 로그인 버튼이 보이는 건지를 알 수 없다. (코드상의 문제인가해서 구글링을 열심히 해보았으나 딱히 문제될만한 부분을 발견하지 못하였다)

그래서 view단이 잘못된건가 해서 확인을 해보았다.

<!-- navigation tag start th:네비게이션 플랫폼 작업 -->
<nav class="nav" th:fragment="navigation">
  <div class="move-box">
    <a th:href="@{/}" class="move home">HOME</a>
    <a th:href="@{/}" class="move free-board">자유게시판</a>
  </div>
  
  <div class="login-box">
    <a sec:authorize="isAnonymous()" class="sign" th:href="@{/user/login}">로그인</a>
    <a sec:authorize="isAnonymous()" class="sign" th:href="@{/user/signup}">회원가입</a>
    <a sec:authorize="isAuthenticated()" class="sign" th:href="@{/user/logout}">로그아웃</a>
  </div>
</nav>
<!-- navigation tag end -->

코드를 보면 로그인을 하게되면 타임리프 문법을 통해 authorization 인가 되었을 경우 로그인, 회원가입 부분이 보이지 않게 되고 로그아웃 버튼이 활성화 되어야하고 인가가 만료되었을 경우 그 반대로 로그인과 회원가입 버튼이 보여야한다.

그래서 타임리프 버전과 스프링 시큐리티, 스프링 부트 버전 문제인가해서 검색해 보았더니 버전이 문제가 될 수도있다고해서 따로 버전들을 바꿔서 실험해 보았으나 그래도 똑같이 버튼들이 모두 보이는 버그가 발생하였다.

이쯤이면 고수분들은 먼가 눈치를 챘을지도 모를것같다.
바로 타임리프와 스프링시큐리티를 호환하는 라이브러리를 Gradle에 설정을 해주지 않았다...
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
바로 해당 부분이다 그래들에 타임리프에 스프링시큐리티의 인증, 인가를 확인을 위해서 사용해 주어야하는 라이브러리를 설치해주지 않아 발생한 문제였던거다.

그렇게 프론트 공부당시에도 서로 호환을 위해 설치해주는 추가 라이브러리들을 신경써서 해주었는데 그 잠깐 백엔드 공부를 한다고 자바공부할동안 기본적인 부분을 잊어버린거였다.

반성하며 이걸로 해당 부분은 확실하게 기억할 수 있게되었으니 만족한다.

아무튼 해당 추가 라이브러리를 그래들에 적용해주고나니 로그인, 회원가입, 로그아웃 버튼들이 정상 작동하여 버그수정 완료하여 기능 구현을 완료하였다.

마무리

마무리로 다시한번 라이브러리를 사용할때는 같이 사용하는게 있다면 서로 호환되는지, 호환하여 사용한다면 추가적인 설치, 설정 하는 부분이 필요한지 생각해보자

오늘치 끝

추가

23-09-25 다음글을 쓸려고 정리하다 데이터베이스에서 사용자를 조회하는 서비스 부분을 안적어서 추가

로그인시에 사용자를 확인해야 하는 부분을 빼먹어버려서 추가합니다

사용자 조회

DB에서 사용자를 조회하기 위해 UserSecurityService를 만들고 해당 서비스를 스프링 시큐리티에 등록하는 방법으로 로그인을 구현하였다

먼저 UserRepository, UserRole 준비

package com.mysite.board.siteUser;

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

import java.util.List;
import java.util.Optional;

public interface UserRepository extends JpaRepository<SiteUser, Long> {
    Optional<SiteUser> findByUsername(String username);
}

DB에서 사용자 이름으로 유저를 찾기위해 findByUsername 메소드를 레포지터리에 추가

이후 사용자의 권한 설정을 위해 UserRole 작성

package com.mysite.board.siteUser;


import lombok.Getter;

@Getter
public enum UserRole {
    ADMIN("ROLE_ADMIN"),
    USER("ROLE_USER");

    UserRole(String value) {
        this.value = value;
    }

    private String value;

}

정말 간단하게 일반 유저와 어드민만을 enum으로 만들어 작성하였다
( 스프링 시큐리티 책을 하나 사서 제대로 공부해봐야되는데 고급 개발자분들께서 책추천해주시면 감사합니다!! ) - 이번 프로젝트에서는 로그인 구현을 목적으로 따로 어드민에 대한 권한 부분은 설정하지 않음

다음으로 UserSecurityService를 작성하여 스프링 시큐리티 설정에 등록

package com.mysite.board.siteUser;


import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RequiredArgsConstructor
@Service
public class UserSecurityService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<SiteUser> _siteUser = this.userRepository.findByUsername(username);
        if (_siteUser.isEmpty()) {
            throw new UsernameNotFoundException("사용자를 찾을수 없습니다.");
        }
        SiteUser siteUser = _siteUser.get();
        List<GrantedAuthority> authorities = new ArrayList<>();
        if ("admin".equals(username)) {
            authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.getValue()));
        } else {
            authorities.add(new SimpleGrantedAuthority(UserRole.USER.getValue()));
        }
        return new User(siteUser.getUsername(), siteUser.getPassword(), authorities);
    }

}

아직 해당 스프링 시큐리티에 대한 전반적인 이해가 부족한것 같다. 하지만 설명해보자면

지금 작성한 코드도 스프링 시큐리티에 등록하기 위해 UserDetailsService 인터페이스 즉 loadUserByUsername 메서드를 구현하도록 강제하는 인터페이스를 구현하였는데 loadUserByUsername 메서드는 사용자명으로 비밀번호를 조회하여 리턴하는 메서드이다.

loadUserByUsername 메서드는 사용자명으로 SiteUser 객체를 조회하고 만약 사용자명에 해당하는 데이터가 없을 경우에는 UsernameNotFoundException 오류를 내게하고, 사용자명이 "admin"인 경우에 ADMIN 권한을 부여, 그 이외의 경우에는 USER권한을 부여하였다.
또한 사용자명, 비밀번호, 권한을 입력으로 스프링 시큐리티의 User객체를 생성하여 리턴

스프링 시큐리티는 loadUserByUsername 메서드에 의해 리턴된 User객체의 비밀번호가 화면으로부터 입력받은 비밀번호와 일치하는지를 검사하는 로직을 내부적으로 가지고 있다.

이제 스프링 시큐리티에 AuthenticationManager 빈을 생성

    @Bean
    AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
}

AuthenticationManager는 스프링 시큐리티의 인증을 담당한다.
사용자 인증시 앞에서 작성한 UserSecurityServicePasswordEncoder(회원가입시 작성한 Bean) 을 사용한다.

이렇게 작성하게 되면 스프링 시큐리티 내부적으로 사용자가 맞는지 확인하여 맞다면 사용자 이름이 "admin"인 경우 ADMIN 권한을 그이외에는 USER 권한을 주게 되고 로그인이 된다.

그다음으로는 위에서 작성한데로 로그인이 되어 로그인, 회원가입 버튼이 사라지고 로그아웃 버튼이 남게 된다.

사진

로그인, 회원가입, 로그아웃 의 캡쳐본

  • 로그인
    설명하는 글씨에서 비밀번호 조건이 틀림 숫자는 해당사항 아님 대,소문자 특수문자 8자 이상이 조건임

  • 로그인 시 로그아웃 버튼이 보여짐

  • 회원가입

비밀번호 조건이 틀릴경우 이와 같이 경고 메세지 출력되고 회원가입 불가능
프론트에서 조건을 걸어버림 ( 다시 생각해보니 프론트에서 조건을 걸고 검증을 한다고 해도 백엔드에서 다시한번 검증을 해야 보안적으로 좋을 것 같다;;;)

그럼 추가사항들은 다 적었으니 정말 끝!

profile
풀스텍 개발자

2개의 댓글

comment-user-thumbnail
2024년 1월 16일

안녕하세요 글 잘보고 갑니다.저는 일다 jsp로 게시판을 만들었는데 파일업로드와 이미지가 보이게 하는거와, dao dto컨트롤러에 관한것은 하나도 안했습니다. 파일업로드를 먼저 구현하는게 나을까요?. 아니면 컨트롤로, 서블릿, jstl을 먼저 보는게 나을까요?. 사실 지금 좀 기일이 급해서요..^^;

답글 달기
comment-user-thumbnail
2024년 2월 26일

UserCreateForm는 어떻게 만드셨는지 궁금하고 파일 경로도 같이 올려주셨으면 좋겠어요!

답글 달기