Merge pull request #42 from beaniejoy/feature/41
Cafe 리스트 검색 api 수정 및 전체적인 리팩토링
This commit is contained in:
@@ -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,6 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.common.config
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.utils.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.config.JwtAuthenticationConfigurer
|
||||
import io.beaniejoy.dongnecafe.security.handler.CustomAccessDeniedHandler
|
||||
import io.beaniejoy.dongnecafe.security.handler.CustomAuthenticationEntryPoint
|
||||
|
||||
@@ -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.security.utils.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.domain.member.model.request.SignInRequest
|
||||
import io.beaniejoy.dongnecafe.model.TokenResponse
|
||||
import io.beaniejoy.dongnecafe.service.AuthService
|
||||
|
||||
@@ -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 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 통해 로깅
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.common.config
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.getAuthPrincipal
|
||||
import io.beaniejoy.dongnecafe.security.utils.getAuthPrincipal
|
||||
import mu.KLogging
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
package io.beaniejoy.dongnecafe.security.config
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.filter.JwtAuthenticationFilter
|
||||
import io.beaniejoy.dongnecafe.security.utils.JwtTokenUtils
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
@@ -6,4 +6,6 @@ object SecurityConstant {
|
||||
|
||||
const val ANONYMOUS_USER = "anonymousUser"
|
||||
const val ROLE_ANONYMOUS = "ROLE_ANONYMOUS"
|
||||
|
||||
const val JWT_AUTHORITIES_KEY = "authorities"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.security.filter
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.utils.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.BEARER
|
||||
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.WHITESPACE
|
||||
import mu.KotlinLogging
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.beaniejoy.dongnecafe.security
|
||||
package io.beaniejoy.dongnecafe.security.utils
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.SecurityUser
|
||||
import io.beaniejoy.dongnecafe.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
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.beaniejoy.dongnecafe.security
|
||||
package io.beaniejoy.dongnecafe.security.utils
|
||||
|
||||
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.ANONYMOUS_USER
|
||||
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.ROLE_ANONYMOUS
|
||||
@@ -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.security.config.JwtAuthenticationConfigurer
|
||||
import io.beaniejoy.dongnecafe.security.utils.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.security.handler.CustomAccessDeniedHandler
|
||||
import io.beaniejoy.dongnecafe.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,6 +20,12 @@ 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
|
||||
@@ -26,7 +34,8 @@ class SecurityConfig {
|
||||
.formLogin().disable()
|
||||
|
||||
.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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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,10 +5,10 @@ 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:
|
||||
|
||||
@@ -1,39 +1,60 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo -e "########### [LOCAL] DB Migration ###########"
|
||||
printf "\n"
|
||||
|
||||
PROJECT_NAME="dongne-cafe-api"
|
||||
PROJECT_ROOT_DIR=$(pwd)
|
||||
FLYWAY_CONFIG_FILE="flyway-local.conf"
|
||||
|
||||
if [[ $PROJECT_ROOT_DIR != *"/$PROJECT_NAME" ]];
|
||||
then
|
||||
echo "Error >> move to project's root directory"
|
||||
exit 1
|
||||
fi
|
||||
check_project_root_path() {
|
||||
if [[ $PROJECT_ROOT_DIR != *"/$PROJECT_NAME" ]];
|
||||
then
|
||||
echo "Error >> move to project's root directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Project's Root Directory: $PROJECT_ROOT_DIR"
|
||||
printf "\n"
|
||||
echo -e "Project's Root Directory: $PROJECT_ROOT_DIR\n"
|
||||
}
|
||||
|
||||
echo "###################################"
|
||||
echo "Using Flyway Version"
|
||||
flyway_version_check() {
|
||||
echo "###################################"
|
||||
echo "Using Flyway Version"
|
||||
|
||||
if ! flyway --version 2> /dev/null;
|
||||
then
|
||||
echo "Error >> Flyway Not Supported"
|
||||
exit 1
|
||||
fi
|
||||
echo "###################################"
|
||||
printf "\n"
|
||||
if ! flyway --version 2> /dev/null;
|
||||
then
|
||||
echo "Error >> Flyway Not Supported"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "###################################\n"
|
||||
}
|
||||
|
||||
echo "1. Flyway Info"
|
||||
flyway info -configFiles="$PROJECT_ROOT_DIR/db/$FLYWAY_CONFIG_FILE"
|
||||
printf "\n"
|
||||
error_check() {
|
||||
if [ $? -ne 0 ];
|
||||
then
|
||||
echo "Error >> $1 & Exit"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "2. Flyway Migrate"
|
||||
flyway migrate -configFiles="$PROJECT_ROOT_DIR/db/$FLYWAY_CONFIG_FILE"
|
||||
printf "\n"
|
||||
printf "\n"
|
||||
}
|
||||
|
||||
echo "3. Flyway Validate"
|
||||
flyway validate -configFiles="$PROJECT_ROOT_DIR/db/$FLYWAY_CONFIG_FILE"
|
||||
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
|
||||
Reference in New Issue
Block a user