Search
Duplicate
moon
sun

회원 가입

회원 가입

📕 Spring Security 5.7

Code

Preview

1.
메인 화면
2.
회원 가입

작업 프로세스

1.
프로젝트 생성
2.
프로젝트 설정
3.
ERD
4.
테이블 정의
5.
도메인
6.
데이터
7.
서비스
8.
스프링 시큐리티 설정
9.
요청 경로 매핑

Preview

메인 화면
회원 가입

메인 화면

회원 가입

작업 프로세스

1.
프로젝트 생성
📄 build.gradle
spring boot version : 2.x.x
spring security version : 5.x.x
의존성 설정
Spring Web
Spring Boot DevTools
Spring Security
Lombok
Thymeleaf
MySQL Driver
Mybatis Framework
2.
프로젝트 설정
📄 application.properties
데이터 소스 설정
MyBatis 설정
4.
테이블 정의
USER
USER_AUTH
5.
도메인
Users.java
UserAuth.java
6.
데이터
UserMapper.xml
UserMapper.java
7.
서비스
UserService.java
UserServiceImpl.java
8.
스프링 시큐리티 설정
📄 ~/config/SecurityConfig.java
📄 ~/config/CommonConfig.java
9.
요청 경로 매핑
📄 ~/controller/HomeController.java
메인 화면
GET
🔗 /
📄 index.html
회원 가입 화면
GET
🔗 /join
📄 join.html
회원 가입 처리
POST
🔗 /join
⭕ ➡ /login (로그인으로 이동)
❌ ➡ /join?error (회원가입으로 다시 이동)
아이디 중복 확인
POST
🔗 /check/{username}
⭕ ➡ true
❌ ➡ false

프로젝트 생성

build.gradle

✅ spring boot 2.x.x
✅ spring security 5.x.x
plugins { id 'java' id 'war' id 'org.springframework.boot' version '2.7.17' id 'io.spring.dependency-management' version '1.0.15.RELEASE' } group = 'com.aloha' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '17' } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.3.1' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() }
Java
복사

Spring Boot Version

Language

Group Id

Artifact Id

packaging type

Java version

의존성 설정

1.
Spring Web
2.
Spring boot devtools
3.
Lombok
4.
Thymeleaf
5.
Spring Security
6.
MySQL Driver
7.
Mybatis Framework

Spring Web

Spring boot devtools

Lombok

Thymeleaf

Spring Security

MySQL Driver

Mybatis Framework

프로젝트 설정

📄 application.properties

데이터 소스 설정
MyBatis 설정
spring.application.name=form # 데이터 소스 - MySQL spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/aloha?serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true&useSSL=false&autoReconnection=true&autoReconnection=true spring.datasource.username=aloha spring.datasource.password=123456 # Mybatis 설정 mybatis.configuration.map-underscore-to-camel-case=true mybatis.type-aliases-package=com.aloha.form.domain mybatis.mapper-locations=classpath:mybatis/mapper/**/**.xml
Markdown
복사

ERD

테이블 정의

USER
USER_AUTH

USER

CREATE TABLE `user` ( `ID` CHAR(36) PRIMARY KEY, `USERNAME` VARCHAR(100) NOT NULL UNIQUE, `PASSWORD` VARCHAR(200) NOT NULL, `NAME` VARCHAR(100) NOT NULL, `EMAIL` varchar(200) DEFAULT NULL, `PROFILE` TEXT DEFAULT NULL, `CREATED_AT` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `UPDATED_AT` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `ENABLED` int DEFAULT 1 ) COMMENT='회원';
SQL
복사

USER_AUTH

CREATE TABLE `user_auth` ( `ID` CHAR(36) PRIMARY KEY -- ID , `USERNAME` varchar(100) NOT NULL -- 회원 아이디 , `AUTH` VARCHAR(100) NOT NULL -- 권한 (ROLE_USER, ROLE_ADMIN, ...) );
SQL
복사

도메인

Users.java
UserAuth.java

Users.java

@Data public class Users { private String id; private String username; private String password; private String name; private String email; private String profile; private Date createdAt; private Date updatedAt; private int enabled; private List<UserAuth> authList; }
Java
복사

UserAuth.java

@Data public class UserAuth { private String id; private String username; private String auth; }
Java
복사

데이터

