권한 체크 추가

This commit is contained in:
Hyojin Ahn 2025-11-20 08:59:50 -05:00
parent a1ca9d1328
commit e51837e6dd
8 changed files with 215 additions and 2 deletions

View File

@ -0,0 +1,32 @@
package com.goi.erp.common.exception;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handleRuntimeException(RuntimeException ex) {
Map<String, Object> body = new HashMap<>();
body.put("error", ex.getMessage());
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
return ResponseEntity.badRequest().body(body);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<String> handleAccessDenied(AccessDeniedException ex) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ex.getMessage());
}
}

View File

@ -0,0 +1,18 @@
package com.goi.erp.common.permission;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class Permission {
private PermissionEnums.Module module;
private PermissionEnums.Action action;
private PermissionEnums.Scope scope;
private final boolean all;
public boolean isAll() {
return all || module == PermissionEnums.Module.ALL;
}
}

View File

@ -0,0 +1,36 @@
package com.goi.erp.common.permission;
public class PermissionChecker {
public static boolean canCreateCRM(PermissionSet set) {
if (set.hasAll()) return true;
return set.has(PermissionEnums.Module.C, PermissionEnums.Action.C);
}
public static boolean canReadCRM(PermissionSet set) {
if (set.hasAll()) return true;
return set.has(PermissionEnums.Module.C, PermissionEnums.Action.R);
}
public static boolean canUpdateCRM(PermissionSet set) {
if (set.hasAll()) return true;
return set.has(PermissionEnums.Module.C, PermissionEnums.Action.U);
}
public static boolean canDeleteCRM(PermissionSet set) {
if (set.hasAll()) return true;
return set.has(PermissionEnums.Module.C, PermissionEnums.Action.D);
}
// 범위까지 체크
public static boolean canReadCRMAll(PermissionSet set) {
if (set.hasAll()) return true;
return set.hasFull(
PermissionEnums.Module.C,
PermissionEnums.Action.R,
PermissionEnums.Scope.A
);
}
}

View File

@ -0,0 +1,22 @@
package com.goi.erp.common.permission;
public class PermissionEnums {
public enum Module {
H, // HCM
C, // CRM
A, // ACC
O, // OPERATION
S, // SYSTEM
ALL // ADMIN
}
public enum Action {
C, R, U, D
}
public enum Scope {
S, P, A
}
}

View File

@ -0,0 +1,32 @@
package com.goi.erp.common.permission;
import java.util.ArrayList;
import java.util.List;
public class PermissionParser {
public static PermissionSet parse(List<String> permissionStrings) {
List<Permission> list = new ArrayList<>();
for (String str : permissionStrings) {
// ALL 권한 추가
if ("ALL".equalsIgnoreCase(str)) {
list.add(new Permission(PermissionEnums.Module.ALL, null, null, true));
continue;
}
// 문자 세개 조합 인지 확인
String[] parts = str.split(":");
if (parts.length != 3) continue;
PermissionEnums.Module module = PermissionEnums.Module.valueOf(parts[0]);
PermissionEnums.Action action = PermissionEnums.Action.valueOf(parts[1]);
PermissionEnums.Scope scope = PermissionEnums.Scope.valueOf(parts[2]);
//
list.add(new Permission(module, action, scope, false));
}
return new PermissionSet(list);
}
}

View File

@ -0,0 +1,26 @@
package com.goi.erp.common.permission;
import java.util.List;
public record PermissionSet(List<Permission> permissions) {
public boolean has(PermissionEnums.Module module,
PermissionEnums.Action action) {
return permissions.stream()
.anyMatch(p -> p.getModule() == module &&
p.getAction() == action);
}
public boolean hasFull(PermissionEnums.Module module,
PermissionEnums.Action action,
PermissionEnums.Scope scope) {
return permissions.stream()
.anyMatch(p -> p.getModule() == module &&
p.getAction() == action &&
p.getScope().ordinal() >= scope.ordinal());
}
public boolean hasAll() {
return permissions.stream().anyMatch(p -> p.isAll());
}
}

View File

