後臺增長id資源權限控制。

前言

上週寫了一個角色權限管理,就是好比說有學生角色,老師角色,防止學生角色對老師角色的相關功能進行操做,不如說對於學生做業評分,若是學生能夠對本身做業評分就亂套了,因此須要加入權限控制接口只能教師操做。可是有一部分違規操做沒法控制,好比說A學生提交了B學生的做業。提交做業接口雖然控制只能學生訪問,可是沒法控制相同角色的用戶對本身的資源的操做。這裏做業就是本身的資源,別人不該該能夠隨意寫一份提交。這時候就須要用到id資源權限控制。spring

實現

這裏並非加一個註解那麼簡單了。大體思路就是操做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,面向切面編程。image.png
在上面的代碼中,咱們提交做業中保存做業到數據庫就是業務邏輯。而角色判斷是權限判斷,須要進行抽離。
這裏拿老項目的代碼學習一下。
首先加一個方法註解,
註解哪一個接口須要id資源權限控制。編程

/**
 * 擁有者權限認證.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OwnerSecured {
  Class<? extends OwnerAuthority> value();
}

再註解哪一個id是咱們須要認證的id,註解哪一個id使咱們拿來認證的id,有可能方法參數裏傳入不少個id。api

/**
 * 擁有者權限參數.
 *
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface OwnerKey {
}

而後寫一個切面,在方法執行前進行權限認證。app

/**
 * 資源權限校驗.
 */
@Aspect
@Component
public 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註解,沒法標識其關鍵字");
    }
  }
}

最後咱們在接口中運用ide

/**
   * 提交做業.
   *
   * @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);
}

相關認證代碼學習

@Override
public boolean checkAccess(Object key) {
    if (!(key instanceof Long)) {
      throw new ValidationException("接收的參數類型只能爲Long");
    }
    Long id = (Long) key;
    // 驗證是否爲當前學生
    return this.workRepository.existsByIdAndStudent(
        id, this.studentService.getCurrentStudent());
}
相關文章
相關標籤/搜索