前言
上周写了一个角色权限治理,就是比如说有学生角色,老师角色,避免学生角色对老师角色的相干性能进行操作,不如说对于学生作业评分,如果学生能够对本人作业评分就乱套了,所以须要退出权限管制接口只能老师操作。然而有一部分违规操作无法控制,比如说A学生提交了B学生的作业。提交作业接口尽管管制只能学生拜访,然而无法控制雷同角色的用户对本人的资源的操作。这里作业就是本人的资源,他人不应该能够随便写一份提交。这时候就须要用到id资源权限管制。
实现
这里并不是加一个注解那么简略了。大抵思路就是操作id资源时会传入id, 只有在批改前验证id对应资源对应所属用户是否是以后登录用户即可。那上边例子来说就是提交作业时验证id对应作业对应所属用户是否为以后登录用户。如果id对应作业对应所属用户为A,以后登录用户为B,就禁止其操作。
实现起来也非常简略。
public Work submit(Long id, Work work) { Work oldWork = this.getById(id); if (!oldWork.getStudent().getId().equals(this.studentService.getCurrentStudent().getId())) { throw new AccessDeniedException("无权更新其它学生的作业"); } ... return this.workRepository.save(oldWork);}
然而这并不符合规范,好的代码应该是其余操作与业务逻辑相抽离,这就用到了spring弱小的Aop,面向切面编程。
在下面的代码中,咱们提交作业中保留作业到数据库就是业务逻辑。而角色判断是权限判断,须要进行抽离。
这里拿老我的项目的代码学习一下。
首先加一个办法注解,
注解哪个接口须要id资源权限管制。
/** * 拥有者权限认证. */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface OwnerSecured { Class<? extends OwnerAuthority> value();}
再注解哪个id是咱们须要认证的id,注解哪个id使咱们拿来认证的id,有可能办法参数里传入很多个id。
/** * 拥有者权限参数. * */@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)public @interface OwnerKey {}
而后写一个切面,在办法执行前进行权限认证。
/** * 资源权限校验. */@Aspect@Componentpublic class OwnerSecuredAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); private final ApplicationContext applicationContext; public OwnerSecuredAspect(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } /** * 切入点. * * @param ownerSecured 注解 */ @Pointcut("@annotation(club.yunzhi.api.workReview.annotation.OwnerSecured) " + "&& @annotation(ownerSecured)") public void annotationPointCut(OwnerSecured ownerSecured) { } /** * 在切点前执行,权限不通过报403. * * @param joinPoint 切点 * @param ownerSecured 拥有者权限注解 */ @Before("annotationPointCut(ownerSecured)") public void before(JoinPoint joinPoint, OwnerSecured ownerSecured) { // 依据切点的@OwnerKey注解获取咱们判断所属用户的id Object paramKey = this.getOwnerKeyValueFromMethodParam(joinPoint); try { // 依据咱们在@OwnerSecured注解里传入的值获取相应的认证器 OwnerAuthority ownerAuthority = applicationContext.getBean(ownerSecured.value()); // 认证 if (!ownerAuthority.checkAccess(paramKey)) { throw new AccessDeniedException("您无权对该资源进行操作"); } } catch (BeansException beansException) { logger.error("未获取到类型" + ownerSecured.value().toString() + "的bean,请增加"); beansException.printStackTrace(); } } /** * 获取在参数中应用@OwnerKey注解的值. * * @param joinPoint 切点 * @return 参数值 */ private Object getOwnerKeyValueFromMethodParam(JoinPoint joinPoint) { Object result = null; boolean found = false; Object[] methodArgs = joinPoint.getArgs(); int numArgs = methodArgs.length; MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations(); for (int i = 0; i < numArgs; i++) { Annotation[] annotations = annotationMatrix[i]; for (Annotation annotation : annotations) { if (annotation.annotationType().equals(OwnerKey.class)) { if (!found) { result = methodArgs[i]; found = true; } else { this.logger.warn("找到多个OwnerKey注解,将以首个OwnerKey注解为主,非首注解将被疏忽"); } } } } if (result != null) { return result; } else { throw new RuntimeException("未在办法中找到OwnerKey注解,无奈标识其关键字"); } }}
最初咱们在接口中使用
/** * 提交作业. * * @param id * @param work * @return */ @PutMapping("{id}") @JsonView(SubmitJsonView.class) @OwnerSecured(WorkService.class) @Secured(YunzhiSecurityRole.ROLE_STUDENT) public Work submit(@OwnerKey @PathVariable Long id, @RequestBody Work work) { return this.workService.submit(id, work);}
相干认证代码
@Overridepublic boolean checkAccess(Object key) { if (!(key instanceof Long)) { throw new ValidationException("接管的参数类型只能为Long"); } Long id = (Long) key; // 验证是否为以后学生 return this.workRepository.existsByIdAndStudent( id, this.studentService.getCurrentStudent());}