diff --git "a/docs/RBAC_\352\265\254\355\230\204_\352\260\200\354\235\264\353\223\234.md" "b/docs/RBAC_\352\265\254\355\230\204_\352\260\200\354\235\264\353\223\234.md" index 84d8a5ce..a113de0e 100644 --- "a/docs/RBAC_\352\265\254\355\230\204_\352\260\200\354\235\264\353\223\234.md" +++ "b/docs/RBAC_\352\265\254\355\230\204_\352\260\200\354\235\264\353\223\234.md" @@ -2,7 +2,8 @@ ## 개요 -UMC Product Server에서 **RBAC (Role-Based Access Control) + ABAC (Attribute-Based Access Control) 하이브리드** 권한 제어 시스템을 구현하는 방법을 설명합니다. +UMC Product Server에서 **RBAC (Role-Based Access Control) + ABAC (Attribute-Based Access Control) 하이브리드** 권한 제어 시스템을 구현하는 +방법을 설명합니다. ### 왜 RBAC + ABAC 하이브리드인가? @@ -74,19 +75,23 @@ public enum MemberRole { ``` **용도:** + - ✅ 시스템 전역 권한 (모든 데이터 접근, 시스템 설정) - ✅ 챌린저가 아닌 사람 구분 - ✅ 기본 인증 여부 **예시:** + ```java // ADMIN만 접근 @PreAuthorize("hasRole('ADMIN')") -public ApiResponse deleteAllData() { } +public ApiResponse deleteAllData() { +} // 인증된 사용자 (MEMBER 이상) @PreAuthorize("hasRole('MEMBER')") -public ApiResponse getMyProfile() { } +public ApiResponse getMyProfile() { +} ``` #### Member Entity 수정 @@ -128,10 +133,12 @@ public class Member extends BaseEntity { ```sql -- db/migration/V{version}__add_member_role.sql ALTER TABLE member -ADD COLUMN role VARCHAR(20) NOT NULL DEFAULT 'MEMBER'; + ADD COLUMN role VARCHAR(20) NOT NULL DEFAULT 'MEMBER'; -- 기존 데이터에 기본값 설정 -UPDATE member SET role = 'MEMBER' WHERE role IS NULL; +UPDATE member +SET role = 'MEMBER' +WHERE role IS NULL; ``` --- @@ -292,7 +299,7 @@ public class MemberPrincipal implements OAuth2User { // JWT용 간단 생성자 public MemberPrincipal(Long memberId, String email, MemberRole memberRole) { this(memberId, email, memberRole, null, null, Collections.emptyList(), - Collections.emptyMap(), "id"); + Collections.emptyMap(), "id"); } @Override @@ -331,24 +338,26 @@ public class MemberPrincipal implements OAuth2User { public boolean isStaff() { return challengerRoles.stream() - .anyMatch(RoleType::isStaffRole); + .anyMatch(RoleType::isStaffRole); } } ``` **Authorities 예시:** + ```java MemberPrincipal { - memberId: 123, - memberRole: MEMBER, - currentChallengerId: 456, - challengerRoles: [SCHOOL_PRESIDENT, SCHOOL_PART_LEADER], + memberId: + 123, + memberRole:MEMBER, + currentChallengerId:456, + challengerRoles: [SCHOOL_PRESIDENT, SCHOOL_PART_LEADER], // getAuthorities() 결과 authorities: [ - "ROLE_MEMBER", // hasRole('MEMBER')로 체크 - "SCHOOL_PRESIDENT", // hasAuthority('SCHOOL_PRESIDENT')로 체크 - "SCHOOL_PART_LEADER" // hasAuthority('SCHOOL_PART_LEADER')로 체크 + "ROLE_MEMBER", // hasRole('MEMBER')로 체크 + "SCHOOL_PRESIDENT", // hasAuthority('SCHOOL_PRESIDENT')로 체크 + "SCHOOL_PART_LEADER" // hasAuthority('SCHOOL_PART_LEADER')로 체크 ] } ``` @@ -397,14 +406,14 @@ public class JwtTokenProvider { // 1. Member 조회 Member member = loadMemberPort.loadById(memberId) - .orElseThrow(() -> new AuthenticationException("Member not found")); + .orElseThrow(() -> new AuthenticationException("Member not found")); // 2. 현재 활성 기수 조회 Long currentGisuId = getCurrentGisuUseCase.getCurrentGisuId(); // 3. 현재 기수의 Challenger 조회 Optional challengerOpt = loadChallengerPort - .findByMemberIdAndGisuId(memberId, currentGisuId); + .findByMemberIdAndGisuId(memberId, currentGisuId); // 4. Challenger가 있으면 역할 로드 Long currentChallengerId = null; @@ -416,29 +425,29 @@ public class JwtTokenProvider { // 현재 기수의 ChallengerRole 조회 List roles = loadChallengerRolePort - .findByChallengerId(challenger.getId()); + .findByChallengerId(challenger.getId()); challengerRoles = roles.stream() - .map(ChallengerRole::getRoleType) - .collect(Collectors.toList()); + .map(ChallengerRole::getRoleType) + .collect(Collectors.toList()); } // 5. MemberPrincipal 생성 MemberPrincipal principal = new MemberPrincipal( - member.getId(), - member.getEmail(), - member.getRole(), - currentChallengerId, - currentGisuId, - challengerRoles, - Collections.emptyMap(), - "id" + member.getId(), + member.getEmail(), + member.getRole(), + currentChallengerId, + currentGisuId, + challengerRoles, + Collections.emptyMap(), + "id" ); return new UsernamePasswordAuthenticationToken( - principal, - token, - principal.getAuthorities() + principal, + token, + principal.getAuthorities() ); } @@ -467,8 +476,8 @@ public class MethodSecurityConfig { // ADMIN > MEMBER String hierarchyString = """ - ROLE_ADMIN > ROLE_MEMBER - """; + ROLE_ADMIN > ROLE_MEMBER + """; hierarchy.setHierarchy(hierarchyString); return hierarchy; @@ -480,7 +489,7 @@ public class MethodSecurityConfig { UmcPermissionEvaluator umcPermissionEvaluator) { DefaultMethodSecurityExpressionHandler handler = - new DefaultMethodSecurityExpressionHandler(); + new DefaultMethodSecurityExpressionHandler(); handler.setPermissionEvaluator(umcPermissionEvaluator); handler.setRoleHierarchy(roleHierarchy()); @@ -509,6 +518,7 @@ public @interface Public { **사용 예시:** ```java + @RestController @RequestMapping("/api/v1/posts") public class PostController { @@ -536,27 +546,40 @@ public class PostController { ```java // 1. Subject Attributes (주체 속성) principal.memberId -principal.memberRole (ADMIN, MEMBER) +principal. + +memberRole(ADMIN, MEMBER) + principal.currentChallengerId -principal.challengerRoles (SCHOOL_PRESIDENT, etc.) +principal. + +challengerRoles(SCHOOL_PRESIDENT, etc .) + principal.currentGisuId // 2. Resource Attributes (리소스 속성) post.authorId post.organizationId -notice.scope (GLOBAL, ORGANIZATION, PERSONAL) +notice. + +scope(GLOBAL, ORGANIZATION, PERSONAL) + activity.deadline schedule.attendanceWindow // 3. Environment Attributes (환경 속성) -LocalDateTime.now() +LocalDateTime. + +now() + request.location -request.ipAddress + request.ipAddress // 4. Action Attributes (행위 속성) -READ, CREATE, UPDATE, DELETE -APPROVE, REJECT -SUBMIT, REVIEW + READ, CREATE, UPDATE, DELETE +APPROVE, + REJECT + SUBMIT, REVIEW ``` --- @@ -585,7 +608,7 @@ public class UmcPermissionEvaluator implements PermissionEvaluator { Object permission) { if (authentication == null || - !(authentication.getPrincipal() instanceof MemberPrincipal)) { + !(authentication.getPrincipal() instanceof MemberPrincipal)) { return false; } @@ -620,7 +643,7 @@ public class UmcPermissionEvaluator implements PermissionEvaluator { try { Post post = loadPostPort.loadById(postId) - .orElseThrow(() -> new PostNotFoundException(postId)); + .orElseThrow(() -> new PostNotFoundException(postId)); return switch (permission.toUpperCase()) { case "UPDATE" -> canUpdatePost(principal, post); @@ -672,7 +695,7 @@ public class UmcPermissionEvaluator implements PermissionEvaluator { try { Notice notice = loadNoticePort.loadById(noticeId) - .orElseThrow(() -> new NoticeNotFoundException(noticeId)); + .orElseThrow(() -> new NoticeNotFoundException(noticeId)); return switch (permission.toUpperCase()) { case "UPDATE", "DELETE" -> canUpdateNotice(principal, notice); @@ -695,7 +718,8 @@ public class UmcPermissionEvaluator implements PermissionEvaluator { case ORGANIZATION -> { // 해당 조직의 운영진만 - if (notice.getOrganizationId() == null) yield false; + if (notice.getOrganizationId() == null) + yield false; yield isOrganizationStaff(principal, notice.getOrganizationId()); } @@ -717,7 +741,7 @@ public class UmcPermissionEvaluator implements PermissionEvaluator { try { Activity activity = loadActivityPort.loadById(activityId) - .orElseThrow(() -> new ActivityNotFoundException(activityId)); + .orElseThrow(() -> new ActivityNotFoundException(activityId)); return switch (permission.toUpperCase()) { case "SUBMIT" -> canSubmitActivity(principal, activity); @@ -775,10 +799,10 @@ public class UmcPermissionEvaluator implements PermissionEvaluator { } List roles = loadChallengerRolePort - .findByChallengerId(principal.getCurrentChallengerId()); + .findByChallengerId(principal.getCurrentChallengerId()); return roles.stream() - .anyMatch(r -> r.getOrganizationId().equals(organizationId)); + .anyMatch(r -> r.getOrganizationId().equals(organizationId)); } private boolean isSameOrganization(MemberPrincipal principal, Challenger targetChallenger) { @@ -787,19 +811,19 @@ public class UmcPermissionEvaluator implements PermissionEvaluator { } List principalOrgIds = loadChallengerRolePort - .findByChallengerId(principal.getCurrentChallengerId()) - .stream() - .map(ChallengerRole::getOrganizationId) - .toList(); + .findByChallengerId(principal.getCurrentChallengerId()) + .stream() + .map(ChallengerRole::getOrganizationId) + .toList(); List targetOrgIds = loadChallengerRolePort - .findByChallengerId(targetChallenger.getId()) - .stream() - .map(ChallengerRole::getOrganizationId) - .toList(); + .findByChallengerId(targetChallenger.getId()) + .stream() + .map(ChallengerRole::getOrganizationId) + .toList(); return principalOrgIds.stream() - .anyMatch(targetOrgIds::contains); + .anyMatch(targetOrgIds::contains); } private boolean isOrganizationStaff(MemberPrincipal principal, Long organizationId) { @@ -808,13 +832,13 @@ public class UmcPermissionEvaluator implements PermissionEvaluator { } List roles = loadChallengerRolePort - .findByChallengerIdAndOrganizationId( - principal.getCurrentChallengerId(), - organizationId - ); + .findByChallengerIdAndOrganizationId( + principal.getCurrentChallengerId(), + organizationId + ); return roles.stream() - .anyMatch(ChallengerRole::isStaffRole); + .anyMatch(ChallengerRole::isStaffRole); } } ``` @@ -840,27 +864,27 @@ public class OrganizationMemberService implements RemoveMemberUseCase { public void removeMember(RemoveMemberCommand command) { // 1. 실행자의 역할 조회 (현재 기수, 요청한 조직) List executorRoles = loadChallengerRolePort - .findByChallengerIdAndOrganizationId( - command.getExecutorChallengerId(), - command.getOrganizationId() - ); + .findByChallengerIdAndOrganizationId( + command.getExecutorChallengerId(), + command.getOrganizationId() + ); if (executorRoles.isEmpty()) { throw new InsufficientOrganizationPermissionException( - "해당 조직의 멤버가 아닙니다." + "해당 조직의 멤버가 아닙니다." ); } // 2. 대상자의 역할 조회 Challenger targetChallenger = loadChallengerPort - .findByMemberId(command.getTargetMemberId()) - .orElseThrow(() -> new NotChallengerException()); + .findByMemberId(command.getTargetMemberId()) + .orElseThrow(() -> new NotChallengerException()); List targetRoles = loadChallengerRolePort - .findByChallengerIdAndOrganizationId( - targetChallenger.getId(), - command.getOrganizationId() - ); + .findByChallengerIdAndOrganizationId( + targetChallenger.getId(), + command.getOrganizationId() + ); if (targetRoles.isEmpty()) { throw new BusinessException(ErrorCode.TARGET_NOT_IN_ORGANIZATION); @@ -868,20 +892,20 @@ public class OrganizationMemberService implements RemoveMemberUseCase { // 3. 권한 검증 (계층 구조) ChallengerRole executorHighestRole = executorRoles.stream() - .max(Comparator.comparing(r -> r.getRoleType().getLevel())) - .orElseThrow(); + .max(Comparator.comparing(r -> r.getRoleType().getLevel())) + .orElseThrow(); ChallengerRole targetHighestRole = targetRoles.stream() - .max(Comparator.comparing(r -> r.getRoleType().getLevel())) - .orElseThrow(); + .max(Comparator.comparing(r -> r.getRoleType().getLevel())) + .orElseThrow(); if (!executorHighestRole.canManage(targetHighestRole)) { throw new InsufficientOrganizationPermissionException( - String.format( - "%s 권한으로는 %s 권한을 가진 멤버를 관리할 수 없습니다.", - executorHighestRole.getRoleType().getDescription(), - targetHighestRole.getRoleType().getDescription() - ) + String.format( + "%s 권한으로는 %s 권한을 가진 멤버를 관리할 수 없습니다.", + executorHighestRole.getRoleType().getDescription(), + targetHighestRole.getRoleType().getDescription() + ) ); } @@ -900,6 +924,7 @@ public class OrganizationMemberService implements RemoveMemberUseCase { ### 패턴 1: 공개 API ```java + @GetMapping("/posts") @Public // 또는 @PreAuthorize("permitAll()") public ApiResponse getPublicPosts() { @@ -910,6 +935,7 @@ public ApiResponse getPublicPosts() { ### 패턴 2: 인증 필요 ```java + @GetMapping("/my-profile") @PreAuthorize("hasRole('MEMBER')") public ApiResponse getMyProfile(@AuthenticationPrincipal MemberPrincipal principal) { @@ -920,6 +946,7 @@ public ApiResponse getMyProfile(@AuthenticationPrincipal MemberPrincipal prin ### 패턴 3: ADMIN만 ```java + @DeleteMapping("/admin/data") @PreAuthorize("hasRole('ADMIN')") public ApiResponse deleteAllData() { @@ -935,9 +962,9 @@ public ApiResponse deleteAllData() { @PostMapping("/activities/{activityId}/review") @PreAuthorize("hasAnyAuthority('CENTRAL_PRESIDENT', 'SCHOOL_PRESIDENT', 'SCHOOL_STAFF')") public ApiResponse reviewActivity( - @PathVariable Long activityId, - @AuthenticationPrincipal MemberPrincipal principal, - @RequestBody ReviewRequest request) { + @PathVariable Long activityId, + @AuthenticationPrincipal MemberPrincipal principal, + @RequestBody ReviewRequest request) { // Layer 3: Service에서 "어느 조직의 운영진인가?" 검증 activityService.review(activityId, principal, request); @@ -948,11 +975,12 @@ public ApiResponse reviewActivity( ### 패턴 5: ABAC (소유권 체크) ```java + @PutMapping("/posts/{postId}") @PreAuthorize("hasPermission(#postId, 'POST', 'UPDATE')") public ApiResponse updatePost( - @PathVariable Long postId, - @RequestBody UpdatePostRequest request) { + @PathVariable Long postId, + @RequestBody UpdatePostRequest request) { postService.update(postId, request); return ApiResponse.success(); @@ -962,6 +990,7 @@ public ApiResponse updatePost( ### 패턴 6: RBAC + ABAC 조합 ```java + @DeleteMapping("/posts/{postId}") @PreAuthorize("hasRole('MEMBER') and hasPermission(#postId, 'POST', 'DELETE')") // ↑ RBAC ↑ ABAC @@ -974,16 +1003,17 @@ public ApiResponse deletePost(@PathVariable Long postId) { ### 패턴 7: Service에서 복잡한 검증 ```java + @DeleteMapping("/organizations/{orgId}/members/{memberId}") @PreAuthorize("hasRole('MEMBER')") // 최소 인증만 public ApiResponse removeMember( - @PathVariable Long orgId, - @PathVariable Long memberId, - @AuthenticationPrincipal MemberPrincipal principal) { + @PathVariable Long orgId, + @PathVariable Long memberId, + @AuthenticationPrincipal MemberPrincipal principal) { // Service에서 조직 내 권한 + 계층 구조 검증 organizationService.removeMember( - new RemoveMemberCommand(orgId, principal.getCurrentChallengerId(), memberId) + new RemoveMemberCommand(orgId, principal.getCurrentChallengerId(), memberId) ); return ApiResponse.success(); @@ -994,17 +1024,17 @@ public ApiResponse removeMember( ## 권한 검증 전략 선택 가이드 -| 시나리오 | Layer | 방법 | 예시 | -|---------|-------|------|------| -| 공개 API | - | `@Public` | 공개 게시글 조회 | -| 인증 필요 | Layer 1 | `hasRole('MEMBER')` | 내 프로필 조회 | -| 시스템 관리자 | Layer 1 | `hasRole('ADMIN')` | 모든 데이터 삭제 | -| 특정 역할 | Layer 1 | `hasAuthority('SCHOOL_PRESIDENT')` | 운영진 여부만 체크 | -| 소유권 체크 | Layer 2 | `hasPermission(#id, 'POST', 'UPDATE')` | 내 게시글만 수정 | -| 조직 권한 | Layer 1+2 | `hasAuthority(...) + hasPermission(...)` | 특정 조직 운영진 | -| 시간 기반 | Layer 2 or 3 | ABAC 또는 Service | 제출 기한 체크 | -| 계층 구조 | Layer 3 | Service | 조직 내 상하 관계 | -| 복합 조건 | Layer 3 | Service | 다중 조건 검증 | +| 시나리오 | Layer | 방법 | 예시 | +|---------|--------------|------------------------------------------|------------| +| 공개 API | - | `@Public` | 공개 게시글 조회 | +| 인증 필요 | Layer 1 | `hasRole('MEMBER')` | 내 프로필 조회 | +| 시스템 관리자 | Layer 1 | `hasRole('ADMIN')` | 모든 데이터 삭제 | +| 특정 역할 | Layer 1 | `hasAuthority('SCHOOL_PRESIDENT')` | 운영진 여부만 체크 | +| 소유권 체크 | Layer 2 | `hasPermission(#id, 'POST', 'UPDATE')` | 내 게시글만 수정 | +| 조직 권한 | Layer 1+2 | `hasAuthority(...) + hasPermission(...)` | 특정 조직 운영진 | +| 시간 기반 | Layer 2 or 3 | ABAC 또는 Service | 제출 기한 체크 | +| 계층 구조 | Layer 3 | Service | 조직 내 상하 관계 | +| 복합 조건 | Layer 3 | Service | 다중 조건 검증 | --- @@ -1076,8 +1106,8 @@ public interface LoadChallengerRolePort { List findByChallengerId(Long challengerId); List findByChallengerIdAndOrganizationId( - Long challengerId, - Long organizationId + Long challengerId, + Long organizationId ); Optional findById(Long id); @@ -1145,28 +1175,30 @@ public enum ErrorCode { ## MemberRole vs ChallengerRole 비교 -| 구분 | MemberRole | ChallengerRole | -|-----|------------|----------------| -| **위치** | member 도메인 | challenger 도메인 | -| **용도** | 시스템 전역 권한 | 조직 내부 권한 | -| **범위** | 전체 시스템 | 특정 조직 + 기수 | -| **종류** | ADMIN, MEMBER | CENTRAL_PRESIDENT, SCHOOL_STAFF, 등 | -| **설정** | Member Entity에 단일 값 | ChallengerRole Entity (1:N 관계) | -| **검증 시점** | Controller `@PreAuthorize` | Service 레이어 비즈니스 로직 | -| **예시** | "시스템 관리자인가?" | "A대학교 9기 회장인가?" | +| 구분 | MemberRole | ChallengerRole | +|-----------|----------------------------|------------------------------------| +| **위치** | member 도메인 | challenger 도메인 | +| **용도** | 시스템 전역 권한 | 조직 내부 권한 | +| **범위** | 전체 시스템 | 특정 조직 + 기수 | +| **종류** | ADMIN, MEMBER | CENTRAL_PRESIDENT, SCHOOL_STAFF, 등 | +| **설정** | Member Entity에 단일 값 | ChallengerRole Entity (1:N 관계) | +| **검증 시점** | Controller `@PreAuthorize` | Service 레이어 비즈니스 로직 | +| **예시** | "시스템 관리자인가?" | "A대학교 9기 회장인가?" | ### 사용 예시 ```java // MemberRole: 시스템 레벨 -if (principal.getMemberRole() == MemberRole.ADMIN) { - // 전체 시스템 관리자 -} +if(principal.getMemberRole() ==MemberRole.ADMIN){ + // 전체 시스템 관리자 + } // ChallengerRole: 조직 레벨 -if (principal.hasRole(RoleType.SCHOOL_PRESIDENT)) { - // 특정 학교의 회장 (현재 기수) -} + if(principal. + +hasRole(RoleType.SCHOOL_PRESIDENT)){ + // 특정 학교의 회장 (현재 기수) + } ``` --- diff --git a/src/main/java/com/umc/product/curriculum/application/port/out/LoadWorkbookMissionPort.java b/src/main/java/com/umc/product/curriculum/application/port/out/LoadWorkbookMissionPort.java index 2dbf4584..0c515aeb 100644 --- a/src/main/java/com/umc/product/curriculum/application/port/out/LoadWorkbookMissionPort.java +++ b/src/main/java/com/umc/product/curriculum/application/port/out/LoadWorkbookMissionPort.java @@ -1,7 +1,6 @@ package com.umc.product.curriculum.application.port.out; import com.umc.product.curriculum.domain.WorkbookMission; - import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/umc/product/global/response/PageResponse.java b/src/main/java/com/umc/product/global/response/PageResponse.java index 9dc5ea03..12e34d54 100644 --- a/src/main/java/com/umc/product/global/response/PageResponse.java +++ b/src/main/java/com/umc/product/global/response/PageResponse.java @@ -1,9 +1,8 @@ package com.umc.product.global.response; -import org.springframework.data.domain.Page; - import java.util.List; import java.util.function.Function; +import org.springframework.data.domain.Page; public record PageResponse( List content, diff --git a/src/main/java/com/umc/product/global/security/annotation/Public.java b/src/main/java/com/umc/product/global/security/annotation/Public.java index c6f9d9c2..cb830ff2 100644 --- a/src/main/java/com/umc/product/global/security/annotation/Public.java +++ b/src/main/java/com/umc/product/global/security/annotation/Public.java @@ -9,8 +9,7 @@ /** * 공개 API를 나타내는 어노테이션 *