UserMapper.xml
UserMapper.java

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.aloha.form.mapper.UserMapper"> <resultMap type="Users" id="userMap"> <id property="id" column="id" /> <result property="id" column="id" /> <result property="username" column="username" /> <result property="password" column="password" /> <result property="name" column="name" /> <result property="email" column="email" /> <result property="profile" column="profile" /> <result property="enabled" column="enabled" /> <result property="createdAt" column="created_at" /> <result property="updatedAt" column="updated_at" /> <collection property="authList" resultMap="authMap"></collection> </resultMap> <resultMap type="UserAuth" id="authMap"> <result property="id" column="id" /> <result property="username" column="username" /> <result property="auth" column="auth" /> </resultMap> <!-- 회원 조회 - id --> <select id="select" resultMap="userMap"> SELECT u.id ,u.username ,password ,name ,email ,profile ,enabled ,created_at ,updated_at ,auth FROM user u LEFT OUTER JOIN user_auth auth ON u.username = auth.username WHERE u.username = #{username} </select> <!-- 회원 가입 --> <insert id="join"> INSERT INTO user ( id, username, password, name, email, profile ) VALUES ( UUID(), #{username}, #{password}, #{name}, #{email}, #{profile} ) </insert> <!-- 회원 정보 수정 --> <update id="update"> UPDATE user SET name = #{name} ,email = #{email} ,profile = #{profile} ,updated_at = now() WHERE username = #{username} </update> <!-- 회원 권한 등록 --> <insert id="insertAuth"> INSERT INTO user_auth( id, username, auth ) VALUES ( UUID(), #{username}, #{auth} ) </insert> </mapper>
XML
복사

UserMapper.java

