共计 3443 个字符,预计需要花费 9 分钟才能阅读完成。
前言
上周写了一个角色权限治理,就是比如说有学生角色,老师角色,避免学生角色对老师角色的相干性能进行操作,不如说对于学生作业评分,如果学生能够对本人作业评分就乱套了,所以须要退出权限管制接口只能老师操作。然而有一部分违规操作无法控制,比如说 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 | |
@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 注解,无奈标识其关键字"); | |
} | |
} | |
} |
最初咱们在接口中使用
/** | |
* 提交作业. | |
* | |
* @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()); | |
} |
正文完
发表至: springboot
2021-05-15