Spring實現(xiàn)擁有者權限驗證的方法示例
問題描述
在做權限驗證的時候,我們經(jīng)常會遇到這樣的情況:教師擁有多個學生,但是在處理學生信息的時候,教師只能操作自己班級的學生。所以,我們要做的就是,當教師嘗試處理別的班的學生的時候,拋出異常。
實體關系
用戶1:1教師,教師m:n班級,班級1:n學生

實現(xiàn)思路
以findById為例。因為從整體上看,用戶和學生是m:n的關系,所以在調(diào)用這個接口的時候,獲取該學生的所有用戶,然后跟當前登錄用戶進行對比,如果不在其中,拋出異常。
利用切面,我們可以在findById、update、delete方法上進行驗證。
注解
我們會在方法上添加注解,以表示對該方法進行權限驗證。
@Target(ElementType.METHOD) // 注解使用在方法上
@Retention(RetentionPolicy.RUNTIME) // 運行時生效
public @interface AuthorityAnnotation {
/**
* 倉庫名
*/
@Required
Class repository();
}
因為我們需要獲取出學生,但是并不限于學生,所以就要將倉庫repository作為一個參數(shù)傳入。
實體
上面我們說過,需要獲取學生中的用戶,所以我們可以在實體中定義一個方法,獲取所有有權限的用戶:getBelongUsers()
但是,我們知道,學生和用戶沒用直接的關系,而且為了復用,在對其他實體進行驗證的時候也能使用,可以考慮創(chuàng)建一個接口,讓需要驗證的實體去實現(xiàn)他。

這樣,我們可以在讓每個實體都集成這個接口,然后形成鏈式調(diào)用,這樣就解決了上面你的兩個問題。
public interface BaseEntity {
List<User> getBelongToUsers();
}
教師:
@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();
}
}
切面
有了實體后,我們就可以建立切面實現(xiàn)驗證功能了。
@Aspect
@Component
public class OwnerAuthorityAspect {
private static final Logger logger = LoggerFactory.getLogger(OwnerAuthorityAspect.class.getName());
/**
* 使用注解,并第一個參數(shù)為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轉(zhuǎn)換為CrudRepository");
CrudRepository<BaseEntity, Object> crudRepository = (CrudRepository<BaseEntity, Object>)object;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
該類實現(xiàn)了ApplicationContextAware接口,通過setApplicationContext函數(shù)獲取到了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轉(zhuǎn)換為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);
}
}
出現(xiàn)的問題
實現(xiàn)之后,進行單元測試的過程中出現(xiàn)了問題。
@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("發(fā)送更新請求");
this.mockMvc
.perform(put(baseUrl + "/" + id)
.cookie(this.cookie)
.content(jsonString)
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}

400的錯誤,說明參數(shù)錯誤,參數(shù)傳的是實體,看下傳了什么:

我們看到,這個字段并不是我們實體中的字段,但是為什么序列化的時候出現(xiàn)了這個字段呢?
原因是這樣的,我們在實體中定義了一個getBelongToUsers函數(shù),然后JSONobject在進行序列化的時候會根據(jù)實體中的getter方法,獲取get后面的為key,也就是將belongToUsers看做了字段。
所以就出現(xiàn)了上面?zhèn)鲗嶓w字段多出的情況,從而引發(fā)了400的錯誤。
解決
我們不想JSONobject在序列化的時候處理getBelongToUsers,就需要聲明一下,這里用到了注解:@JsonIgnore。這樣在序列化的時候就會忽略它。
@Entity
public class Student implements BaseEntity {
......
@JsonIgnore
@Override
public List<User> getBelongToUsers() {
return this.getKlass().getBelongToUsers();
}
}
修改后的學生實體如上,其他實現(xiàn)了getBelongToUsers方法的,都需要做相同處理。
總結(jié)
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Java線程創(chuàng)建靜態(tài)代理模式代碼實例
這篇文章主要介紹了Java線程創(chuàng)建靜態(tài)代理模式代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-11-11
解決spring boot創(chuàng)建項目遇到配置的問題
這篇文章主要介紹了解決spring boot創(chuàng)建項目遇到配置的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Java安全框架——Shiro的使用詳解(附springboot整合Shiro的demo)
這篇文章主要介紹了Java安全框架——Shiro的使用詳解,幫助大家更好的理解和學習使用Shiro,感興趣的朋友可以了解下2021-04-04

