νμ κ°μ
Code
Preview
1.
λ©μΈ νλ©΄
2.
νμ κ°μ
μμ νλ‘μΈμ€
1.
νλ‘μ νΈ μμ±
2.
νλ‘μ νΈ μ€μ
3.
ERD
4.
ν
μ΄λΈ μ μ
5.
λλ©μΈ
6.
λ°μ΄ν°
7.
μλΉμ€
8.
μ€νλ§ μνλ¦¬ν° μ€μ
9.
μμ² κ²½λ‘ λ§€ν
Preview
β’
λ©μΈ νλ©΄
β’
νμ κ°μ
λ©μΈ νλ©΄
νμ κ°μ
μμ νλ‘μΈμ€
1.
νλ‘μ νΈ μμ±
β’
build.gradle
β’
spring boot version : 3.x.x
β’
spring security version : 6.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 3.x.x
spring security 6.x.x
plugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '3.3.5'
id 'io.spring.dependency-management' version '1.1.6'
}
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:3.0.3'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
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:3.0.3'
testImplementation 'org.springframework.security:spring-security-test'
}
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=security6
# λ°μ΄ν° μμ€ - 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.security6.domain
mybatis.mapper-locations=classpath:mybatis/mapper/**/**.xml
Markdown
볡μ¬
ERD
ν μ΄λΈ μ μ
β’
USER
β’
USER_AUTH
USER
CREATE TABLE `user` (
`NO` bigint NOT NULL AUTO_INCREMENT,
`USERNAME` varchar(100) NOT NULL,
`PASSWORD` varchar(200) NOT NULL,
`NAME` varchar(100) NOT NULL,
`EMAIL` varchar(200) DEFAULT NULL,
`CREATED_AT` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`UPDATED_AT` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`ENABLED` int DEFAULT 1,
PRIMARY KEY (`NO`)
) COMMENT='νμ';
SQL
볡μ¬
USER_AUTH
CREATE TABLE `user_auth` (
no bigint NOT NULL AUTO_INCREMENT -- κΆνλ²νΈ
, username varchar(100) NOT NULL -- μμ΄λ
, auth varchar(100) NOT NULL -- κΆν (ROLE_USER, ROLE_ADMIN, ...)
, PRIMARY KEY(no)
);
SQL
볡μ¬
λλ©μΈ
β’
Users.java
β’
UserAuth.java
Users.java
@Data
public class Users {
private Long no;
private String username;
private String password;
private String name;
private String email;
private Date createdAt;
private Date updatedAt;
private int enabled;
private List<UserAuth> authList;
}
Java
볡μ¬
UserAuth.java
@Data
public class UserAuth {
private Long no;
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.security6.mapper.UserMapper">
<resultMap type="Users" id="userMap">
<id property="no" column="no" />
<result property="no" column="no" />
<result property="username" column="username" />
<result property="password" column="password" />
<result property="name" column="name" />
<result property="email" column="email" />
<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="no" column="no" />
<result property="username" column="username" />
<result property="auth" column="auth" />
</resultMap>
<!-- νμ μ‘°ν - id -->
<select id="select" resultMap="userMap">
SELECT u.no
,u.username
,password
,name
,email
,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 ( username, password, name, email )
VALUES ( #{username}, #{password}, #{name}, #{email} )
</insert>
<!-- νμ μ 보 μμ -->
<update id="update">
UPDATE user
SET name = #{name}
,email = #{email}
,updated_at = now()
WHERE username = #{username}
</update>
<!-- νμ κΆν λ±λ‘ -->
<insert id="insertAuth">
INSERT INTO user_auth( username, auth )
VALUES ( #{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.authorizeHttpRequests(auth -> auth
.requestMatchers("/**").permitAll());
return http.build();
}
/**
* π μνΈν λ°©μ λΉ λ±λ‘
* @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>νμκ°μ
</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
볡μ¬