목차

개발환경

프로젝트 생성 및 기본 설정

application.yml 설정

Spring Security config 생성 및 설정

회원가입 form 생성

컨트롤러 생성

프론트 페이지 생성

프로젝트 구조

GitHub 링크


개발환경

개발 환경 버전
Java 17
Spring Boot 3.2.7
Spring Security 6.2.5
Spring Data JPA 3.2.7
Lombok 1.18.30
빌드 관리 도구 Gradle 8.8
템플릿 엔진 Mustache
DB MySQL 8.0.36
개발 도구 Spring Tool Suite 4.22.1


프로젝트 생성 및 기본 설정

File > New > Spring Starter Project

참고사진_1


참고사진_2


Spring Boot Version 3.2.7, 의존성 추가

참고사진_3


application.yml 설정

application.properties -> application.yml 변경

spring:
  application:
    name: login

server:
  servlet:
    session:
      timeout: 3600 #세션 유지시간 설정
    encoding:
      force: true #mustache UTF-8

---
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: [URL]
    username: [아이디]
    password: [비밀번호]
  jpa:
    properties:
      hibernate:
        format_sql: true #JPA 쿼리문 정렬된 형태로 출력

# JPA 쿼리문, binding parameter 출력
logging:
  level:
    org:
      hibernate:
        SQL: TRACE
        orm:
          jdbc:
            bind: TRACE


스프링 시큐리티 config 생성 및 설정

SecurityConfig.java

package com.example.login.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        
        // csrf token
        http
            .csrf((csrf) -> csrf.disable());  // 비활성화
        
        // 인가
        http
            .authorizeHttpRequests((authorize) -> authorize
                    .requestMatchers("/", "/login", "/loginProc", "/join", "/joinProc").permitAll()  // 보안 검사 없이 접근을 허용 
                    .requestMatchers("/member/**").hasAnyRole("ADMIN", "USER")  // role - ADMIN, USER만 접근 허용
                    .requestMatchers("/admin").hasRole("ADMIN")  // role - ADMIN만 접근 허용
                    .anyRequest().authenticated()
            );

        // Custom Login
        http
            .formLogin((form) -> form.loginPage("/login")
                    .usernameParameter("loginId")  // 아이디 parameter 설정
                    .passwordParameter("loginPwd") // 비밀번호 parameter 설정
                    .loginProcessingUrl("/loginProc")
                    .permitAll()
            );
        
        // 다중 로그인
        http
            .sessionManagement(session -> session
                    .maximumSessions(1) // 다중 로그인 허용 개수
                    .maxSessionsPreventsLogin(true) // 새로운 로그인 차단
            );
        
        // 세션 고정 보호
        http
            .sessionManagement((session) -> session
                    .sessionFixation((sessionFixation) -> sessionFixation
                            .newSession()   // 로그인 시 세션 신규 생성
                            //.changeSessionId() // 로그인 시 세션은 그대로 두고 세션 아이디만 변경
                    )
            );
        
        // 로그아웃
        http
            .logout((logout) -> logout.logoutUrl("/logout")
                    .logoutSuccessUrl("/")
            );

        return http.build();
    }

    /**
     * BCrypt 암호화
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}


컨트롤러 생성

메인 컨트롤러

MainController.java

package com.example.login.controller;

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

@Controller
public class MainController {

    /**
     * 메인
     * @return
     */
    @GetMapping("/")
    public String index() {
        return "index";
    }

}


회원가입 컨트롤러

JoinController.java

package com.example.login.controller;

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

@Controller
public class JoinController {

    /**
     * 회원가입
     * @return
     */
    @GetMapping("/join")
    public String join() {
        return "join";
    }

}


프론트 페이지 생성

메인 페이지

index.mustache

<!DOCTYPE html>
<html lang="ko"> 
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>메인 페이지</title>
        <style>
            .container {
                width: 800px !important ;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="col-md-12 pt-5">
                    <div class="card mb-4">
                        <h5 class="card-header text-center">메인 페이지</h5>
                        <div class="card-body">
                            <div class="col-md-12 text-center">
                                <a href="/join" class="btn btn-outline-primary">회원가입</a>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
    </body>
</html>


회원가입 페이지

join.mustache

<!DOCTYPE html>
<html lang="ko"> 
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>회원가입</title>
        <style>
            .container {
                width: 800px !important ;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <form name="joinForm" method="post" action="/joinProc">
                <div class="row">
                    <div class="col-md-12 pt-5">
                        <div class="card mb-4">
                            <h5 class="card-header text-center">회원가입</h5>
                            <div class="card-body">
                                <div class="mb-3 row">
                                    <label for="html5-text-input" class="col-md-2 col-form-label">아이디</label>
                                    <div class="col-md-10">
                                        <input class="form-control" type="text" id="loginId" name="loginId" />
                                    </div>
                                </div>
                                <div class="mb-3 row">
                                    <label for="html5-search-input" class="col-md-2 col-form-label">비밀번호</label>
                                    <div class="col-md-10">
                                        <input class="form-control" id="loginPwd" name="loginPwd" type="password" />
                                    </div>
                                </div>
                                <div class="mb-3 row">
                                    <label for="html5-search-input" class="col-md-2 col-form-label">닉네임</label>
                                    <div class="col-md-10">
                                        <input class="form-control" type="text" id="nickname" name="nickname" />
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-12 text-center">
                        <button type="submit" class="btn btn-outline-danger">회원가입</button>
                        <a href="/" class="btn btn-outline-secondary">취소</a>
                    </div>
                </div>
            </form>
        </div>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
    </body>
</html>


프로젝트 구조

참고사진_4


GitHub 링크

전체 소스 다운로드

©2024, DevDream