From c140f8fffb6c61cfe0539ebd2f88e27ca46c10e3 Mon Sep 17 00:00:00 2001 From: geonhos Date: Fri, 24 May 2024 15:58:15 +0900 Subject: [PATCH] =?UTF-8?q?admin:=20server=20exception=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20=EC=8B=9C=20stateless=20=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20csrf=20=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../poc/admin/common/SubStringHelper.java | 17 ++++++++ .../poc/admin/domain/admin/entity/Admin.java | 2 +- .../domain/admin/entity/AdminActionLog.java | 5 ++- .../admin/entity/AdminTokenRepository.java | 2 +- .../admin/service/AdminActionLogService.java | 9 +++- .../admin/service/AdminTokenService.java | 2 +- .../poc/admin/filter/LoggingFilter.java | 2 +- .../poc/admin/security/SecurityConfig.java | 8 ++-- .../security/jwt/JwtTokenValidateFilter.java | 2 +- .../web/common/CommonRestController.java | 17 ++++++++ .../web/common/reqres/CsrfTokenResponse.java | 18 ++++++++ .../main/resources/static/js/pagehelper.js | 40 ++++++++++-------- .../src/main/resources/static/js/reqhelper.js | 16 ++++++++ .../templates/main/admin/management/list.html | 41 +++++++++---------- 14 files changed, 129 insertions(+), 52 deletions(-) create mode 100644 poc/admin/src/main/java/com/bpgroup/poc/admin/common/SubStringHelper.java create mode 100644 poc/admin/src/main/java/com/bpgroup/poc/admin/web/common/CommonRestController.java create mode 100644 poc/admin/src/main/java/com/bpgroup/poc/admin/web/common/reqres/CsrfTokenResponse.java diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/common/SubStringHelper.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/common/SubStringHelper.java new file mode 100644 index 0000000..ef5a3fd --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/common/SubStringHelper.java @@ -0,0 +1,17 @@ +package com.bpgroup.poc.admin.common; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +public class SubStringHelper { + + public static String substringInBytes(String original, int byteLength) { + byte[] bytes = original.getBytes(StandardCharsets.UTF_8); + if (bytes.length > byteLength) { + bytes = Arrays.copyOf(bytes, byteLength); + return new String(bytes, StandardCharsets.UTF_8); + } + return original; + } + +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/Admin.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/Admin.java index 2a364af..8a7eaac 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/Admin.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/Admin.java @@ -16,7 +16,7 @@ public class Admin extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "login_id", length = 100, nullable = false) + @Column(name = "login_id", length = 100, nullable = false, unique = true) private String loginId; @Column(name = "password", length = 255, nullable = false) diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminActionLog.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminActionLog.java index 92139b1..2d3c286 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminActionLog.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminActionLog.java @@ -1,5 +1,6 @@ package com.bpgroup.poc.admin.domain.admin.entity; +import com.bpgroup.poc.admin.common.SubStringHelper; import com.bpgroup.poc.admin.domain.BaseEntity; import jakarta.persistence.*; import lombok.AccessLevel; @@ -23,7 +24,7 @@ public class AdminActionLog extends BaseEntity { @Column(name = "request_value") private String requestValue; - @Column(name = "response_value") + @Column(name = "response_value", length = 1000) private String responseValue; @ManyToOne(fetch = FetchType.LAZY) @@ -39,7 +40,7 @@ public class AdminActionLog extends BaseEntity { private AdminActionLog(String sessionId, String responseValue) { this.sessionId = sessionId; - this.responseValue = responseValue; + this.responseValue = SubStringHelper.substringInBytes(responseValue, 1000); } public static AdminActionLog createOf(String sessionId, String requestUri, String requestValue, Admin admin) { diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminTokenRepository.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminTokenRepository.java index bad968a..f012e2a 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminTokenRepository.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminTokenRepository.java @@ -5,5 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface AdminTokenRepository extends JpaRepository { - Optional findLastByAdminIdAndIpAndState(Long adminId, String ip, TokenState state); + Optional findFirstByAdminIdAndIpAndStateOrderByIdDesc(Long adminId, String ip, TokenState state); } diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminActionLogService.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminActionLogService.java index 4d2fae7..88c5606 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminActionLogService.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminActionLogService.java @@ -9,6 +9,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.util.Optional; + @Service @RequiredArgsConstructor @Validated @@ -26,8 +28,11 @@ public class AdminActionLogService { public void update( @NotNull @Valid AdminActionLogUpdateCommand command ) { - AdminActionLog findAdminActionLog = adminActionLogRepository.findBySessionId(command.getSessionId()).orElseThrow(() -> new IllegalArgumentException("Not found admin action log")); - findAdminActionLog.update(command.toEntity()); + Optional findAdminActionLog = adminActionLogRepository.findBySessionId(command.getSessionId()); + if (findAdminActionLog.isPresent()) { + AdminActionLog adminActionLog = findAdminActionLog.get(); + adminActionLog.update(command.toEntity()); + } } } diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenService.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenService.java index 9a779aa..a5e4ee2 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenService.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenService.java @@ -25,7 +25,7 @@ public class AdminTokenService { } public void expire(@NotNull @Valid AdminTokenExpireCommand command) { - AdminToken findAdminToken = adminTokenRepository.findLastByAdminIdAndIpAndState(command.getAdminId(), command.getIp(), TokenState.NORMAL) + AdminToken findAdminToken = adminTokenRepository.findFirstByAdminIdAndIpAndStateOrderByIdDesc(command.getAdminId(), command.getIp(), TokenState.NORMAL) .orElseThrow(() -> new IllegalArgumentException("Not found token")); if (findAdminToken.isNotExpired(LocalDateTime.now())) { diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/filter/LoggingFilter.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/filter/LoggingFilter.java index c537438..78d2757 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/filter/LoggingFilter.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/filter/LoggingFilter.java @@ -32,7 +32,7 @@ public class LoggingFilter extends OncePerRequestFilter { private final AdminActionLogAppService adminActionLogAppService; private final JwtTokenProvider jwtTokenProvider; - private static final String[] EXCLUDED_URL_PATTERNS = {"/login", "/logout", "/error", "/css", "/js", "/images", "/favicon.ico", "/common/modal", "/font"}; + private static final String[] EXCLUDED_URL_PATTERNS = {"/login", "/logout", "/error", "/css", "/js", "/images", "/favicon.ico", "/common/modal", "/font", "/csrf"}; private static final String[] EXCLUDED_CONTENT_TYPES = {"text/html"}; @Override diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityConfig.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityConfig.java index 685dd59..c927e89 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityConfig.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/SecurityConfig.java @@ -1,11 +1,10 @@ package com.bpgroup.poc.admin.security; -import com.bpgroup.poc.admin.app.jwt.JwtTokenAppService; -import com.bpgroup.poc.admin.security.authentication.*; import com.bpgroup.poc.admin.app.authentication.AuthenticationAppService; +import com.bpgroup.poc.admin.app.authorization.AuthorizationAppService; +import com.bpgroup.poc.admin.security.authentication.*; import com.bpgroup.poc.admin.security.authorization.CustomAccessDeniedHandler; import com.bpgroup.poc.admin.security.authorization.CustomAuthorizationManager; -import com.bpgroup.poc.admin.app.authorization.AuthorizationAppService; import com.bpgroup.poc.admin.security.jwt.JwtTokenConstants; import com.bpgroup.poc.admin.security.jwt.JwtTokenProvider; import com.bpgroup.poc.admin.security.jwt.JwtTokenValidateFilter; @@ -38,7 +37,6 @@ public class SecurityConfig { private final AuthenticationAppService authenticationAppService; private final AuthorizationAppService authorizationAppService; - private final JwtTokenAppService jwtTokenAppService; private final JwtTokenProvider jwtTokenProvider; @@ -70,7 +68,7 @@ public class SecurityConfig { private void configureAuthorization(HttpSecurity http) throws Exception { http.authorizeHttpRequests(c -> c .requestMatchers("/css/**", "/images/**", "/js/**", "/font/**", "/favicon.ico").permitAll() - .requestMatchers("/common/modal/**").permitAll() + .requestMatchers("/common/modal/**", "/csrf").permitAll() .requestMatchers(LOGIN_PATH, LOGOUT_PATH, ERROR_PATH).permitAll() .anyRequest() .access(new CustomAuthorizationManager(authorizationAppService)) diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidateFilter.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidateFilter.java index 1a8787c..de29563 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidateFilter.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidateFilter.java @@ -23,7 +23,7 @@ import java.util.stream.Stream; public class JwtTokenValidateFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; - private static final String[] EXCLUDED_URL_PATTERNS = {"/login", "/logout", "/error", "/css", "/js", "/images", "/favicon.ico", "/common/modal", "/font"}; + private static final String[] EXCLUDED_URL_PATTERNS = {"/login", "/logout", "/error", "/css", "/js", "/images", "/favicon.ico", "/common/modal", "/font", "/csrf"}; @Override diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/web/common/CommonRestController.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/common/CommonRestController.java new file mode 100644 index 0000000..02909ac --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/common/CommonRestController.java @@ -0,0 +1,17 @@ +package com.bpgroup.poc.admin.web.common; + +import com.bpgroup.poc.admin.web.common.reqres.CsrfTokenResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class CommonRestController { + @GetMapping("/csrf") + public ResponseEntity token(HttpServletRequest request) { + CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); + return ResponseEntity.ok(CsrfTokenResponse.of(token.getToken(), token.getHeaderName())); + } +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/web/common/reqres/CsrfTokenResponse.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/common/reqres/CsrfTokenResponse.java new file mode 100644 index 0000000..85519f9 --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/web/common/reqres/CsrfTokenResponse.java @@ -0,0 +1,18 @@ +package com.bpgroup.poc.admin.web.common.reqres; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class CsrfTokenResponse { + private final String token; + private final String header; + + public static CsrfTokenResponse of(String token, String header) { + return new CsrfTokenResponse(token, header); + } +} diff --git a/poc/admin/src/main/resources/static/js/pagehelper.js b/poc/admin/src/main/resources/static/js/pagehelper.js index c282049..d05b031 100644 --- a/poc/admin/src/main/resources/static/js/pagehelper.js +++ b/poc/admin/src/main/resources/static/js/pagehelper.js @@ -56,12 +56,15 @@ const PageHelper = { } } - this.showAlertModal({ - title: '오류', - message: message, - }, { - cancel: cancelFunction - }); + alert(message); + + // TODO: 퍼블리싱 추가 후 주석 해제 및 작업 필요 + // this.showAlertModal({ + // title: '오류', + // message: message, + // }, { + // cancel: cancelFunction + // }); }, /** @@ -98,17 +101,20 @@ const PageHelper = { callbacks = {}; } - this.loadAndApplyPage({ - url: `/common/modal/alert`, - method: 'POST', - data: `title=${params.title}&message=${params.message}`, - contentSelector: 'div[data-alert-modal="body"]', - appendToSelector: 'body' - }, { - success: function () { - EventRouter.register('clickAlertModalCancelButton', callbacks.cancel); - }, - }); + alert(params.message); + + // TODO: 퍼블리싱 추가 후 주석 해제 및 작업 필요 + // this.loadAndApplyPage({ + // url: `/common/modal/alert`, + // method: 'POST', + // data: `title=${params.title}&message=${params.message}`, + // contentSelector: 'div[data-alert-modal="body"]', + // appendToSelector: 'body' + // }, { + // success: function () { + // EventRouter.register('clickAlertModalCancelButton', callbacks.cancel); + // }, + // }); }, /** diff --git a/poc/admin/src/main/resources/static/js/reqhelper.js b/poc/admin/src/main/resources/static/js/reqhelper.js index 5cbfbf4..6385552 100644 --- a/poc/admin/src/main/resources/static/js/reqhelper.js +++ b/poc/admin/src/main/resources/static/js/reqhelper.js @@ -28,6 +28,7 @@ const Reqhelper = { } }) .catch((error) => { + refreshCsrf(); if (eFunc) { eFunc(error); } @@ -65,6 +66,7 @@ const Reqhelper = { } }) .catch((error) => { + refreshCsrf(); if (eFunc) { eFunc(error); } @@ -76,4 +78,18 @@ const Reqhelper = { }); } +} + +function refreshCsrf() { + fetch('/csrf', { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }).then(response => { + response.json().then(data => { + const csrfToken = data.token; + document.querySelector('meta[name="_csrf"]').setAttribute('content', csrfToken); + }); + }); } \ No newline at end of file diff --git a/poc/admin/src/main/resources/templates/main/admin/management/list.html b/poc/admin/src/main/resources/templates/main/admin/management/list.html index 4d5ee8b..115c0ed 100644 --- a/poc/admin/src/main/resources/templates/main/admin/management/list.html +++ b/poc/admin/src/main/resources/templates/main/admin/management/list.html @@ -88,6 +88,25 @@ }) ; + /** + * 전체 조회 + */ + document.getElementById('btnFindAll').addEventListener(('click'), function () { + const requestUri = /*[[@{/admin/management/list}]]*/ ''; + Reqhelper.reqGetJson(requestUri, (res) => { + const selAdmin = document.getElementById('selAdmin'); + selAdmin.innerHTML = ''; + res.forEach(item => { + const option = document.createElement('option'); + option.value = item.id; + option.text = item.loginId; + selAdmin.appendChild(option); + }); + }, () => { + PageHelper.showErrorModal('데이터 조회에 실패했습니다.'); + }); + }); + /** * 관리자 등록 */ @@ -111,25 +130,6 @@ }); - /** - * 전체 조회 - */ - document.getElementById('btnFindAll').addEventListener(('click'), function () { - const requestUri = /*[[@{/admin/management/list}]]*/ ''; - Reqhelper.reqGetJson(requestUri, (res) => { - const selAdmin = document.getElementById('selAdmin'); - selAdmin.innerHTML = ''; - res.forEach(item => { - const option = document.createElement('option'); - option.value = item.id; - option.text = item.loginId; - selAdmin.appendChild(option); - }); - }, () => { - PageHelper.showErrorModal('데이터 조회에 실패했습니다.'); - }); - }); - document.getElementById('selAdmin').addEventListener(('change'), function () { document.getElementById('iptUpdateId').value = this.value; document.getElementById('iptUpdateLoginId').value = this.options[this.selectedIndex].text; @@ -153,8 +153,7 @@ Reqhelper.reqPostJson(requestUri, data, () => { PageHelper.showAlertModal({title: '수정 완료', message: '수정이 완료되었습니다.'}); }, () => { - // PageHelper.showErrorModal('수정에 실패했습니다.'); - alert('수정에 실패했습니다.'); + PageHelper.showErrorModal('수정에 실패했습니다.'); }); });