Spring實(shí)現(xiàn)擁有者權(quán)限驗(yàn)證的方法示例
問(wèn)題描述
在做權(quán)限驗(yàn)證的時(shí)候,我們經(jīng)常會(huì)遇到這樣的情況:教師擁有多個(gè)學(xué)生,但是在處理學(xué)生信息的時(shí)候,教師只能操作自己班級(jí)的學(xué)生。所以,我們要做的就是,當(dāng)教師嘗試處理別的班的學(xué)生的時(shí)候,拋出異常。
實(shí)體關(guān)系
用戶1:1教師
,教師m:n
班級(jí),班級(jí)1:n
學(xué)生
實(shí)現(xiàn)思路
以findById
為例。因?yàn)閺恼w上看,用戶
和學(xué)生
是m:n
的關(guān)系,所以在調(diào)用這個(gè)接口的時(shí)候,獲取該學(xué)生的所有用戶
,然后跟當(dāng)前登錄用戶
進(jìn)行對(duì)比,如果不在其中,拋出異常。
利用切面,我們可以在findById
、update
、delete
方法上進(jìn)行驗(yàn)證。
注解
我們會(huì)在方法上添加注解,以表示對(duì)該方法進(jìn)行權(quán)限驗(yàn)證。
@Target(ElementType.METHOD) // 注解使用在方法上 @Retention(RetentionPolicy.RUNTIME) // 運(yùn)行時(shí)生效 public @interface AuthorityAnnotation { /** * 倉(cāng)庫(kù)名 */ @Required Class repository(); }
因?yàn)槲覀冃枰@取出學(xué)生,但是并不限于學(xué)生,所以就要將倉(cāng)庫(kù)repository
作為一個(gè)參數(shù)傳入。
實(shí)體
上面我們說(shuō)過(guò),需要獲取學(xué)生中的用戶,所以我們可以在實(shí)體中定義一個(gè)方法,獲取所有有權(quán)限的用戶:getBelongUsers()
但是,我們知道,學(xué)生和用戶沒(méi)用直接的關(guān)系,而且為了復(fù)用,在對(duì)其他實(shí)體進(jìn)行驗(yàn)證的時(shí)候也能使用,可以考慮創(chuàng)建一個(gè)接口,讓需要驗(yàn)證的實(shí)體去實(shí)現(xiàn)他。
這樣,我們可以在讓每個(gè)實(shí)體都集成這個(gè)接口,然后形成鏈?zhǔn)秸{(diào)用,這樣就解決了上面你的兩個(gè)問(wèn)題。
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; } }
班級(jí):
@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; } }
學(xué)生:
@Entity public class Student implements BaseEntity { ... @Override public List<User> getBelongToUsers() { return this.getKlass().getBelongToUsers(); } }
切面
有了實(shí)體后,我們就可以建立切面實(shí)現(xiàn)驗(yàn)證功能了。
@Aspect @Component public class OwnerAuthorityAspect { private static final Logger logger = LoggerFactory.getLogger(OwnerAuthorityAspect.class.getName()); /** * 使用注解,并第一個(gè)參數(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) { }
首先,我們要獲取到待操作對(duì)象
。但是在獲取對(duì)象之前,我們必須獲取到repository
。
這里我們利用applicationContext
來(lái)獲取倉(cāng)庫(kù)bean
,然后再利用獲取到的bean,生成repository對(duì)象。
@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, 并通過(guò)applicationContext來(lái)獲取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; } }
該類(lèi)實(shí)現(xiàn)了ApplicationContextAware
接口,通過(guò)setApplicationContext
函數(shù)獲取到了applicationContext
。
接下來(lái),就是利用repository
獲取對(duì)象,然后獲取他的所屬用戶,再與當(dāng)前登錄用戶進(jìn)行比較。
@Before("doAccessCheck(id, authorityAnnotation)") public void before(Long id, AuthorityAnnotation authorityAnnotation) { logger.debug("獲取注解上的repository, 并通過(guò)applicationContext來(lái)獲取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("獲取實(shí)體對(duì)象"); Optional<BaseEntity> baseEntityOptional = crudRepository.findById(id); if(!baseEntityOptional.isPresent()) { throw new RuntimeException("對(duì)不起,未找到相關(guān)的記錄"); } BaseEntity baseEntity = baseEntityOptional.get(); logger.debug("獲取登錄用戶以及擁有者,并進(jìn)行比對(duì)"); 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("權(quán)限不允許"); } } }
使用
在控制器的方法上使用注解:@AuthorityAnnotation
,傳入repository。
@RestController @RequestMapping("/student") public class StudentController { private final StudentService studentService; // 學(xué)生 @Autowired public StudentController(StudentService studentService) { this.studentService = studentService; } /** * 通過(guò)id獲取學(xué)生 * * @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)的問(wèn)題
實(shí)現(xiàn)之后,進(jìn)行單元測(cè)試的過(guò)程中出現(xiàn)了問(wèn)題。
@Test public void update() throws Exception { logger.info("獲取一個(gè)保存學(xué)生"); Student student = studentService.getOneSaveStudent(); Long id = student.getId(); logger.info("獲取一個(gè)更新學(xué)生"); Student newStudent = studentService.getOneUnSaveStudent(); String jsonString = JSONObject.toJSONString(newStudent); logger.info("發(fā)送更新請(qǐng)求"); this.mockMvc .perform(put(baseUrl + "/" + id) .cookie(this.cookie) .content(jsonString) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk()); }
400的錯(cuò)誤,說(shuō)明參數(shù)錯(cuò)誤,參數(shù)傳的是實(shí)體,看下傳了什么:
我們看到,這個(gè)字段并不是我們實(shí)體中的字段,但是為什么序列化的時(shí)候出現(xiàn)了這個(gè)字段呢?
原因是這樣的,我們?cè)趯?shí)體中定義了一個(gè)getBelongToUsers
函數(shù),然后JSONobject
在進(jìn)行序列化的時(shí)候會(huì)根據(jù)實(shí)體中的getter
方法,獲取get
后面的為key
,也就是將belongToUsers
看做了字段。
所以就出現(xiàn)了上面?zhèn)鲗?shí)體字段多出的情況,從而引發(fā)了400的錯(cuò)誤。
解決
我們不想JSONobject
在序列化的時(shí)候處理getBelongToUsers
,就需要聲明一下,這里用到了注解:@JsonIgnore
。這樣在序列化的時(shí)候就會(huì)忽略它。
@Entity public class Student implements BaseEntity { ...... @JsonIgnore @Override public List<User> getBelongToUsers() { return this.getKlass().getBelongToUsers(); } }
修改后的學(xué)生實(shí)體如上,其他實(shí)現(xiàn)了getBelongToUsers
方法的,都需要做相同處理。
總結(jié)
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解SpringBoot如何實(shí)現(xiàn)緩存預(yù)熱
緩存預(yù)熱是指在 Spring Boot 項(xiàng)目啟動(dòng)時(shí),預(yù)先將數(shù)據(jù)加載到緩存系統(tǒng)(如 Redis)中的一種機(jī)制,下面我們就來(lái)看看SpringBoot是如何實(shí)現(xiàn)緩存預(yù)熱的吧2024-01-01java學(xué)習(xí)指南之字符串與正則表達(dá)式
在日常Java后端開(kāi)發(fā)過(guò)程中,免不了對(duì)數(shù)據(jù)字段的解析,自然就少不了對(duì)字符串的操作,這其中就包含了正則表達(dá)式這一塊的內(nèi)容,下面這篇文章主要給大家介紹了關(guān)于java學(xué)習(xí)指南之字符串與正則表達(dá)式的相關(guān)資料,需要的朋友可以參考下2023-05-05Java線程創(chuàng)建靜態(tài)代理模式代碼實(shí)例
這篇文章主要介紹了Java線程創(chuàng)建靜態(tài)代理模式代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11java?-jar/-cp啟動(dòng)添加外部的依賴包方式
這篇文章主要介紹了java?-jar/-cp啟動(dòng)添加外部的依賴包方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01解決spring boot創(chuàng)建項(xiàng)目遇到配置的問(wèn)題
這篇文章主要介紹了解決spring boot創(chuàng)建項(xiàng)目遇到配置的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Java安全框架——Shiro的使用詳解(附springboot整合Shiro的demo)
這篇文章主要介紹了Java安全框架——Shiro的使用詳解,幫助大家更好的理解和學(xué)習(xí)使用Shiro,感興趣的朋友可以了解下2021-04-04