@Mapper public interface UserMapper { // 회원 조회 public Users select(String id) throws Exception; // 회원 가입 public int join(Users user) throws Exception; // 회원 수정 public int update(Users user) throws Exception; // 회원 권한 등록 public int insertAuth(UserAuth userAuth) throws Exception; }
Java
복사

서비스

UserService.java
UserServiceImpl.java

UserService.java

public interface UserService { // 조회 public Users select(String username) throws Exception; // 회원 가입 public int join(Users user) throws Exception; // 회원 수정 public int update(Users user) throws Exception; // 회원 권한 등록 public int insertAuth(UserAuth userAuth) throws Exception; }
Java
복사

UserServiceImpl.java

@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private PasswordEncoder passwordEncoder; @Override public Users select(String username) throws Exception { Users user = userMapper.select(username); return user; } @Override public int join(Users user) throws Exception { String username = user.getUsername(); String password = user.getPassword(); String encodedPassword = passwordEncoder.encode(password); // 🔒 비밀번호 암호화 user.setPassword(encodedPassword); // 회원 등록 int result = userMapper.join(user); if( result > 0 ) { // 회원 기본 권한 등록 UserAuth userAuth = new UserAuth(); userAuth.setUsername(username); userAuth.setAuth("ROLE_USER"); result = userMapper.insertAuth(userAuth); } return result; } @Override public int update(Users user) throws Exception { int result = userMapper.update(user); return result; } @Override public int insertAuth(UserAuth userAuth) throws Exception { int result = userMapper.insertAuth(userAuth); return result; } }
Java
복사

스프링 시큐리티 설정

📄 ~/config/SecurityConfig.java
📄 ~/config/CommonConfig.java

📄 ~/config/SecurityConfig.java

@Configuration @EnableWebSecurity public class SecurityConfig { // 스프링 시큐리티 설정 메소드 @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // ✅ 인가 설정 http.authorizeRequests(requests -> requests .antMatchers("/**").permitAll() .anyRequest().permitAll() ); return http.build(); } }
Java
복사

📄 ~/config/CommonConfig.java

@Configuration public class CommonConfig { /** * 🍃 암호화 방식 빈 등록 * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
Java
복사

요청 경로 매핑

📄 ~/controller/HomeController.java
메인 화면
GET
🔗 /
📄 index.html
회원 가입 화면
GET
🔗 /join
📄 join.html
회원 가입 처리
POST
🔗 /join
⭕ ➡ /login (로그인으로 이동)
❌ ➡ /join?error (회원가입으로 다시 이동)
아이디 중복 확인
POST
🔗 /check/{username}
⭕ ➡ true
❌ ➡ false

📄 ~/controller/HomeController.java

@Slf4j @Controller public class HomeController { @Autowired private UserService userService; /** * 메인 화면 * 🔗 [GET] - / * 📄 index.html * @return */ @GetMapping("") public String home() { log.info(":::::::::: 메인 화면 ::::::::::"); return "index"; } /** * 회원 가입 화면 * 🔗 [GET] - /join * 📄 join.html * @return */ @GetMapping("/join") public String join() { log.info(":::::::::: 회원 가입 화면 ::::::::::"); return "join"; } /** * 회원 가입 처리 * 🔗 [POST] - /join * ➡ ⭕ /login * ❌ /join?error * @param user * @return * @throws Exception */ @PostMapping("/join") public String joinPro(Users user) throws Exception { log.info(":::::::::: 회원 가입 처리 ::::::::::"); log.info("user : " + user); int result = userService.join(user); if( result > 0 ) { return "redirect:/login"; } return "redirect/join?error"; } /** * 아이디 중복 검사 * @param username * @return * @throws Exception */ @ResponseBody @GetMapping("/check/{username}") public ResponseEntity<Boolean> userCheck(@PathVariable("username") String username) throws Exception { log.info("아이디 중복 확인 : " + username); Users user = userService.select(username); // 아이디 중복 if( user != null ) { log.info("중복된 아이디 입니다 - " + username); return new ResponseEntity<>(false, HttpStatus.OK); } // 사용 가능한 아이디입니다. log.info("사용 가능한 아이디 입니다." + username); return new ResponseEntity<>(true, HttpStatus.OK); } }
Java
복사

index.html

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>OAuth</title> <!-- bootstrap css --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container col-12 col-lg-4"> <div class="px-4 py-5 mt-5 text-center"> <h1 class="display-5 fw-bold text-body-emphasis">메인 화면</h1> </div> <!-- 비 로그인 시 --> <th:block sec:authorize="isAnonymous()"> <div class="d-grid gap-2"> <a href="/login" class="btn btn-lg btn-primary">로그인</a> <a href="/join" class="btn btn-lg btn-success">회원가입</a> </div> </th:block> <!-- 로그인 시 --> <th:block sec:authorize="isAuthenticated()"> <div class="card"> <div class="inner p-4"> <div class="d-flex flex-column align-items-center"> <div class="item my-2"> <img th:src="${user.profile}" alt="프로필" class="rounded-circle"> </div> <div class="item my-2"> <h3 th:text="${user.name}"></h3> </div> <div class="item my-2"> <h5 th:text="${user.email}"></h5> </div> <div class="item my-2 w-100"> <span sec:authentication="principal">인증된 사용자</span> </div> </div> </div> </div> <form action="/logout" method="post"> <!-- CSRF TOKEN --> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"> <div class="d-grid gap-2"> <button type="submit" class="btn btn-lg btn-primary">로그아웃</button> </div> </form> </th:block> </div> <!-- bootstrap js --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"></script> </body> </html>
HTML
복사

join.html

✅ 아이디 중복 검사 : 비동기 요청으로 이미 가입된 아이디가 있는지 확인하는 기능
👩🏼‍💻
비동기 요청은 JavaScript 로 XMLHttpRequest, fetch, axios 등을 사용할 수 있습니다.
비동기 관련 내용은 아래 페이지를 참조해보세요~!
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>OAuth</title> <!-- bootstrap css --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container col-12 col-lg-4"> <div class="px-4 py-5 mt-5 text-center"> <h1 class="display-5 fw-bold text-body-emphasis">회원 가입</h1> </div> <!-- 회원가입 영역 --> <main class="form-signin login-box w-100 m-auto"> <form id="form" action="/join" method="post" class="needs-validation" onsubmit="return checkSubmit(event)"> <!-- CSRF TOKEN --> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"> <div class="input-group my-2"> <div class="form-floating" id="box-id"> <input type="text" class="form-control" id="username" name="username" value="" placeholder="아이디" autofocus> <label for="username">아이디</label> </div> <div class="input-group-append"> <button type="button" class="btn btn-lg btn-outline-secondary h-100" onclick="checkId()" >중복확인</button> </div> </div> <div class="form-floating my-2"> <input type="password" class="form-control" id="password" name="password" placeholder="비밀번호"> <label for="password">비밀번호</label> </div> <div class="form-floating my-2"> <input type="password" class="form-control" id="passwordCheck" name="passwordCheck" placeholder="비밀번호 확인"> <label for="passwordCheck">비밀번호 확인</label> </div> <div class="form-floating my-2"> <input type="text" class="form-control" id="name" name="name" value="" placeholder="이름" autofocus=""> <label for="name">이름</label> </div> <div class="form-floating my-2"> <input type="text" class="form-control" id="email" name="email" value="" placeholder="이메을" autofocus=""> <label for="email">이메일</label> </div> <div class="d-grid gap-2"> <button class="btn btn-lg btn-primary w-100 py-2" type="submit">회원가입</button> <a href="/" class="btn btn-lg btn-success w-100 py-2">메인</a> </div> </form> </main> </div> <!-- bootstrap js --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"></script> <script> // 💍 CRSF TOKEN const csrfToken = "[[${_csrf.token}]]" /* 아이디 중복 확인 */ async function checkId() { const username = document.getElementById("username").value; // null 또는 undefined if (!username) { alert("아이디를 입력해주세요"); return; } try { // 아이디 중복 확인 const response = await fetch(`/check/${username}`, { method: 'GET', headers: { 'X-CSRF-TOKEN': csrfToken } }); if (response.ok) { const result = await response.text(); let boxId = document.getElementById('box-id'); if (result === 'true') { alert('사용 가능한 아이디입니다.'); boxId.classList.remove('needs-validation'); boxId.classList.add('was-validated'); return true; } else { alert('중복된 아이디입니다.'); boxId.classList.remove('was-validated'); boxId.classList.add('needs-validation'); } return false; } else { alert('아이디 중복 확인 중 오류가 발생했습니다.'); return false; } } catch (error) { console.error('Error:', error); alert('아이디 중복 확인 중 오류가 발생했습니다.'); return false; } } /* 제출 확인 - 아이디 중복 체크 */ async function checkSubmit(event) { event.preventDefault(); // 폼 제출 방지 // 아이디 중복 체크 const isIdAvailable = await checkId(); if (!isIdAvailable) { return; } document.getElementById("form").submit(); } </script> </body> </html>
HTML
복사