admin: 관리자 action log 추가

This commit is contained in:
geonhos 2024-05-21 14:37:14 +09:00
parent 618f92df7a
commit 24873db317
14 changed files with 315 additions and 17 deletions

View File

@ -0,0 +1,39 @@
package com.bpgroup.poc.admin.app.admin;
import com.bpgroup.poc.admin.domain.base.admin.entity.Admin;
import com.bpgroup.poc.admin.domain.base.admin.service.AdminActionLogCreateCommand;
import com.bpgroup.poc.admin.domain.base.admin.service.AdminActionLogService;
import com.bpgroup.poc.admin.domain.base.admin.service.AdminActionLogUpdateCommand;
import com.bpgroup.poc.admin.domain.base.admin.service.AdminService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AdminActionLogAppService {
private final AdminActionLogService adminActionLogService;
private final AdminService adminService;
public void create(AdminActionLogCreate.Request request) {
Admin findAdmin = adminService.find(request.getLoginId()).orElseThrow(() -> new IllegalArgumentException("Not found admin"));
adminActionLogService.create(
AdminActionLogCreateCommand.of(
request.getSessionId(),
request.getRequestUri(),
request.getRequestValue(),
findAdmin
)
);
}
public void update(AdminActionLogUpdate.Request request) {
adminActionLogService.update(
AdminActionLogUpdateCommand.of(
request.getSessionId(),
request.getResponseValue()
)
);
}
}

View File

@ -0,0 +1,21 @@
package com.bpgroup.poc.admin.app.admin;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
public class AdminActionLogCreate {
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class Request {
private final String sessionId;
private final String requestUri;
private final String requestValue;
private final String loginId;
public static Request of(String sessionId, String requestUri, String requestValue, String loginId) {
return new Request(sessionId, requestUri, requestValue, loginId);
}
}
}

View File

@ -0,0 +1,20 @@
package com.bpgroup.poc.admin.app.admin;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
public class AdminActionLogUpdate {
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class Request {
private final String sessionId;
private final String responseValue;
public static Request of(String sessionId, String responseValue) {
return new Request(sessionId, responseValue);
}
}
}

View File

@ -25,6 +25,6 @@ public class AuthorizationService {
Admin admin = findAdmin.get(); Admin admin = findAdmin.get();
return admin.getAdminRole().getRole().getRoleMenus().stream() return admin.getAdminRole().getRole().getRoleMenus().stream()
.anyMatch(roleMenu -> roleMenu.getMenu().getUri().equals(requestUri)); .anyMatch(roleMenu -> requestUri.contains(roleMenu.getMenu().getUri()));
} }
} }

View File

@ -1,6 +1,5 @@
package com.bpgroup.poc.admin.common; package com.bpgroup.poc.admin.common;
import com.bpgroup.poc.admin.security.jwt.JwtTokenConstants;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
@ -14,9 +13,18 @@ public class CookieHelper {
HttpServletRequest request = attr.getRequest(); HttpServletRequest request = attr.getRequest();
Cookie[] cookies = request.getCookies(); Cookie[] cookies = request.getCookies();
return getValue(cookies, name);
}
public static Optional<String> getValueFromCookieWithName(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
return getValue(cookies, name);
}
private static Optional<String> getValue(Cookie[] cookies, String name) {
if (null != cookies) { if (null != cookies) {
for (Cookie cookie : cookies) { for (Cookie cookie : cookies) {
if (JwtTokenConstants.NAME.equals(cookie.getName())) { if (cookie.getName().equals(name)) {
return Optional.of(cookie.getValue()); return Optional.of(cookie.getValue());
} }
} }

View File

@ -0,0 +1,19 @@
package com.bpgroup.poc.admin.common;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
public class JwtHelper {
public static Claims getClaims(String jwtKey, String token) {
SecretKey key = Keys.hmacShaKeyFor(jwtKey.getBytes(StandardCharsets.UTF_8));
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload();
}
}

View File

@ -0,0 +1,56 @@
package com.bpgroup.poc.admin.domain.base.admin.entity;
import com.bpgroup.poc.admin.domain.base.BaseEntity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "admin_action_log")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class AdminActionLog extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "session_id", nullable = false)
private String sessionId;
@Column(name = "request_uri", nullable = false)
private String requestUri;
@Column(name = "request_value")
private String requestValue;
@Column(name = "response_value")
private String responseValue;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "admin_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), nullable = false)
private Admin admin;
private AdminActionLog(String sessionId, String requestUri, String requestValue, Admin admin) {
this.sessionId = sessionId;
this.requestUri = requestUri;
this.requestValue = requestValue;
this.admin = admin;
}
private AdminActionLog(String sessionId, String responseValue) {
this.sessionId = sessionId;
this.responseValue = responseValue;
}
public static AdminActionLog createOf(String sessionId, String requestUri, String requestValue, Admin admin) {
return new AdminActionLog(sessionId, requestUri, requestValue, admin);
}
public static AdminActionLog updateOf(String sessionId, String responseValue) {
return new AdminActionLog(sessionId, responseValue);
}
public void update(AdminActionLog entity) {
this.responseValue = entity.responseValue;
}
}

