log 파일 추가, 권한별 메뉴 기능 추가
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
package com.kamco.cd.kamcoback.auth;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kamco.cd.kamcoback.common.enums.RoleType;
|
||||
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** redis에 등록된 메뉴별 권한 확인 */
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RedisAuthorizationManager
|
||||
implements AuthorizationManager<RequestAuthorizationContext> {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(RedisAuthorizationManager.class);
|
||||
private static final String REDIS_KEY = "auth:api:role";
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public AuthorizationDecision check(
|
||||
Supplier<Authentication> authenticationSupplier, RequestAuthorizationContext context) {
|
||||
|
||||
HttpServletRequest request = context.getRequest();
|
||||
String requestPath = normalizePath(request.getRequestURI());
|
||||
|
||||
Authentication authentication = authenticationSupplier.get();
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
|
||||
// ADMIN 은 무조건 허용
|
||||
if (hasAdmin(authentication)) {
|
||||
return new AuthorizationDecision(true);
|
||||
}
|
||||
|
||||
// 사용자 role 목록
|
||||
List<String> userRoles = extractRoles(authentication);
|
||||
if (userRoles.isEmpty()) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
|
||||
// Redis 조회 (1회)
|
||||
String json = redisTemplate.opsForValue().get(REDIS_KEY);
|
||||
if (json == null || json.isBlank()) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
|
||||
// JSON 파싱 (1회)
|
||||
List<MenuDto.MenuWithRolesDto> menus = parseMenus(json);
|
||||
if (menus.isEmpty()) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
|
||||
// role + prefix URI 매칭
|
||||
for (String role : userRoles) {
|
||||
if (isAllowed(menus, role, requestPath)) {
|
||||
return new AuthorizationDecision(true);
|
||||
}
|
||||
}
|
||||
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* 핵심 권한 판별 로직
|
||||
* ========================= */
|
||||
private boolean isAllowed(List<MenuDto.MenuWithRolesDto> menus, String role, String requestPath) {
|
||||
|
||||
for (MenuDto.MenuWithRolesDto menu : menus) {
|
||||
if (menu == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String baseUri = menu.getMenuApiUri();
|
||||
if (baseUri == null || baseUri.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// role 포함 여부
|
||||
if (!hasRole(menu.getRoles(), role)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// prefix URI 허용
|
||||
if (matchUri(baseUri, requestPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* URI prefix 매칭
|
||||
* ========================= */
|
||||
private boolean matchUri(String baseUri, String requestPath) {
|
||||
String base = normalizePath(baseUri);
|
||||
String req = normalizePath(requestPath);
|
||||
|
||||
// /api/log/audit → /api/log/audit/**
|
||||
return req.equals(base) || req.startsWith(base + "/");
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* role 문자열 처리
|
||||
* ========================= */
|
||||
private boolean hasRole(String rolesCsv, String targetRole) {
|
||||
if (rolesCsv == null || rolesCsv.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
if (targetRole == null || targetRole.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String r : rolesCsv.split(",")) {
|
||||
if (targetRole.equalsIgnoreCase(r.trim())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* Redis JSON 파싱
|
||||
* ========================= */
|
||||
private List<MenuDto.MenuWithRolesDto> parseMenus(String json) {
|
||||
try {
|
||||
return objectMapper.readValue(json, new TypeReference<List<MenuDto.MenuWithRolesDto>>() {});
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse redis menu json. key={}", REDIS_KEY, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* ADMIN 판별
|
||||
* ========================= */
|
||||
private boolean hasAdmin(Authentication authentication) {
|
||||
for (GrantedAuthority ga : authentication.getAuthorities()) {
|
||||
if (ga == null || ga.getAuthority() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String auth = ga.getAuthority();
|
||||
String admin = RoleType.ADMIN.getId();
|
||||
|
||||
if (auth.equals(admin) || auth.equals("ROLE_" + admin)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* ROLE 목록 추출
|
||||
* ========================= */
|
||||
private List<String> extractRoles(Authentication authentication) {
|
||||
return authentication.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.filter(a -> a != null && !a.isBlank())
|
||||
.map(a -> a.startsWith("ROLE_") ? a.substring(5) : a)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* URI 정규화
|
||||
* ========================= */
|
||||
private String normalizePath(String path) {
|
||||
if (path == null || path.isBlank()) {
|
||||
return "/";
|
||||
}
|
||||
return path.replaceAll("//+", "/");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user