前言

上周写了一个角色权限治理,就是比如说有学生角色,老师角色,避免学生角色对老师角色的相干性能进行操作,不如说对于学生作业评分,如果学生能够对本人作业评分就乱套了,所以须要退出权限管制接口只能老师操作。然而有一部分违规操作无法控制,比如说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());}