View File

@ -0,0 +1,9 @@
package com.bpgroup.poc.admin.domain.base.admin.entity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface AdminActionLogRepository extends JpaRepository<AdminActionLog, Long> {
Optional<AdminActionLog> findBySessionId(String sessionId);
}

View File

@ -0,0 +1,29 @@
package com.bpgroup.poc.admin.domain.base.admin.service;
import com.bpgroup.poc.admin.domain.base.admin.entity.Admin;
import com.bpgroup.poc.admin.domain.base.admin.entity.AdminActionLog;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class AdminActionLogCreateCommand {
@NotBlank
private final String sessionId;
@NotBlank
private final String requestUri;
private final String requestValue;
@NotNull
private final Admin admin;
public static AdminActionLogCreateCommand of(String sessionId, String requestUri, String requestValue, Admin admin) {
return new AdminActionLogCreateCommand(sessionId, requestUri, requestValue, admin);
}
public AdminActionLog toEntity() {
return AdminActionLog.createOf(sessionId, requestUri, requestValue, admin);
}
}

View File

@ -0,0 +1,33 @@
package com.bpgroup.poc.admin.domain.base.admin.service;
import com.bpgroup.poc.admin.domain.base.admin.entity.AdminActionLog;
import com.bpgroup.poc.admin.domain.base.admin.entity.AdminActionLogRepository;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@Service
@RequiredArgsConstructor
@Validated
@Transactional
public class AdminActionLogService {
private final AdminActionLogRepository adminActionLogRepository;
public void create(
@NotNull @Valid AdminActionLogCreateCommand command
) {
adminActionLogRepository.save(command.toEntity());
}
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());
}
}

View File

@ -0,0 +1,24 @@
package com.bpgroup.poc.admin.domain.base.admin.service;
import com.bpgroup.poc.admin.domain.base.admin.entity.AdminActionLog;
import jakarta.validation.constraints.NotBlank;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class AdminActionLogUpdateCommand {
@NotBlank
private final String sessionId;
@NotBlank
private final String responseValue;
public static AdminActionLogUpdateCommand of(String sessionId, String responseValue) {
return new AdminActionLogUpdateCommand(sessionId, responseValue);
}
public AdminActionLog toEntity() {
return AdminActionLog.updateOf(sessionId, responseValue);
}
}

View File

@ -9,6 +9,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.util.Optional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Validated @Validated
@ -33,4 +35,8 @@ public class AdminService {
public void delete(@NotNull Long id) { public void delete(@NotNull Long id) {
adminRepository.deleteById(id); adminRepository.deleteById(id);
} }
public Optional<Admin> find(String adminId) {
return adminRepository.findByLoginId(adminId);
}
} }

View File

