Spring实现拥有者权限验证

问题描述

在作权限验证的时候,咱们常常会遇到这样的状况:教师拥有多个学生,可是在处理学生信息的时候,教师只能操做本身班级的学生。因此,咱们要作的就是,当教师尝试处理别的班的学生的时候,抛出异常。json

实体关系

用户1:1教师,教师m:n班级,班级1:n学生segmentfault

clipboard.png

实现思路

findById为例。由于从总体上看,用户学生m:n的关系,因此在调用这个接口的时候,获取该学生的全部用户,而后跟当前登陆用户进行对比,若是不在其中,抛出异常。cookie

利用切面,咱们能够在findByIdupdatedelete方法上进行验证。app

注解

咱们会在方法上添加注解,以表示对该方法进行权限验证。ide

@Target(ElementType.METHOD)         // 注解使用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
public @interface AuthorityAnnotation {
    /**
     * 仓库名
     */
    @Required
    Class repository();
}

由于咱们须要获取出学生,可是并不限于学生,因此就要将仓库repository做为一个参数传入。函数

实体

上面咱们说过,须要获取学生中的用户,因此咱们能够在实体中定义一个方法,获取全部有权限的用户:getBelongUsers()单元测试

可是,咱们知道,学生和用户没用直接的关系,并且为了复用,在对其余实体进行验证的时候也能使用,能够考虑建立一个接口,让须要验证的实体去实现他。测试

clipboard.png

这样,咱们能够在让每一个实体都集成这个接口,而后造成链式调用,这样就解决了上面你的两个问题。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());
}

clipboard.png

400的错误,说明参数错误,参数传的是实体,看下传了什么:

clipboard.png

咱们看到,这个字段并非咱们实体中的字段,可是为何序列化的时候出现了这个字段呢?

缘由是这样的,咱们在实体中定义了一个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...

相关文章
相关标签/搜索