我正在编写简单的spring boot rest应用程序,我在创建DELETE端点时遇到了很少的架构问题。我已经尝试了解决这个问题的方法,我需要建议哪个更好,为什么。
首先,我有一个带有@ControllerAdvice
的类,其中包含异常处理程序:
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
@ExceptionHandler(ResourceNotAccessException.class)
public ResponseEntity<String> handleResourceNotAccessException(ResourceNotAccessException ex) {
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(ex.getMessage());
}
所以我想创建端点:
@DeleteMapping("/{offerId}")
public void deleteOffer(@PathVariable Long offerId, Authentication authentication) {
// here code to delete offer.
}
所以主要假设是记录的用户可以删除哪个id等于offeId并且他是该要约的所有者(我可以使用代码检查所有权:offer.getOwner().getUsername().equals(authentication.getName());
)或者具有Role.ADMIN
我找到了三种方法:
控制器中的所有逻辑,只有简单的方法
@DeleteMapping("/{offerId}")
public void deleteOffer(@PathVariable Long offerId, Authentication authentication) {
AbstractOffer offer = offerService.findOfferById(offerId).orElseThrow(() -> new ResourceNotFoundException("Offer with given Id doesn't exists."));
if (!offerService.isOwner(offer,authentication)) throw new ResourceNotAccessException("Cannot delete offer if you aren't owner");
offerService.deleteOffer(offer);
}
删除此资源的逻辑放入PermissionEvaluator
或使用SPeL和注释验证@PreAuthorize(...)
在这种情况下,我没有抛出任何异常而且没有调用ControllerAdvice
`
将所有逻辑放入服务类,并从控制器只调用此方法。
@Service
public class OfferService {
.
.
.
public void deleteOfferIfOwner(Long offerId,Authentication authentication) {
AbstractOffer offer = findOfferById(offerId).orElseThrow(() -> new ResourceNotFoundException("Offer with given Id doesn't exists."));
if(!isOwner(offer,authentication)) throw new ResourceNotAccessException("Cannot delete offer if you aren't owner");
deleteOffer(offer);
}
.
.
}
然后:
@DeleteMapping("/{offerId}")
public void deleteOffer(@PathVariable Long offerId, Authentication authentication) {
offerService.deleteOfferIfOwner(offerId,authentication);
}
在我看来,第一个解决方案很好,因为异常连接到控制器并转换为响应,但我不确定我应该在控制器中放入多少逻辑,以及在这种情况下如何验证身份验证是否为管理员。
第二个 - 不知道该说些什么,对我来说它太复杂了,我不得不将服务类注入验证类或注释(不确定它是不是很好的做法)但它应该有用。
第三个 - 我将所有逻辑投入使用并抛出2个异常,它们是RUNTIME EXCEPTION并连接到控制器和controllerAdvice,因为这些异常取决于调用端点的结果。
如果能够正确解决这个问题以及有关代码和项目架构的任何反馈,我会很高兴。 目前大多数代码都可以在这里找到: Github
答案 0 :(得分:0)
我在我的应用程序中处理此问题的方式与您的第一种和第三种方式相似,但略有不同。
更具体地说,我使用定制类ResourceAccessHelper
,并将所有验证用户对资源的访问权限的代码放入其中,或者在用户不符合要求的条件时引发异常。
/**
* Helper class for checking whether a user has certain access rights to a resource during an HTTP
* request.
*
* @author Thanos Psaridis
*/
@Component
public class ResourceAccessHelper {
private final RolesHelper rolesHelper;
/**
* Constructor accepting {@link RolesHelper}
*
* @param rolesHelper Helper class for checking user Roles
*/
@Autowired
public ResourceAccessHelper(RolesHelper rolesHelper) {
this.rolesHelper = rolesHelper;
}
/**
* Getter for {@link RolesHelper}
*
* @return {@link RolesHelper}
*/
public RolesHelper getRolesHelper() {
return rolesHelper;
}
/**
* Verifies that the given parameters are true by Checking user access by course id
*
* @param userEmail user email
* @param courseId the course id the user may have access to
* @throws AccessDeniedException in case user does not have access to this course id
*/
public final void checkAccess(@NotNull final String userEmail, long courseId)
throws AccessDeniedException {
checkCourseDaoAccess(userEmail, courseId);
}
/**
* Custom logic for checking whether a user can pass certain constraints given a courseId.
*
* @param userEmail user email
* @param courseId the course id the user may have access to
* @throws AccessDeniedException in case the access requirements are not met.
*/
private void checkCourseDaoAccess(@NotNull String userEmail, long courseId)
throws AccessDeniedException {
if (isSuperAdmin(userEmail)) return;
final AppUser appUser = rolesHelper.getUser(userEmail);
if (isAdmin(userEmail)) {
final List<Course> tenantCourses = appUser.getTenant().getCourses();
final Optional<Course> optional =
tenantCourses.stream().filter(course -> course.getId().equals(courseId)).findAny();
if (optional.isPresent()) return;
}
if (isStudent(userEmail)
&& appUser
.getEnrolments()
.stream()
.anyMatch(enrolment -> enrolment.getCourse().getId().equals(courseId))) return;
throw new AccessDeniedException("ACCESS DENIED");
}
}
然后,我将调用上述类,然后调用您的服务类,该服务类通过将资源包装在try-catch块中来删除控制器中的资源。
try {
// check access beforehand
resourceAccessHelper.checkAccess(principalObject.getEmail(), givenCourseId);
//proceed with the deletion of course.
courseService.deleteCourse(givenCourseId)
} catch (AccessDeniedException ignored) {
//handle your exception here by sending a 404 response error or something similar
}