在作权限验证的时候,咱们常常会遇到这样的状况:教师拥有多个学生,可是在处理学生信息的时候,教师只能操做本身班级的学生。因此,咱们要作的就是,当教师尝试处理别的班的学生的时候,抛出异常。json
用户1:1教师
,教师m:n
班级,班级1:n
学生segmentfault
以findById
为例。由于从总体上看,用户
和学生
是m:n
的关系,因此在调用这个接口的时候,获取该学生的全部用户
,而后跟当前登陆用户
进行对比,若是不在其中,抛出异常。cookie
利用切面,咱们能够在findById
、update
、delete
方法上进行验证。app
咱们会在方法上添加注解,以表示对该方法进行权限验证。ide
@Target(ElementType.METHOD) // 注解使用在方法上 @Retention(RetentionPolicy.RUNTIME) // 运行时生效 public @interface AuthorityAnnotation { /** * 仓库名 */ @Required Class repository(); }
由于咱们须要获取出学生,可是并不限于学生,因此就要将仓库repository
做为一个参数传入。函数
上面咱们说过,须要获取学生中的用户,因此咱们能够在实体中定义一个方法,获取全部有权限的用户:getBelongUsers()
单元测试
可是,咱们知道,学生和用户没用直接的关系,并且为了复用,在对其余实体进行验证的时候也能使用,能够考虑建立一个接口,让须要验证的实体去实现他。测试
这样,咱们能够在让每一个实体都集成这个接口,而后造成链式调用,这样就解决了上面你的两个问题。ui
public interface BaseEntity { List<User> getBelongToUsers(); }
教师:this
@Entity public class Teacher implements YunzhiEntity, BaseEntity { ... @Override public List<User> getBelongToUsers() { List<User> userList = new ArrayList<>(); userList.add(this.getUser()); return userList; } }
班级:
@Entity public class Klass implements BaseEntity { ... @Override public List<User> getBelongToUsers() { List<User> userList = new ArrayList<>(); for (Teacher teacher: this.getTeacherList()) { userList.addAll(teacher.getBelongToUsers()); } return userList; } }
学生:
@Entity public class Student implements BaseEntity { ... @Override public List<User> getBelongToUsers() { return this.getKlass().getBelongToUsers(); } }
有了实体后,咱们就能够创建切面实现验证功能了。
@Aspect @Component public class OwnerAuthorityAspect { private static final Logger logger = LoggerFactory.getLogger(OwnerAuthorityAspect.class.getName()); /** * 使用注解,并第一个参数为id */ @Pointcut("@annotation(com.yunzhiclub.alice.annotation.AuthorityAnnotation) && args(id,..) && @annotation(authorityAnnotation)") public void doAccessCheck(Long id, AuthorityAnnotation authorityAnnotation) { } @Before("doAccessCheck(id, authorityAnnotation)") public void before(Long id, AuthorityAnnotation authorityAnnotation) { }
首先,咱们要获取到待操做对象
。可是在获取对象以前,咱们必须获取到repository
。
这里咱们利用applicationContext
来获取仓库bean
,而后再利用获取到的bean,生成repository对象。
@Aspect @Component public class OwnerAuthorityAspect implements ApplicationContextAware { private ApplicationContext applicationContext = null; // 初始化上下文 ...... @Before("doAccessCheck(id, authorityAnnotation)") public void before(Long id, AuthorityAnnotation authorityAnnotation) { logger.debug("获取注解上的repository, 并经过applicationContext来获取bean"); Class<?> repositoryClass = authorityAnnotation.repository(); Object object = applicationContext.getBean(repositoryClass); logger.debug("将Bean转换为CrudRepository"); CrudRepository<BaseEntity, Object> crudRepository = (CrudRepository<BaseEntity, Object>)object; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
该类实现了ApplicationContextAware
接口,经过setApplicationContext
函数获取到了applicationContext
。
接下来,就是利用repository
获取对象,而后获取他的所属用户,再与当前登陆用户进行比较。
@Before("doAccessCheck(id, authorityAnnotation)") public void before(Long id, AuthorityAnnotation authorityAnnotation) { logger.debug("获取注解上的repository, 并经过applicationContext来获取bean"); Class<?> repositoryClass = authorityAnnotation.repository(); Object object = applicationContext.getBean(repositoryClass); logger.debug("将Bean转换为CrudRepository"); CrudRepository<BaseEntity, Object> crudRepository = (CrudRepository<BaseEntity, Object>)object; logger.debug("获取实体对象"); Optional<BaseEntity> baseEntityOptional = crudRepository.findById(id); if(!baseEntityOptional.isPresent()) { throw new RuntimeException("对不起,未找到相关的记录"); } BaseEntity baseEntity = baseEntityOptional.get(); logger.debug("获取登陆用户以及拥有者,并进行比对"); List<User> belongToTUsers = baseEntity.getBelongToUsers(); User currentLoginUser = userService.getCurrentLoginUser(); Boolean havePermission = false; if (currentLoginUser != null && belongToTUsers.size() != 0) { for (User user: belongToTUsers) { if (user.getId().equals(currentLoginUser.getId())) { havePermission = true; break; } } if (!havePermission) { throw new RuntimeException("权限不容许"); } } }
在控制器的方法上使用注解:@AuthorityAnnotation
,传入repository。
@RestController @RequestMapping("/student") public class StudentController { private final StudentService studentService; // 学生 @Autowired public StudentController(StudentService studentService) { this.studentService = studentService; } /** * 经过id获取学生 * * @param id * @return */ @AuthorityAnnotation(repository = StudentRepository.class) @GetMapping("/{id}") @JsonView(StudentJsonView.get.class) public Student findById(@PathVariable Long id) { return studentService.findById(id); } }
实现以后,进行单元测试的过程当中出现了问题。
@Test public void update() throws Exception { logger.info("获取一个保存学生"); Student student = studentService.getOneSaveStudent(); Long id = student.getId(); logger.info("获取一个更新学生"); Student newStudent = studentService.getOneUnSaveStudent(); String jsonString = JSONObject.toJSONString(newStudent); logger.info("发送更新请求"); this.mockMvc .perform(put(baseUrl + "/" + id) .cookie(this.cookie) .content(jsonString) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); }
400的错误,说明参数错误,参数传的是实体,看下传了什么:
咱们看到,这个字段并非咱们实体中的字段,可是为何序列化的时候出现了这个字段呢?
缘由是这样的,咱们在实体中定义了一个getBelongToUsers
函数,而后JSONobject
在进行序列化的时候会根据实体中的getter
方法,获取get
后面的为key
,也就是将belongToUsers
看作了字段。
因此就出现了上面传实体字段多出的状况,从而引起了400的错误。
咱们不想JSONobject
在序列化的时候处理getBelongToUsers
,就须要声明一下,这里用到了注解:@JsonIgnore
。这样在序列化的时候就会忽略它。
@Entity public class Student implements BaseEntity { ...... @JsonIgnore @Override public List<User> getBelongToUsers() { return this.getKlass().getBelongToUsers(); } }
修改后的学生实体如上,其余实现了getBelongToUsers
方法的,都须要作相同处理。
在解决这个问题的时候,开始就是本身埋头写,不少细节都没有处理好。而后偶然google到了潘老师以前写过的一篇文章,就对前面写的进行了完善。虽然本身解决问题的过程仍是有不少收获的,可是若是开始直接参考这篇文章,会省很多事。
实际上是这样的,咱们写博客,一方面是让本身有所提高,另外一方面也是为了团队中的其余成员少走一些弯路。看来此次我是没有好好利用资源了。
相关参考:
https://my.oschina.net/dashan...
https://segmentfault.com/a/11...