Compare commits
39 Commits
feature/31
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09fd3457e9 | ||
|
|
55527dfad3 | ||
|
|
5d516c95b6 | ||
|
|
743fe11dc6 | ||
|
|
a108cdd466 | ||
|
|
8107468562 | ||
|
|
30c92f18dd | ||
|
|
e95349f743 | ||
|
|
ac40f5b42a | ||
|
|
49e19666b2 | ||
|
|
afa9f93dab | ||
|
|
01c39b333e | ||
|
|
c95a7ec867 | ||
|
|
7b5135c109 | ||
|
|
2c93cc3dd2 | ||
|
|
be217c6b01 | ||
|
|
dbf8b046b0 | ||
|
|
6f183e38ec | ||
|
|
2c8b4c9a09 | ||
|
|
c0fcefa8c0 | ||
|
|
6036bd63ad | ||
|
|
cb5179abd7 | ||
|
|
9e376bfefb | ||
|
|
f4b11c755c | ||
|
|
af1a95b1c7 | ||
|
|
8cc64c879f | ||
|
|
e08c02a99a | ||
|
|
13566e1f86 | ||
|
|
e9115e5322 | ||
|
|
0210499e66 | ||
|
|
42c8b3df14 | ||
|
|
54c4481031 | ||
|
|
2d142eb829 | ||
|
|
09b6beee53 | ||
|
|
a39084a11a | ||
|
|
1beaaad422 | ||
|
|
826cfb0eaa | ||
|
|
37169355da | ||
|
|
3e7b928d8f |
@@ -1,24 +1,26 @@
|
||||
pipeline {
|
||||
agent any
|
||||
tools {
|
||||
jdk("openjdk-17")
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Init') {
|
||||
steps {
|
||||
sh 'printenv'
|
||||
script {
|
||||
sh 'whoami'
|
||||
sh 'printenv'
|
||||
|
||||
migration_script = './script/db_migration.sh'
|
||||
FLYWAY_CONFIG = '/home/ec2-user/flyway/flyway.conf'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('DB Migrate') {
|
||||
steps {
|
||||
sh """
|
||||
chmod 755 ${migration_script}
|
||||
/bin/bash ${migration_script}
|
||||
"""
|
||||
flywayrunner installationName: 'flywaytool-jenkins',
|
||||
flywayCommand: 'info migrate validate',
|
||||
commandLineArgs: "-configFiles=${FLYWAY_CONFIG}",
|
||||
credentialsId: 'ecb29499-7272-4e8b-b3ab-a7a3ab7eafab',
|
||||
url: '',
|
||||
locations: "filesystem:${WORKSPACE}/db/migration"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
49
README.md
49
README.md
@@ -5,11 +5,19 @@
|
||||
|
||||
<br>
|
||||
|
||||
## Specification
|
||||
- java 17
|
||||
- kotlin 1.6.21
|
||||
- Spring Boot 2.7.0
|
||||
- MySQL 8.0.21
|
||||
## :pushpin: Specification
|
||||
- Lang
|
||||
- java 17
|
||||
- kotlin 1.6.21
|
||||
- Framework
|
||||
- Spring Boot 2.7.0
|
||||
- DB
|
||||
- MySQL 8.0.21
|
||||
- Flyway(migration)
|
||||
- CI/CD
|
||||
- Jenkins
|
||||
- Cloud Server
|
||||
- AWS Lightsail(Amazon Linux2)
|
||||
|
||||
<br>
|
||||
|
||||
@@ -21,11 +29,11 @@
|
||||
- `dongne-common`
|
||||
- entity, repository, error, security(jwt util) 등 관리하는 공통모듈
|
||||
- `db`
|
||||
- flyway migration(gradle) 적용 모듈
|
||||
- flyway migration 관리 디렉토리
|
||||
|
||||
<br>
|
||||
|
||||
## :pushpin: Run Application
|
||||
## :pushpin: Setting
|
||||
|
||||
### 💽 로컬 DB 구성 (docker)
|
||||
- local에 DB(MySQL)용 docker container run
|
||||
@@ -34,31 +42,8 @@
|
||||
$ docker run --name mysql-server -e MYSQL_ROOT_PASSWORD=beaniejoy -d -p 3306:3306 mysql:8.0.21
|
||||
```
|
||||
|
||||
### 💽 DB Migration (flyway)
|
||||
[flyway doc](https://documentation.red-gate.com/fd/flyway-documentation-138346877.html)
|
||||
- **Info**
|
||||
Prints the details and status information about all the migrations
|
||||
```bash
|
||||
$ ./gradlew :db:flywayInfo
|
||||
```
|
||||
- **Validate**
|
||||
Validates the applied migrations against the available ones
|
||||
DB에 적용된 migration과 local에 적용된 migration 정보 일치 여부 체크
|
||||
```bash
|
||||
$ ./gradlew :db:flywayValidate
|
||||
```
|
||||
- **Migrate**
|
||||
Migrates the schema to the latest version
|
||||
migration 설정 내용들 반영
|
||||
```bash
|
||||
$ ./gradlew :db:flywayMigrate
|
||||
```
|
||||
- **Clean**
|
||||
Drops all objects (tables, views, procedures, triggers, …) in the configured schemas
|
||||
(prodution 단계에서는 절대 사용 X)
|
||||
```bash
|
||||
$ ./gradlew :db:flywayClean -i
|
||||
```
|
||||
### 💽 DB Migration (with flyway)
|
||||
- [DB migration directory README](https://github.com/beaniejoy/dongne-cafe-api/blob/main/db/README.md)
|
||||
|
||||
### 💽 docker compose 실행(수정 작업 진행중)
|
||||
- docker compose를 이용한 nginx, DB(MySQL), application 한꺼번에 실행하는 경우
|
||||
|
||||
@@ -67,6 +67,8 @@ subprojects {
|
||||
|
||||
// Logging
|
||||
implementation("io.github.microutils:kotlin-logging:${Version.Deps.KOTLIN_LOGGING}")
|
||||
implementation("com.google.code.gson:gson")
|
||||
|
||||
|
||||
// Test
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
|
||||
@@ -15,8 +15,4 @@ object Plugins {
|
||||
const val PLUGIN_SPRING = "plugin.spring"
|
||||
const val PLUGIN_JPA = "plugin.jpa"
|
||||
}
|
||||
|
||||
object FlywayDB {
|
||||
const val FLYWAY = "org.flywaydb.flyway"
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,4 @@ object Version {
|
||||
const val KOTLIN_LOGGING = "3.0.4"
|
||||
const val JWT = "0.11.5"
|
||||
}
|
||||
|
||||
object FlywayDB {
|
||||
const val FLYWAY_CORE = "9.8.1"
|
||||
}
|
||||
}
|
||||
56
db/README.md
Normal file
56
db/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# DB Migration
|
||||
|
||||
- flyway version: `9.15.4`
|
||||
- [flyway doc](https://documentation.red-gate.com/fd/flyway-documentation-138346877.html)
|
||||
|
||||
<br>
|
||||
|
||||
## :pushpin: Installation(Local)
|
||||
LOCAL 환경에 해당
|
||||
```shell
|
||||
$ brew install flyway
|
||||
```
|
||||
- macOS 전용
|
||||
|
||||
<br>
|
||||
|
||||
## :pushpin: Flyway Command
|
||||
|
||||
- **Clean**
|
||||
Drops all objects (tables, views, procedures, triggers, …) in the configured schemas
|
||||
(prodution 단계에서는 절대 사용 X)
|
||||
```bash
|
||||
$ flyway clean -configFiles=db/flyway.conf
|
||||
```
|
||||
|
||||
- **Info**
|
||||
Prints the details and status information about all the migrations
|
||||
```bash
|
||||
$ flyway info -configFiles=db/flyway.conf
|
||||
```
|
||||
|
||||
- **Migrate**
|
||||
Migrates the schema to the latest version
|
||||
migration 설정 내용들 반영
|
||||
```bash
|
||||
$ flyway migrate -configFiles=db/flyway.conf
|
||||
```
|
||||
|
||||
- **Validate**
|
||||
Validates the applied migrations against the available ones
|
||||
DB에 적용된 migration과 local에 적용된 migration 정보 일치 여부 체크
|
||||
```bash
|
||||
$ flyway validate -configFiles=db/flyway.conf
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## :pushpin: Migration for Local Env
|
||||
|
||||
```shell
|
||||
$ cd [PROJECT_ROOT_DIR]
|
||||
|
||||
$ chmod 755 ./script/migration-local.sh
|
||||
$ ./script/migration-local.sh
|
||||
```
|
||||
project의 root directory로 이동하는 것이 중요
|
||||
@@ -1,26 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.flywaydb:flyway-mysql:${Version.FlywayDB.FLYWAY_CORE}")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id(Plugins.FlywayDB.FLYWAY).version(Version.FlywayDB.FLYWAY_CORE)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.flywaydb:flyway-core:${Version.FlywayDB.FLYWAY_CORE}") // flyway
|
||||
}
|
||||
|
||||
flyway {
|
||||
baselineDescription = "Start Flyway Migration!"
|
||||
baselineOnMigrate = true
|
||||
baselineVersion = "000"
|
||||
locations = arrayOf("filesystem:./migration", "filesystem:./seed")
|
||||
configFiles = arrayOf("conf/flyway.conf")
|
||||
cleanDisabled = false // activate flywayClean
|
||||
ignoreMigrationPatterns = arrayOf("*:pending") // ignore validating pending(대기) state
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
flyway.url=jdbc:mysql://localhost:3306/dongne?autoreconnect=true&characterEncoding=utf8&serverTimezone=Asia/Seoul
|
||||
flyway.user=root
|
||||
flyway.password=beaniejoy
|
||||
flyway.driver=com.mysql.cj.jdbc.Driver
|
||||
10
db/flyway-local.conf
Normal file
10
db/flyway-local.conf
Normal file
@@ -0,0 +1,10 @@
|
||||
flyway.url=jdbc:mysql://localhost:3306/dongne?autoreconnect=true&characterEncoding=utf8&serverTimezone=Asia/Seoul
|
||||
flyway.user=root
|
||||
flyway.password=beaniejoy
|
||||
flyway.driver=com.mysql.cj.jdbc.Driver
|
||||
flyway.locations=filesystem:db/migration,db/seed
|
||||
|
||||
flyway.baselineOnMigrate=true
|
||||
flyway.baselineVersion=000
|
||||
# flyway.ignoreMigrationPatterns=*:pending
|
||||
flyway.cleanDisabled=false
|
||||
@@ -6,8 +6,8 @@ CREATE TABLE `cafe` (
|
||||
`total_rate` float NOT NULL COMMENT '카페 종합 평가 점수',
|
||||
`description` varchar(255) COMMENT '카페 상세설명',
|
||||
`created_at` datetime NOT NULL COMMENT '카페 등록날짜',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '카페 등록자',
|
||||
`created_by` varchar(320) NOT NULL COMMENT '카페 등록자',
|
||||
`updated_at` datetime NULL COMMENT '카페 변경날짜',
|
||||
`updated_by` varchar(20) NULL COMMENT '카페 변경자',
|
||||
`updated_by` varchar(320) NULL COMMENT '카페 변경자',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -3,9 +3,9 @@ CREATE TABLE `cafe_menu` (
|
||||
`name` varchar(50) NOT NULL COMMENT '카페 메뉴명',
|
||||
`price` decimal(10, 2) NOT NULL COMMENT '메뉴 가격',
|
||||
`created_at` datetime NOT NULL COMMENT '메뉴 등록날짜',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '메뉴 등록자',
|
||||
`created_by` varchar(320) NOT NULL COMMENT '메뉴 등록자',
|
||||
`updated_at` datetime COMMENT '메뉴 변경날짜',
|
||||
`updated_by` varchar(20) NULL COMMENT '메뉴 변경자',
|
||||
`updated_by` varchar(320) NULL COMMENT '메뉴 변경자',
|
||||
`cafe_id` bigint unsigned NOT NULL COMMENT '연관된 카페 ID',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `cafe_id` (`cafe_id`),
|
||||
|
||||
@@ -2,9 +2,9 @@ CREATE TABLE `cafe_image` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '카페 이미지 ID',
|
||||
`img_url` varchar(255) NOT NULL COMMENT '이미지 경로',
|
||||
`created_at` datetime NOT NULL COMMENT '이미지 등록날짜',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '이미지 등록자',
|
||||
`created_by` varchar(320) NOT NULL COMMENT '이미지 등록자',
|
||||
`updated_at` datetime COMMENT '이미지 변경날짜',
|
||||
`updated_by` varchar(20) NULL COMMENT '이미지 변경자',
|
||||
`updated_by` varchar(320) NULL COMMENT '이미지 변경자',
|
||||
`cafe_id` bigint unsigned NOT NULL COMMENT '연관된 카페 ID',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `cafe_id` (`cafe_id`),
|
||||
|
||||
@@ -2,9 +2,9 @@ CREATE TABLE `menu_option`(
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '옵션 ID',
|
||||
`title` varchar(50) NOT NULL COMMENT '메뉴 옵션 이름',
|
||||
`created_at` datetime NOT NULL COMMENT '옵션 등록날짜',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '옵션 등록자',
|
||||
`created_by` varchar(320) NOT NULL COMMENT '옵션 등록자',
|
||||
`updated_at` datetime COMMENT '옵션 변경날짜',
|
||||
`updated_by` varchar(20) NULL COMMENT '옵션 변경자',
|
||||
`updated_by` varchar(320) NULL COMMENT '옵션 변경자',
|
||||
`menu_id` bigint unsigned NOT NULL COMMENT '연관된 카페 메뉴 ID',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `menu_id` (`menu_id`),
|
||||
|
||||
@@ -3,9 +3,9 @@ CREATE TABLE `option_detail` (
|
||||
`name` varchar(50) NOT NULL COMMENT '옵션 상세명',
|
||||
`extra_price` decimal(10, 2) NOT NULL COMMENT '옵션 추가 요금',
|
||||
`created_at` datetime NOT NULL COMMENT '옵션 상세 등록날짜',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '옵션 상세 등록자',
|
||||
`created_by` varchar(320) NOT NULL COMMENT '옵션 상세 등록자',
|
||||
`updated_at` datetime COMMENT '옵션 상세 변경날짜',
|
||||
`updated_by` varchar(20) NULL COMMENT '옵션 상세 변경자',
|
||||
`updated_by` varchar(320) NULL COMMENT '옵션 상세 변경자',
|
||||
`option_id` bigint unsigned NOT NULL COMMENT '연관된 옵션 ID',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `option_id` (`option_id`),
|
||||
|
||||
@@ -7,8 +7,8 @@ CREATE TABLE `member` (
|
||||
`role_type` varchar(20) COMMENT '회원 권한',
|
||||
`activated` tinyint NOT NULL COMMENT '계정 활성화 여부',
|
||||
`created_at` datetime NOT NULL COMMENT '회원 등록날짜',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '회원 등록자',
|
||||
`created_by` varchar(320) NOT NULL COMMENT '회원 등록자',
|
||||
`updated_at` datetime NULL COMMENT '회원 변경날짜',
|
||||
`updated_by` varchar(20) NULL COMMENT '회원 변경자',
|
||||
`updated_by` varchar(320) NULL COMMENT '회원 변경자',
|
||||
PRIMARY KEY (`member_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -1,3 +1,5 @@
|
||||
DROP PROCEDURE IF EXISTS insertCafeImages;
|
||||
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE insertCafeImages()
|
||||
BEGIN
|
||||
@@ -15,10 +17,10 @@ BEGIN
|
||||
|
||||
WHILE(j <= 3) DO
|
||||
INSERT IGNORE INTO `cafe_image` (img_url, created_at, created_by, updated_at, updated_by, cafe_id)
|
||||
VALUES (CONCAT('test_img_url_', idx_img), now(), 'system', now(), 'system', var_cafe_id);
|
||||
VALUES (CONCAT('https://d3qy02qh8hbgxp.cloudfront.net/cafe', idx_img, '.jpg'), now(), 'system', now(), 'system', var_cafe_id);
|
||||
|
||||
SET j = j + 1;
|
||||
SET idx_img = idx_img + 1;
|
||||
SET idx_img = idx_img % 7 + 1;
|
||||
END WHILE;
|
||||
|
||||
SET i = i + 1;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.common.config
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
|
||||
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
class AuditingConfig
|
||||
@@ -1,9 +1,9 @@
|
||||
package io.beaniejoy.dongnecafe.common.config
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.config.JwtAuthenticationConfigurer
|
||||
import io.beaniejoy.dongnecafe.security.handler.CustomAccessDeniedHandler
|
||||
import io.beaniejoy.dongnecafe.security.handler.CustomAuthenticationEntryPoint
|
||||
import io.beaniejoy.dongnecafe.utils.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.infra.security.config.JwtAuthenticationConfigurer
|
||||
import io.beaniejoy.dongnecafe.infra.security.handler.CustomAccessDeniedHandler
|
||||
import io.beaniejoy.dongnecafe.infra.security.handler.CustomAuthenticationEntryPoint
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
|
||||
import org.springframework.context.annotation.Bean
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.common.entity
|
||||
|
||||
import org.springframework.data.domain.AuditorAware
|
||||
import org.springframework.stereotype.Component
|
||||
import java.util.*
|
||||
|
||||
@Component
|
||||
class BaseEntityAuditorAware: AuditorAware<String> {
|
||||
override fun getCurrentAuditor(): Optional<String> {
|
||||
return Optional.of("system")
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.beaniejoy.dongnecafe.controller
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.response.ApplicationResponse
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.utils.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.domain.member.model.request.SignInRequest
|
||||
import io.beaniejoy.dongnecafe.model.TokenResponse
|
||||
import io.beaniejoy.dongnecafe.service.AuthService
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.beaniejoy.dongnecafe.security
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.infra.security.SecurityUser
|
||||
import mu.KLogging
|
||||
import org.springframework.security.authentication.AuthenticationProvider
|
||||
import org.springframework.security.authentication.BadCredentialsException
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import io.beaniejoy.dongnecafe.domain.member.entity.Member
|
||||
import io.beaniejoy.dongnecafe.domain.member.repository.MemberRepository
|
||||
import io.beaniejoy.dongnecafe.infra.security.SecurityUser
|
||||
import mu.KLogging
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.security.filter
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.BEARER
|
||||
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.WHITESPACE
|
||||
import mu.KotlinLogging
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.web.filter.GenericFilterBean
|
||||
import javax.servlet.FilterChain
|
||||
import javax.servlet.ServletRequest
|
||||
import javax.servlet.ServletResponse
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
class JwtAuthenticationFilter(
|
||||
private val jwtTokenUtils: JwtTokenUtils
|
||||
) : GenericFilterBean() {
|
||||
private val log = KotlinLogging.logger {}
|
||||
|
||||
/**
|
||||
* JWT access token 인증 처리
|
||||
*/
|
||||
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
||||
val httpRequest = request as HttpServletRequest
|
||||
log.info { "[JwtAuthenticationFilter][${request.dispatcherType}] uri: ${request.requestURI}" }
|
||||
|
||||
getAccessToken(httpRequest)?.let {
|
||||
jwtTokenUtils.getAuthentication(it)
|
||||
}?.also {
|
||||
SecurityContextHolder.getContext().authentication = it
|
||||
log.info { "Valid Access Token [${it.name}]" }
|
||||
}
|
||||
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
|
||||
private fun getAccessToken(request: HttpServletRequest): String? {
|
||||
val bearer = request.getHeader(HttpHeaders.AUTHORIZATION)
|
||||
?: return null
|
||||
|
||||
val splitBearer = bearer.split(WHITESPACE)
|
||||
if (splitBearer.first() != BEARER) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (splitBearer.size != 2 || splitBearer.last().isBlank()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return splitBearer.last()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
server:
|
||||
port: 9090
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: local
|
||||
@@ -5,22 +8,17 @@ spring:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none # use [service-api] flyway migration
|
||||
ddl-auto: none
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
dialect: org.hibernate.dialect.MySQL8Dialect
|
||||
format_sql: true
|
||||
show-sql: false
|
||||
open-in-view:
|
||||
flyway:
|
||||
enabled: false
|
||||
devtools:
|
||||
livereload:
|
||||
enabled: false # no use devtools' LiveReload Server
|
||||
|
||||
server:
|
||||
port: 9090
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.hibernate.SQL: debug # logger 통해 로깅
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.beaniejoy.dongnecafe.common.config
|
||||
|
||||
import io.beaniejoy.dongnecafe.utils.security.getAuthPrincipal
|
||||
import mu.KLogging
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.data.domain.AuditorAware
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import java.util.*
|
||||
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
class AuditingConfig {
|
||||
companion object: KLogging() {
|
||||
const val SYSTEM = "system"
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun auditorProvider(): AuditorAware<String> {
|
||||
return AuditorAware<String> {
|
||||
Optional.of(SecurityContextHolder.getContext().authentication?.getAuthPrincipal() ?: SYSTEM)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.beaniejoy.dongnecafe.common.entity
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy
|
||||
import org.springframework.data.annotation.LastModifiedBy
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.EntityListeners
|
||||
import javax.persistence.MappedSuperclass
|
||||
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener::class)
|
||||
abstract class BaseEntity protected constructor() : BaseTimeEntity() {
|
||||
@CreatedBy
|
||||
@Column(updatable = false)
|
||||
lateinit var createdBy: String
|
||||
protected set
|
||||
|
||||
@LastModifiedBy
|
||||
lateinit var updatedBy: String
|
||||
protected set
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
package io.beaniejoy.dongnecafe.common
|
||||
package io.beaniejoy.dongnecafe.common.entity
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.LastModifiedBy
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.EntityListeners
|
||||
import javax.persistence.MappedSuperclass
|
||||
|
||||
@@ -13,18 +12,11 @@ import javax.persistence.MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener::class)
|
||||
abstract class BaseTimeEntity protected constructor() {
|
||||
@CreatedDate
|
||||
var createdAt: LocalDateTime = LocalDateTime.now()
|
||||
protected set
|
||||
|
||||
@CreatedBy
|
||||
var createdBy: String = ""
|
||||
@Column(updatable = false)
|
||||
lateinit var createdAt: LocalDateTime
|
||||
protected set
|
||||
|
||||
@LastModifiedDate
|
||||
var updatedAt: LocalDateTime? = null
|
||||
protected set
|
||||
|
||||
@LastModifiedBy
|
||||
var updatedBy: String? = null
|
||||
lateinit var updatedAt: LocalDateTime
|
||||
protected set
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.entity
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
|
||||
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.CafeMenuRegisterRequest
|
||||
import javax.persistence.*
|
||||
|
||||
@@ -11,7 +11,7 @@ class Cafe protected constructor(
|
||||
address: String,
|
||||
phoneNumber: String,
|
||||
description: String,
|
||||
) : BaseTimeEntity() {
|
||||
) : BaseEntity() {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "cafe_id", nullable = false)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.entity
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
|
||||
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
|
||||
import javax.persistence.*
|
||||
|
||||
@Entity
|
||||
@Table(name = "cafe_image")
|
||||
class CafeImage(
|
||||
class CafeImage protected constructor(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "cafe_image_id", nullable = false)
|
||||
@@ -17,4 +17,4 @@ class CafeImage(
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "cafe_id", nullable = false)
|
||||
val cafe: Cafe
|
||||
) : BaseTimeEntity()
|
||||
) : BaseEntity()
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.entity
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
|
||||
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.MenuOptionRegisterRequest
|
||||
import java.math.BigDecimal
|
||||
import javax.persistence.*
|
||||
@@ -10,7 +10,7 @@ import javax.persistence.*
|
||||
class CafeMenu protected constructor(
|
||||
name: String,
|
||||
price: BigDecimal,
|
||||
) : BaseTimeEntity() {
|
||||
) : BaseEntity() {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "cafe_menu_id", nullable = false)
|
||||
@@ -33,7 +33,11 @@ class CafeMenu protected constructor(
|
||||
val menuOptionList: MutableList<MenuOption> = arrayListOf()
|
||||
|
||||
companion object {
|
||||
fun createCafeMenu(name: String, price: BigDecimal, menuOptionRequestList: List<MenuOptionRegisterRequest>): CafeMenu {
|
||||
fun createCafeMenu(
|
||||
name: String,
|
||||
price: BigDecimal,
|
||||
menuOptionRequestList: List<MenuOptionRegisterRequest>
|
||||
): CafeMenu {
|
||||
val menuOptionEntityList = menuOptionRequestList.map { menuOptionRequestDto ->
|
||||
MenuOption.createMenuOption(
|
||||
title = menuOptionRequestDto.title,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.entity
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
|
||||
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.OptionDetailRegisterRequest
|
||||
import javax.persistence.*
|
||||
|
||||
@@ -8,7 +8,7 @@ import javax.persistence.*
|
||||
@Table(name = "menu_option")
|
||||
class MenuOption protected constructor(
|
||||
title: String
|
||||
) : BaseTimeEntity() {
|
||||
) : BaseEntity() {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "menu_option_id", nullable = false)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.entity
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
|
||||
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
|
||||
import java.math.BigDecimal
|
||||
import javax.persistence.*
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.persistence.*
|
||||
class OptionDetail protected constructor(
|
||||
name: String,
|
||||
extraPrice: BigDecimal
|
||||
) : BaseTimeEntity() {
|
||||
) : BaseEntity() {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "option_detail_id", nullable = false)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.repository
|
||||
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface CafeRepository : JpaRepository<Cafe, Long> {
|
||||
fun findByName(name: String): Cafe?
|
||||
|
||||
fun findByNameContainingIgnoreCase(name: String?, pageable: Pageable): Page<Cafe>
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.domain.member.entity
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
|
||||
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
|
||||
import io.beaniejoy.dongnecafe.domain.member.constant.RoleType
|
||||
import javax.persistence.*
|
||||
|
||||
@@ -11,7 +11,7 @@ class Member(
|
||||
password: String,
|
||||
address: String,
|
||||
phoneNumber: String
|
||||
): BaseTimeEntity() {
|
||||
) : BaseEntity() {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "member_id", nullable = false)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.beaniejoy.dongnecafe.infra.logging
|
||||
|
||||
import io.beaniejoy.dongnecafe.utils.logging.*
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper
|
||||
|
||||
data class HttpLogMessage(
|
||||
val httpMethod: String,
|
||||
val requestUri: String,
|
||||
val httpStatus: HttpStatus,
|
||||
val clientIp: String,
|
||||
val elapsedTime: Double,
|
||||
val headers: String?,
|
||||
val requestParam: String?,
|
||||
val requestBody: String?,
|
||||
val responseBody: String?,
|
||||
) {
|
||||
companion object {
|
||||
fun createInstance(
|
||||
requestWrapper: ContentCachingRequestWrapper,
|
||||
responseWrapper: ContentCachingResponseWrapper,
|
||||
elapsedTime: Double
|
||||
): HttpLogMessage {
|
||||
return HttpLogMessage(
|
||||
httpMethod = requestWrapper.method,
|
||||
requestUri = requestWrapper.requestURI,
|
||||
httpStatus = HttpStatus.valueOf(responseWrapper.status),
|
||||
clientIp = requestWrapper.getClientIp(),
|
||||
elapsedTime = elapsedTime,
|
||||
headers = requestWrapper.getRequestHeaders(),
|
||||
requestParam = requestWrapper.getRequestParams(),
|
||||
requestBody = requestWrapper.getRequestBody(),
|
||||
responseBody = responseWrapper.getResponseBody(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toPrettierLog(): String {
|
||||
return """
|
||||
|
|
||||
|[REQUEST] ${this.httpMethod} ${this.requestUri} ${this.httpStatus} (${this.elapsedTime})
|
||||
|>> CLIENT_IP: ${this.clientIp}
|
||||
|>> HEADERS: ${this.headers}
|
||||
|>> REQUEST_PARAM: ${this.requestParam}
|
||||
|>> REQUEST_BODY: ${this.requestBody}
|
||||
|>> RESPONSE_BODY: ${this.responseBody}
|
||||
""".trimMargin()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.beaniejoy.dongnecafe.infra.logging
|
||||
|
||||
import mu.KotlinLogging
|
||||
import org.slf4j.MDC
|
||||
import org.springframework.core.Ordered
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.filter.OncePerRequestFilter
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper
|
||||
import java.util.*
|
||||
import javax.servlet.FilterChain
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
class ReqResLoggingFilter : OncePerRequestFilter() {
|
||||
private val log = KotlinLogging.logger {}
|
||||
|
||||
companion object {
|
||||
const val REQUEST_ID = "request_id"
|
||||
}
|
||||
|
||||
override fun doFilterInternal(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
filterChain: FilterChain,
|
||||
) {
|
||||
val cachingRequestWrapper = ContentCachingRequestWrapper(request)
|
||||
val cachingResponseWrapper = ContentCachingResponseWrapper(response)
|
||||
|
||||
val requestId = UUID.randomUUID().toString().substring(0, 8)
|
||||
|
||||
MDC.put(REQUEST_ID, requestId)
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
filterChain.doFilter(cachingRequestWrapper, cachingResponseWrapper)
|
||||
val end = System.currentTimeMillis()
|
||||
|
||||
try {
|
||||
log.info {
|
||||
HttpLogMessage.createInstance(
|
||||
requestWrapper = cachingRequestWrapper,
|
||||
responseWrapper = cachingResponseWrapper,
|
||||
elapsedTime = (end - startTime) / 1000.0
|
||||
).toPrettierLog()
|
||||
}
|
||||
|
||||
cachingResponseWrapper.copyBodyToResponse()
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "[${this::class.simpleName}] Logging 실패" }
|
||||
}
|
||||
|
||||
MDC.remove(REQUEST_ID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.beaniejoy.dongnecafe.infra.logging.constant
|
||||
|
||||
/**
|
||||
* http request client ip possible enum list
|
||||
* (ref. https://blog.yevgnenll.me/posts/find-client-ip-from-http-request-header)
|
||||
* @property headerName String client ip header name
|
||||
*/
|
||||
enum class HttpClientIp(
|
||||
val headerName: String,
|
||||
) {
|
||||
X_FORWARDED_FOR("X-Forwarded-For"),
|
||||
PROXY_CLIENT_IP("Proxy-Client-IP"),
|
||||
WL_PROXY_CLIENT_IP("WL-Proxy-Client-IP"),
|
||||
HTTP_X_FORWARDED("HTTP_X_FORWARDED"),
|
||||
HTTP_X_FORWARDED_FOR("HTTP_X_FORWARDED_FOR"),
|
||||
HTTP_CLIENT_IP("HTTP_CLIENT_IP")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.beaniejoy.dongnecafe.security
|
||||
package io.beaniejoy.dongnecafe.infra.security
|
||||
|
||||
import io.beaniejoy.dongnecafe.domain.member.entity.Member
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.beaniejoy.dongnecafe.security.config
|
||||
package io.beaniejoy.dongnecafe.infra.security.config
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.filter.JwtAuthenticationFilter
|
||||
import io.beaniejoy.dongnecafe.infra.security.filter.JwtAuthenticationFilter
|
||||
import io.beaniejoy.dongnecafe.utils.security.JwtTokenUtils
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.beaniejoy.dongnecafe.infra.security.constant
|
||||
|
||||
object SecurityConstant {
|
||||
const val BEARER = "Bearer"
|
||||
const val WHITESPACE = " "
|
||||
|
||||
const val ANONYMOUS_USER = "anonymousUser"
|
||||
const val ROLE_ANONYMOUS = "ROLE_ANONYMOUS"
|
||||
|
||||
const val JWT_AUTHORITIES_KEY = "authorities"
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.beaniejoy.dongnecafe.security.filter
|
||||
package io.beaniejoy.dongnecafe.infra.security.filter
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.BEARER
|
||||
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.WHITESPACE
|
||||
import io.beaniejoy.dongnecafe.utils.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.infra.security.constant.SecurityConstant.BEARER
|
||||
import io.beaniejoy.dongnecafe.infra.security.constant.SecurityConstant.WHITESPACE
|
||||
import mu.KotlinLogging
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
@@ -24,9 +24,11 @@ class JwtAuthenticationFilter(
|
||||
val httpRequest = request as HttpServletRequest
|
||||
log.info { "[JwtAuthenticationFilter][${request.dispatcherType}] uri: ${request.requestURI}" }
|
||||
|
||||
// 인증 헤더에 토큰값 없는 경우 pass
|
||||
getAccessToken(httpRequest)?.let {
|
||||
jwtTokenUtils.getAuthentication(it)
|
||||
}?.also {
|
||||
// 유효한 인증 토큰 존재하는 경우 SecurityContext 토큰값 저장
|
||||
SecurityContextHolder.getContext().authentication = it
|
||||
log.info { "Valid Access Token [${it.name}]" }
|
||||
}
|
||||
@@ -34,6 +36,10 @@ class JwtAuthenticationFilter(
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증 토큰 획득
|
||||
* Authorization : Bearer [AUTH_TOKEN]
|
||||
*/
|
||||
private fun getAccessToken(request: HttpServletRequest): String? {
|
||||
val bearer = request.getHeader(HttpHeaders.AUTHORIZATION)
|
||||
?: return null
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.beaniejoy.dongnecafe.security.handler
|
||||
package io.beaniejoy.dongnecafe.infra.security.handler
|
||||
|
||||
import mu.KLogging
|
||||
import org.springframework.security.access.AccessDeniedException
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.beaniejoy.dongnecafe.security.handler
|
||||
package io.beaniejoy.dongnecafe.infra.security.handler
|
||||
|
||||
import mu.KLogging
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
@@ -1,6 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.security.constant
|
||||
|
||||
object SecurityConstant {
|
||||
const val BEARER = "Bearer"
|
||||
const val WHITESPACE = " "
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.beaniejoy.dongnecafe.utils.logging
|
||||
|
||||
import com.google.gson.Gson
|
||||
import io.beaniejoy.dongnecafe.infra.logging.constant.HttpClientIp
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
fun HttpServletRequest.getRequestHeaders(): String? {
|
||||
val request = this
|
||||
return Gson().toJson(
|
||||
mutableMapOf<String, String?>().apply {
|
||||
request.headerNames.toList().forEach {
|
||||
this[it] = request.getHeader(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun HttpServletRequest.getRequestParams(): String {
|
||||
return this.parameterMap.mapValues {
|
||||
it.value.joinToString(",")
|
||||
}.entries.joinToString("&")
|
||||
}
|
||||
|
||||
fun HttpServletRequest.getClientIp(): String {
|
||||
HttpClientIp.values().forEach { clientIpHeader ->
|
||||
this.getHeader(clientIpHeader.headerName).also {
|
||||
if (it.isNullOrBlank().not() && "unknown".equals(it, true).not()) {
|
||||
return it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.remoteAddr
|
||||
}
|
||||
|
||||
fun ContentCachingRequestWrapper.getRequestBody(): String {
|
||||
return this.contentAsByteArray.toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
// TODO: logging response body maximum size 고려
|
||||
fun ContentCachingResponseWrapper.getResponseBody(): String {
|
||||
return this.contentAsByteArray.toString(Charsets.UTF_8)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.beaniejoy.dongnecafe.security
|
||||
package io.beaniejoy.dongnecafe.utils.security
|
||||
|
||||
import io.beaniejoy.dongnecafe.infra.security.SecurityUser
|
||||
import io.beaniejoy.dongnecafe.infra.security.constant.SecurityConstant.JWT_AUTHORITIES_KEY
|
||||
import io.jsonwebtoken.Claims
|
||||
import io.jsonwebtoken.ExpiredJwtException
|
||||
import io.jsonwebtoken.Jwts
|
||||
@@ -24,9 +26,7 @@ class JwtTokenUtils(
|
||||
private val key: Key = Keys.hmacShaKeyFor(secretKey.toByteArray())
|
||||
private val validityTimeMilliSec: Long = validityTimeSec * 1000
|
||||
|
||||
companion object : KLogging() {
|
||||
const val AUTHORITIES_KEY = "authorities"
|
||||
}
|
||||
companion object : KLogging()
|
||||
|
||||
fun createToken(authentication: Authentication): String {
|
||||
val authenticatedMember = (authentication.principal as SecurityUser).member
|
||||
@@ -37,7 +37,7 @@ class JwtTokenUtils(
|
||||
|
||||
return Jwts.builder()
|
||||
.setSubject(authenticatedMember.email)
|
||||
.claim(AUTHORITIES_KEY, authorities)
|
||||
.claim(JWT_AUTHORITIES_KEY, authorities)
|
||||
.signWith(key, SignatureAlgorithm.HS256)
|
||||
.setExpiration(expirationDate)
|
||||
.compact()
|
||||
@@ -47,7 +47,7 @@ class JwtTokenUtils(
|
||||
val claims = getValidTokenBody(accessToken)
|
||||
?: return null
|
||||
|
||||
val authorities = claims[AUTHORITIES_KEY].toString().split(",")
|
||||
val authorities = claims[JWT_AUTHORITIES_KEY].toString().split(",")
|
||||
.map { SimpleGrantedAuthority(it) }
|
||||
|
||||
return UsernamePasswordAuthenticationToken(claims.subject, accessToken, authorities)
|
||||
@@ -62,10 +62,10 @@ class JwtTokenUtils(
|
||||
.parseClaimsJws(accessToken)
|
||||
.body
|
||||
} catch (e: ExpiredJwtException) {
|
||||
logger.error { "JWT access token expired. > Error: ${e.message}" }
|
||||
logger.info { "JWT access token expired. > Error: ${e.message}" }
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
logger.error { "JWT access token invalid. > Error: ${e.message}" }
|
||||
logger.info { "JWT access token invalid. > Error: ${e.message}" }
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.beaniejoy.dongnecafe.utils.security
|
||||
|
||||
import io.beaniejoy.dongnecafe.infra.security.constant.SecurityConstant.ANONYMOUS_USER
|
||||
import io.beaniejoy.dongnecafe.infra.security.constant.SecurityConstant.ROLE_ANONYMOUS
|
||||
import org.springframework.security.core.Authentication
|
||||
|
||||
fun Authentication.getAuthPrincipal() : String? {
|
||||
if (this.isAnonymous()) return null
|
||||
|
||||
return this.principal.toString()
|
||||
}
|
||||
|
||||
fun Authentication.isAnonymous(): Boolean {
|
||||
return this.principal == ANONYMOUS_USER || this.authorities.any { it.authority == ROLE_ANONYMOUS }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.common.config
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
|
||||
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
class AuditingConfig
|
||||
@@ -1,7 +1,9 @@
|
||||
package io.beaniejoy.dongnecafe.common.config
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.JwtAuthenticationConfigurer
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.infra.security.config.JwtAuthenticationConfigurer
|
||||
import io.beaniejoy.dongnecafe.utils.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.infra.security.handler.CustomAccessDeniedHandler
|
||||
import io.beaniejoy.dongnecafe.infra.security.handler.CustomAuthenticationEntryPoint
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
|
||||
import org.springframework.context.annotation.Bean
|
||||
@@ -18,15 +20,22 @@ class SecurityConfig {
|
||||
@Autowired
|
||||
lateinit var jwtTokenUtils: JwtTokenUtils
|
||||
|
||||
@Autowired
|
||||
lateinit var customAccessDeniedHandler: CustomAccessDeniedHandler
|
||||
|
||||
@Autowired
|
||||
lateinit var customAuthenticationEntryPoint: CustomAuthenticationEntryPoint
|
||||
|
||||
@Bean
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
return http
|
||||
// only api 방식 인증 & 인가 적용 위해 csrf & formLogin 비활성화
|
||||
.csrf().disable()
|
||||
.formLogin().disable()
|
||||
|
||||
// FIXME 임시 permitAll 설정
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
// .anyRequest().authenticated()
|
||||
.anyRequest().permitAll()
|
||||
|
||||
.and()
|
||||
.sessionManagement()
|
||||
@@ -34,6 +43,11 @@ class SecurityConfig {
|
||||
|
||||
.and()
|
||||
.also { jwtAuthenticationConfigurer(it) }
|
||||
.exceptionHandling()
|
||||
.authenticationEntryPoint(customAuthenticationEntryPoint) // 인증 예외 entryPoint 적용
|
||||
.accessDeniedHandler(customAccessDeniedHandler) // 인가 예외 handler 적용
|
||||
|
||||
.and()
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -43,6 +57,7 @@ class SecurityConfig {
|
||||
.jwtTokenUtils(jwtTokenUtils)
|
||||
}
|
||||
|
||||
// Security Filter 미적용 자원 설정
|
||||
@Bean
|
||||
fun webSecurityCustomizer(): WebSecurityCustomizer {
|
||||
return WebSecurityCustomizer { web ->
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.common.entity
|
||||
|
||||
import org.springframework.data.domain.AuditorAware
|
||||
import org.springframework.stereotype.Component
|
||||
import java.util.*
|
||||
|
||||
@Component
|
||||
class BaseEntityAuditorAware: AuditorAware<String> {
|
||||
override fun getCurrentAuditor(): Optional<String> {
|
||||
// TODO 추후 사용자 로그인 기능 추가되면 실제 등록한 사용자를 DB에 저장하는 방향으로 수정
|
||||
return Optional.of("system")
|
||||
}
|
||||
}
|
||||
@@ -40,9 +40,10 @@ class CafeController(
|
||||
*/
|
||||
@GetMapping
|
||||
fun searchCafeList(
|
||||
@RequestParam("name") name: String?,
|
||||
@PageableDefault(sort = ["name"], direction = Sort.Direction.ASC, page = 0, size = 10) pageable: Pageable
|
||||
): ApplicationResponse<Page<CafeSearchInfo>> {
|
||||
val searchCafes = cafeService.searchCafeList(pageable)
|
||||
val searchCafes = cafeService.searchCafeList(name, pageable)
|
||||
|
||||
return ApplicationResponse
|
||||
.success()
|
||||
|
||||
@@ -59,8 +59,8 @@ class CafeService(
|
||||
}
|
||||
}
|
||||
|
||||
fun searchCafeList(pageable: Pageable): Page<CafeSearchInfo> {
|
||||
val cafeList: Page<Cafe> = cafeRepository.findAll(pageable)
|
||||
fun searchCafeList(name: String?, pageable: Pageable): Page<CafeSearchInfo> {
|
||||
val cafeList: Page<Cafe> = cafeRepository.findByNameContainingIgnoreCase(name, pageable)
|
||||
|
||||
return cafeList.map { CafeSearchInfo.of(it) }
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.security
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.filter.JwtAuthenticationFilter
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||
|
||||
class JwtAuthenticationConfigurer :
|
||||
SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>() {
|
||||
private lateinit var jwtTokenUtils: JwtTokenUtils
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
http
|
||||
.addFilterBefore(
|
||||
JwtAuthenticationFilter(this.jwtTokenUtils),
|
||||
UsernamePasswordAuthenticationFilter::class.java
|
||||
)
|
||||
}
|
||||
|
||||
fun jwtTokenUtils(jwtTokenUtils: JwtTokenUtils): JwtAuthenticationConfigurer {
|
||||
this.jwtTokenUtils = jwtTokenUtils
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,18 @@ spring:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none # flyway migration 사용
|
||||
ddl-auto: none
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
dialect: org.hibernate.dialect.MySQL8Dialect
|
||||
format_sql: true
|
||||
show-sql: false
|
||||
devtools:
|
||||
livereload:
|
||||
enabled: false # no use devtools' LiveReload Server
|
||||
security:
|
||||
filter:
|
||||
order: 10
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
||||
29
dongne-service-api/src/main/resources/logback-spring.xml
Normal file
29
dongne-service-api/src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
|
||||
<!-- Pattern -->
|
||||
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%5level) [%15.15t] [%X{request_id}] %clr(%-40.40logger{39}){cyan} : %m%n%wEx"/>
|
||||
<!-- Request Thread Console Appender -->
|
||||
<appender name="THREAD_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<springProfile name="local">
|
||||
<logger additivity="false" level="INFO" name="io.beaniejoy.dongnecafe">
|
||||
<appender-ref ref="THREAD_CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<!-- Bootstrap class file -->
|
||||
<logger additivity="false" level="INFO" name="io.beaniejoy.dongnecafe.DongneServiceApiApplicationKt">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
</configuration>
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.repository
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.config.AuditingConfig
|
||||
import io.beaniejoy.dongnecafe.common.entity.BaseEntityAuditorAware
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.utils.CafeTestUtils
|
||||
import mu.KLogging
|
||||
@@ -17,7 +16,6 @@ import org.springframework.data.repository.findByIdOrNull
|
||||
@DataJpaTest(
|
||||
includeFilters = [
|
||||
ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [AuditingConfig::class]),
|
||||
ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [BaseEntityAuditorAware::class])
|
||||
]
|
||||
)
|
||||
internal class CafeRepositoryTest {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd ..
|
||||
|
||||
./gradlew :db:flywayInfo
|
||||
|
||||
./gradlew :db:flywayValidate
|
||||
60
script/migration-local.sh
Executable file
60
script/migration-local.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
|
||||
PROJECT_NAME="dongne-cafe-api"
|
||||
PROJECT_ROOT_DIR=$(pwd)
|
||||
FLYWAY_CONFIG_FILE="flyway-local.conf"
|
||||
|
||||
check_project_root_path() {
|
||||
if [[ $PROJECT_ROOT_DIR != *"/$PROJECT_NAME" ]];
|
||||
then
|
||||
echo "Error >> move to project's root directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "Project's Root Directory: $PROJECT_ROOT_DIR\n"
|
||||
}
|
||||
|
||||
flyway_version_check() {
|
||||
echo "###################################"
|
||||
echo "Using Flyway Version"
|
||||
|
||||
if ! flyway --version 2> /dev/null;
|
||||
then
|
||||
echo "Error >> Flyway Not Supported"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "###################################\n"
|
||||
}
|
||||
|
||||
error_check() {
|
||||
if [ $? -ne 0 ];
|
||||
then
|
||||
echo "Error >> $1 & Exit"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "\n"
|
||||
}
|
||||
|
||||
flyway_migration_process() {
|
||||
STEP_1="1. Flyway Info"
|
||||
STEP_2="2. Flyway Migrate"
|
||||
STEP_3="3. Flyway Validate"
|
||||
|
||||
echo $STEP_1
|
||||
flyway info -configFiles="$PROJECT_ROOT_DIR/db/$FLYWAY_CONFIG_FILE"
|
||||
error_check "$STEP_1"
|
||||
|
||||
echo $STEP_2
|
||||
flyway migrate -configFiles="$PROJECT_ROOT_DIR/db/$FLYWAY_CONFIG_FILE" -outputType=json
|
||||
error_check "$STEP_2"
|
||||
|
||||
echo $STEP_3
|
||||
flyway validate -configFiles="$PROJECT_ROOT_DIR/db/$FLYWAY_CONFIG_FILE" -outputType=json
|
||||
error_check "$STEP_3"
|
||||
}
|
||||
|
||||
echo "########### [LOCAL] DB Migration ###########"
|
||||
check_project_root_path
|
||||
flyway_version_check
|
||||
flyway_migration_process
|
||||
@@ -2,4 +2,3 @@ rootProject.name = "dongne-cafe-api"
|
||||
include("dongne-common")
|
||||
include("dongne-service-api")
|
||||
include("dongne-account-api")
|
||||
include("db")
|
||||
|
||||
Reference in New Issue
Block a user