基於shiro廣東11選5源碼下載的自定義註解擴展
如何通過邏輯進行頁面與api接口的關聯。
shiro的自身註解的用法。
如何編寫自定義註解。
如何通過邏輯進行頁面與api接口的關聯
在表與表的結構關系中,頁面和接口表最終都是與權限表進行的關聯(詳情請查看我的上一篇文章《權限設計的雜談》)。 權限實體圖 我們現在希望用另一種方案去替代他,實現一個低成本同時兼顧一定程度的權限控制。這裏我們引入兩個概念。業務模塊,操作類型。
業務模塊
概念:將系統中的業務模塊抽象成一種數據,我們可以用字符串的形式去表示,例如:角色管理對應是role-manage、用戶管理對應是user-manage等等。我們將系統中所存在的業務模塊通過“最小特權原則”進行劃分,最終形成一批可分配的數據。
使用原則:api接口和頁面以及功能從本質上來說,都和業務模塊有邏輯關系,於是,我們可以對api接口與頁面(以及功能點)進行邏輯匹配,來判斷頁面與接口的關系。
操作類型
概念:將系統中的所有的操作類型抽象成一種數據,我們也可以用字符串的形式去表示,例如:新增對應的是add、分配對應的是allot等等。我們將系統中所有的操作類型根據業務模塊通過“數據許可證”進行劃分,最終形成一批可分配的數據。
現在提出了這兩個概念,他們最終的實際的使用方式是什麽,我們先從以下幾個角度去思考一下。
數據庫中的頁面表或的api接口表中的數據就是真實有效嗎?
頁面或接口的實際使用,是以功能存在為前提,還是以數據庫表中的數據存在為前提。
權限結構中,“控制對象”的存儲只有數據庫這一種途徑嗎?
我們從結論出發來看這幾個問題,首先“控制對象”的存儲除了在數據庫中也可以代碼中,也可以在配置文件中,並不一定非得在數據庫;那麽接著回答第二個問題,當數據庫存在的接口信息,而服務端並沒有開發這個接口的時候,數據庫的信本身就有問題,亦或者,數據庫裏新增的接口必定是服務端上已經部署的接口才能生效;接著就是第一個問題,那麽數據庫中關於“控制對象”的表中的數據並不一定是真實有效的。所以我們可以得出以下的解決方案
我們可以在接口上用註解的形式補充“業務模塊”和“操作類型”的數據信息,這兩類信息都可以存於常量類中,
在數據庫添加創建頁面表結構和頁面功能表結構的時候,添加“業務模塊”和“操作類型”字段。
可以將“業務模塊”和“操作類型”的信息存於數據庫的字典表中。
模塊的新增或操作的新增,必定帶來了接口的新增,那麽就會帶來一次系統部署活動,這個運維成本是無法減少的,並不能通過表結構來減少。
業務模塊與頁面和接口的關系
但是這種方案僅適用於非強控制接口型的項目,在強控制型的接口項目仍然要將頁面與接口進行綁定,雖然這會帶來巨大的運維成本。另外也可以通過接口路由規則進行劃分,例如:/api/page/xxxx/(僅對頁面使用),/api/mobile/xxxxx(僅對移動端使用)將僅供頁面使用的接口進行分類,這類接口僅做認證不做授權,也可以達到目的。
shiro的自身註解的用法
通過一個理論上的思路認可之後,剩下的則是付諸技術上的實踐,我們這邊采用的是Apache Shiro的安全框架,在Spring Boot的環境下應用。簡要說明以下幾個shiro的註解。
註解名 作用
@RequiresAuthentication 作用於的類、方法、實例上。調用時,當前的subject是必須經過了認證的。
@RequiresGuest 作用於的類、方法、實例上。調用時,subject可以是guest狀態。
@RequiresPermissions 作用於的類、方法、實例上。調用時,需要判斷suject中是否包含當前接口中的Permission(權限信息)。
@RequiresRoles 作用於的類、方法、實例上。調用時,需要判斷subject中是否包含當前接口中的Role(角色信息)。
@RequiresUser 作用於的類、方法、實例上。調用時,需要判斷subject中是否當前應用中的用戶。
/**
- 1.當前接口需要經過"認證"過程
-
@return
*/
@RequestMapping(value = "/info",method = RequestMethod.GET)@RequiresAuthentication
br/>@RequiresAuthentication
return "恭喜你,拿到了參數信息";
}/**
- 2.1.當前接口需要經過權限校驗(需包含 角色的查詢 或 菜單的查詢)
-
@return
*/
@RequestMapping(value = "/info",method = RequestMethod.GET)@RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR)
br/>@RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR)
return "恭喜你,拿到了參數信息";
}/**
- 2.2.當前接口需要經過權限校驗(需包含 角色的查詢 與 菜單的查詢)
-
@return
*/
@RequestMapping(value = "/info",method = RequestMethod.GET)@RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR)
br/>@RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR)
return "恭喜你,拿到了參數信息";
}/**
- 3.1.當前接口需要經過角色校驗(需包含admin的角色)
-
@return
*/
@RequestMapping(value = "/info",method = RequestMethod.GET)@RequiresRoles(value={"admin"})
br/>@RequiresRoles(value={"admin"})
return "恭喜你,拿到了參數信息";
}/**
- 3.2.當前接口需要經過角色與權限的校驗(需包含admin的角色,以及角色的查詢 或 菜單的查詢)
- @return
*/
@RequestMapping(value = "/info",method = RequestMethod.GET)@RequiresRoles(value={"admin"})
br/>@RequiresRoles(value={"admin"})
public String test(){
return "恭喜你,拿到了參數信息";
}
在我們的實際使用過程中,實際上只需要使用@RequiresPermissions和@RequiresAuthentication就可以了這一個註解就可以了,在上一小節的結尾,我們采取了業務模塊與操作的結合方案來解耦頁面和api接口的關系,和apache Shiro的這種方式正好一致。但是@RequiresRoles這個我們盡可能不采用,因為角色的組合形式太多,角色名沒有辦法在接口中具象唯一化(很難指定接口歸某個角色調用,但是一定能知道接口歸屬於某些業務模塊的某些操作。)
現在我們來回顧一下整個運轉的流程。
shiro權限的驗證流程
如何編寫自定義註解
但是僅僅是擁有shiro中的這5個註解肯定是不夠使用的。在實際的使用過程中,根據需求,我們會在權限認證中加入我們自己特有的業務邏輯的,我們為了便捷則可以采用自定義註解的方式進行使用。這種方法不僅僅適用於Apache Shiro,很多其他的框架如:Hibernate Validator、SpringMVC、甚至我們可以寫一套校驗體系,在aop中去驗證權限,這都是沒問題的。所以自定義註解的作用很廣。但是在這裏,我僅僅基於shiro的來實現適用於它的自定義註解。
定義註解類
/**
- 用於認證的接口的註解,組合形式默認是“或”的關系*/
@Target({ElementType.METHOD})
br/>*/
@Target({ElementType.METHOD})
public @interface Auth {
/**- 業務模塊
- @return
*/
String[] module();
/** - 操作類型
*/
String[] action();
}
定義註解的處理類
/**
-
Auth註解的操作類
*/
public class AuthHandler extends AuthorizingAnnotationHandler {public AuthHandler() {
//寫入註解
super(Auth.class);
}@Override
public void assertAuthorized(Annotation a) throws AuthorizationException {
if (a instanceof Auth) {
Auth annotation = (Auth) a;
String[] module = annotation.module();
String[] action = annotation.action();
//1.獲取當前主題
Subject subject = this.getSubject();
//2.驗證是否包含當前接口的權限有一個通過則通過
boolean hasAtLeastOnePermission = false;
for(String m:module){
for(String ac:action){
//使用hutool的字符串工具類
String permission = StrFormatter.format("{}:{}",m,ac);
if(subject.isPermitted(permission)){
hasAtLeastOnePermission=true;
break;
}
}
}
if(!hasAtLeastOnePermission){
throw new AuthorizationException("沒有訪問此接口的權限");
}}
}
}
定義shiro攔截處理類
/** -
攔截器
*/
public class AuthMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {public AuthMethodInterceptor() {
super(new AuthHandler());
}public AuthMethodInterceptor(AnnotationResolver resolver) {
super(new AuthHandler(), resolver);
}@Override
public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
// 驗證權限
try {
((AuthHandler) this.getHandler()).assertAuthorized(getAnnotation(mi));
} catch (AuthorizationException ae) {
if (ae.getCause() == null) {
ae.initCause(new AuthorizationException("當前的方法沒有通過鑒權: " + mi.getMethod()));
}
throw ae;
}
}
}
定義shiro的aop切面類
/**
- shiro的aop切面
*/
public class AuthAopInterceptor extends AopAllianceAnnotationsAuthorizingMethodInterceptor {
public AuthAopInterceptor() {
super();
// 添加自定義的註解攔截器
this.methodInterceptors.add(new AuthMethodInterceptor(new SpringAnnotationResolver()));
}
}
定義shiro的自定義註解啟動類
/**
-
啟動自定義註解
*/
public class ShiroAdvisor extends AuthorizationAttributeSourceAdvisor {public ShiroAdvisor() {
// 這裏可以添加多個
setAdvice(new AuthAopInterceptor());
}@SuppressWarnings({"unchecked"})@Override
br/>@Override
Method m = method;
if (targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
return this.isFrameAnnotation(m);
} catch (NoSuchMethodException ignored) {} } return super.matches(method, targetClass);
}
private boolean isFrameAnnotation(Method method) {
return null != AnnotationUtils.findAnnotation(method, Auth.class);
}
}
總體的思路順序:定義註解類(定義業務可使用的變量)->定義註解處理類(通過註解中的變量做業務邏輯處理)->定義註解的攔截器->定義aop的切面類->最後定義shiro的自定義註解啟用類。其他的自定義的註解的編寫思路和這個也是類似的。
基於shiro廣東11選5源碼下載的自定義註解擴展