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...

相關文章
相關標籤/搜索