@ -1,5 +1,7 @@
package com.goi.erp.controller;
import com.goi.erp.common.permission.PermissionChecker;
import com.goi.erp.common.permission.PermissionSet;
import com.goi.erp.dto.CustomerRequestDto;
import com.goi.erp.dto.CustomerResponseDto;
import com.goi.erp.service.CustomerService;
@ -7,6 +9,8 @@ import com.goi.erp.service.CustomerService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -22,6 +26,12 @@ public class CustomerController {
// CREATE
@PostMapping
public ResponseEntity<CustomerResponseDto> createCustomer(@RequestBody CustomerRequestDto requestDto) {
// 권한 체크
PermissionSet permissionSet = (PermissionSet) SecurityContextHolder.getContext().getAuthentication().getDetails();
if (!PermissionChecker.canCreateCRM(permissionSet)) {
throw new AccessDeniedException("You do not have permission to create CRM data");
}
CustomerResponseDto responseDto = customerService.createCustomer(requestDto);
return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
}
@ -29,12 +39,24 @@ public class CustomerController {
// READ ALL
@GetMapping
public ResponseEntity<List<CustomerResponseDto>> getAllCustomers() {
// 권한 체크
PermissionSet permissionSet = (PermissionSet) SecurityContextHolder.getContext().getAuthentication().getDetails();
if (!PermissionChecker.canReadCRMAll(permissionSet)) {
throw new AccessDeniedException("You do not have permission to read all CRM data");
}
return ResponseEntity.ok(customerService.getAllCustomers());
}
// READ ONE
@GetMapping("/{uuid}")
public ResponseEntity<CustomerResponseDto> getCustomer(@PathVariable UUID uuid) {
// 권한 체크
PermissionSet permissionSet = (PermissionSet) SecurityContextHolder.getContext().getAuthentication().getDetails();
if (!PermissionChecker.canReadCRM(permissionSet)) {
throw new AccessDeniedException("You do not have permission to read CRM data");
}
return ResponseEntity.ok(customerService.getCustomerByUuid(uuid));
}
@ -43,12 +65,24 @@ public class CustomerController {
public ResponseEntity<CustomerResponseDto> updateCustomer(
@PathVariable UUID uuid,
@RequestBody CustomerRequestDto requestDto) {
// 권한 체크
PermissionSet permissionSet = (PermissionSet) SecurityContextHolder.getContext().getAuthentication().getDetails();
if (!PermissionChecker.canUpdateCRM(permissionSet)) {
throw new AccessDeniedException("You do not have permission to update CRM data");
}
return ResponseEntity.ok(customerService.updateCustomer(uuid, requestDto));
}
// DELETE
@DeleteMapping("/{uuid}")
public ResponseEntity<Void> deleteCustomer(@PathVariable UUID uuid) {
// 권한 체크
PermissionSet permissionSet = (PermissionSet) SecurityContextHolder.getContext().getAuthentication().getDetails();
if (!PermissionChecker.canDeleteCRM(permissionSet)) {
throw new AccessDeniedException("You do not have permission to delete CRM data");
}
customerService.deleteCustomer(uuid);
return ResponseEntity.noContent().build();
}

View File

@ -7,6 +7,9 @@ import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.goi.erp.common.permission.PermissionParser;
import com.goi.erp.common.permission.PermissionSet;
import java.security.Key;
import java.util.List;
import java.util.function.Function;
@ -95,6 +98,16 @@ public class JwtService {
.getBody();
}
/**
* Permission Set 변환
*/
@SuppressWarnings("unchecked")
public PermissionSet getPermissions(String token) {
Claims claims = extractAllClaims(token);
List<String> permissions = claims.get("permissions", List.class);
return PermissionParser.parse(permissions);
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey); // auth-service와 동일한 Base64 secret
return Keys.hmacShaKeyFor(keyBytes);
@ -104,7 +117,7 @@ public class JwtService {
JwtService jwtService = new JwtService();
jwtService.secretKey = "D0HaHnTPKLkUO9ULL1Ulm6XDZjhzuFtvTCcxTxSoCS8=";
String token = "eyJhbGciOiJIUzI1NiJ9.eyJmaXJzdE5hbWUiOiJIeW9KaW4iLCJsYXN0TmFtZSI6IkFobiIsInBlcm1pc3Npb25zIjpbIkFMTCJdLCJyb2xlcyI6WyJBZG1pbiJdLCJzdWIiOiJhMjM4ZWQ0OC03OGRjLTQ2YWEtOWNiMC1hNWYxZGQyZDJmMTMiLCJpYXQiOjE3NjM1NzE2ODYsImV4cCI6MTc2MzY1ODA4Nn0.0fQT-I4dqHMapIEbxfy8X_SdCReYhUy6djnXUzw4gWc";
String token = "eyJhbGciOiJIUzI1NiJ9.eyJmaXJzdE5hbWUiOiJIeW9qaW4iLCJsYXN0TmFtZSI6IkFobiIsInBlcm1pc3Npb25zIjpbIkFMTCJdLCJyb2xlcyI6WyJBZG1pbiJdLCJzdWIiOiJhMjM4ZWQ0OC03OGRjLTQ2YWEtOWNiMC1hNWYxZGQyZDJmMTMiLCJpYXQiOjE3NjM2NDYzNzQsImV4cCI6MTc2MzczMjc3NH0.HwFZeNbxVldcBeiqc3TUO3x5on2Quy7_Yd_HTkIfOR4";
// user 정보
Claims claims = jwtService.extractAllClaims(token);
@ -115,7 +128,7 @@ public class JwtService {
System.out.println("IssuedAt: " + claims.getIssuedAt());
System.out.println("Expiration: " + claims.getExpiration());
System.out.println("FirstName: " + claims.get("firstName", String.class));
System.out.println("LastName: " + claims.get("LastName", String.class));
System.out.println("LastName: " + claims.get("lastName", String.class));
// 모든 Claims 확인
// Claims claims = Jwts.parserBuilder()