@ -1,9 +1,17 @@
package com.bpgroup.poc.admin.filter; package com.bpgroup.poc.admin.filter;
import com.bpgroup.poc.admin.app.admin.AdminActionLogAppService;
import com.bpgroup.poc.admin.app.admin.AdminActionLogCreate;
import com.bpgroup.poc.admin.app.admin.AdminActionLogUpdate;
import com.bpgroup.poc.admin.common.CookieHelper;
import com.bpgroup.poc.admin.common.JwtHelper;
import com.bpgroup.poc.admin.security.jwt.JwtTokenConstants;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
@ -14,12 +22,17 @@ import org.springframework.web.util.ContentCachingResponseWrapper;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
@Slf4j @Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE)
@RequiredArgsConstructor
@Component @Component
public class LoggingFilter extends OncePerRequestFilter { public class LoggingFilter extends OncePerRequestFilter {
private final AdminActionLogAppService adminActionLogAppService;
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
customDoFilter(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response), filterChain); customDoFilter(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response), filterChain);
@ -38,8 +51,29 @@ public class LoggingFilter extends OncePerRequestFilter {
} }
private void loggingRequest(ContentCachingRequestWrapper request, String sessionId) { private void loggingRequest(ContentCachingRequestWrapper request, String sessionId) {
String requestValue = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8);
Optional<String> jwtToken = CookieHelper.getValueFromCookieWithName(request, JwtTokenConstants.NAME);
if (jwtToken.isPresent()) {
try {
Claims claims = JwtHelper.getClaims(JwtTokenConstants.KEY, jwtToken.get());
String username = claims.get("username", String.class);
adminActionLogAppService.create(
AdminActionLogCreate.Request.of(
sessionId,
request.getRequestURI(),
requestValue,
username
)
);
} catch (Exception e) {
log.info("session ID: {} Request - JWT 토큰 만료", sessionId);
}
}
log.info("session ID: {} Request - method: {} uri: {}", sessionId, request.getMethod(), request.getRequestURI()); log.info("session ID: {} Request - method: {} uri: {}", sessionId, request.getMethod(), request.getRequestURI());
log.info("session ID: {} Request - params: {}", sessionId, new String(request.getContentAsByteArray(), StandardCharsets.UTF_8)); log.info("session ID: {} Request - params: {}", sessionId, requestValue);
} }
private void loggingResponse(ContentCachingResponseWrapper response, String sessionId) { private void loggingResponse(ContentCachingResponseWrapper response, String sessionId) {
@ -48,7 +82,16 @@ public class LoggingFilter extends OncePerRequestFilter {
log.info("Session ID: {} Response - status: {} Content-Type: {}", sessionId, response.getStatus(), contentType); log.info("Session ID: {} Response - status: {} Content-Type: {}", sessionId, response.getStatus(), contentType);
if (contentType != null && isLoggingContentType(contentType)) { if (contentType != null && isLoggingContentType(contentType)) {
log.info("Session ID: {} Response - body: {}", sessionId, new String(response.getContentAsByteArray(), StandardCharsets.UTF_8)); String responseValue = new String(response.getContentAsByteArray(), StandardCharsets.UTF_8);
adminActionLogAppService.update(
AdminActionLogUpdate.Request.of(
sessionId,
responseValue
)
);
log.info("Session ID: {} Response - body: {}", sessionId, responseValue);
} }
} }

View File

@ -1,9 +1,8 @@
package com.bpgroup.poc.admin.security.jwt; package com.bpgroup.poc.admin.security.jwt;
import com.bpgroup.poc.admin.common.CookieHelper; import com.bpgroup.poc.admin.common.CookieHelper;
import com.bpgroup.poc.admin.common.JwtHelper;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@ -13,9 +12,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.crypto.SecretKey;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -27,15 +24,9 @@ public class JwtTokenValidateFilter extends OncePerRequestFilter {
try { try {
Optional<String> jwtToken = CookieHelper.getValueFromCookieWithName(JwtTokenConstants.NAME); Optional<String> jwtToken = CookieHelper.getValueFromCookieWithName(JwtTokenConstants.NAME);
if (jwtToken.isPresent()) { if (jwtToken.isPresent()) {
SecretKey key = Keys.hmacShaKeyFor(JwtTokenConstants.KEY.getBytes(StandardCharsets.UTF_8)); Claims claims = JwtHelper.getClaims(JwtTokenConstants.KEY, jwtToken.get());
Claims claims = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(jwtToken.get())
.getPayload();
String username = claims.get("username", String.class); String username = claims.get("username", String.class);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, null); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, null);
SecurityContextHolder.getContext().setAuthentication(auth); SecurityContextHolder.getContext().setAuthentication(auth);
} }