- * Spring Security의 {@link PreAuthorize @PreAuthorize("permitAll()")}와 동일하게 동작합니다. - * 인증 없이 접근 가능한 API에 사용합니다. + * Spring Security의 {@link PreAuthorize @PreAuthorize("permitAll()")}와 동일하게 동작합니다. 인증 없이 접근 가능한 API에 사용합니다. *

* *

사용 예시:

@@ -31,4 +30,4 @@ @PreAuthorize("permitAll()") // Meta-annotation: @Public = @PreAuthorize("permitAll()") public @interface Public { -} \ No newline at end of file +} diff --git a/src/test/java/com/umc/product/UmcProductApplicationTests.java b/src/test/java/com/umc/product/UmcProductApplicationTests.java index a9c74e90..2eaf7b7e 100644 --- a/src/test/java/com/umc/product/UmcProductApplicationTests.java +++ b/src/test/java/com/umc/product/UmcProductApplicationTests.java @@ -1,6 +1,5 @@ package com.umc.product; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest diff --git a/src/test/java/com/umc/product/support/RestDocsConfig.java b/src/test/java/com/umc/product/support/RestDocsConfig.java index b7c0289a..f1e39e37 100644 --- a/src/test/java/com/umc/product/support/RestDocsConfig.java +++ b/src/test/java/com/umc/product/support/RestDocsConfig.java @@ -1,12 +1,14 @@ package com.umc.product.support; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; + import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; - @TestConfiguration public class RestDocsConfig { diff --git a/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet b/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet index 91911b24..6f825d30 100644 --- a/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet +++ b/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet @@ -1,14 +1,14 @@ //==== Request Fields -|=== -|Path|Type|Optional|Description + |=== + |Path|Type|Optional|Description {{#fields}} -|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} -|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} -|{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} -|{{#tableCellContent}}{{description}}{{/tableCellContent}} + |{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} + |{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} + |{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} + |{{#tableCellContent}}{{description}}{{/tableCellContent}} {{/fields}} -|=== \ No newline at end of file + |=== diff --git a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet b/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet index 457e34d4..46001498 100644 --- a/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet +++ b/src/test/resources/org/springframework/restdocs/templates/response-fields.snippet @@ -1,14 +1,14 @@ //==== Response Fields -|=== -|Path|Type|Optional|Description + |=== + |Path|Type|Optional|Description {{#fields}} -|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} -|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} -|{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} -|{{#tableCellContent}}{{description}}{{/tableCellContent}} + |{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} + |{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} + |{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} + |{{#tableCellContent}}{{description}}{{/tableCellContent}} {{/fields}} -|=== \ No newline at end of file + |===