背景
權限系統是根據系統設置的安全規則或者安全策略,用戶可以訪問而且只能訪問自己被授權的資源。
一般而言,企業內部一套成熟的權限系統,都是基於角色(Role)的訪問控制方法(RBAC – Role Based Access Control),即權限(Permission)與角色相關聯,用戶(User)通過成為適當角色的成員而得到這些角色的權限,權限包含資源(或者與操作組合方式相結合),最終實現權限控制的目的。
一言以蔽之,基於角色的訪問控制方法的訪問邏輯表達式為“Who對What(Which)進行How的操作”,它的由內到外的邏輯結構為權限->角色->用戶,即一個角色對應綁定多個權限,一個用戶對應綁定多個角色,這也是秦蒼基礎架構部對於公共權限服務實現的基本指導思想。
一.API權限定義、入庫和攔截
對於API權限,我們實行基於注解(Annotation)的掃描入庫和攔截,不需要業務服務自行在界面上錄入
1、權限定義
API權限以每個接口或者實現類中的方法作為權限資源,每個權限和微服務名(Service Name)掛鈎。
我們通過在業務服務的API上添加注解的方式,進行權限定義。基礎架構部會提供一個權限組件(Permission Component)Jar給業務服務部門,里面包含了自定義的注解,這樣的實現方式,對業務服務的影響非常小,增加權限機制只是在代碼層面加幾個注解而已。具體使用方式如下
對於一個普通的接口類,我們可以這樣定義:
1
2
3
4
5
6
7
8
|
public interface UserService {
boolean addUser(@UserId String userId, User user);
boolean deleteUser(@UserId String userId, User user);
}
|
對於通過Swagger方式暴露出去的API,我們可以這樣定義:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public interface UserService {
boolean addUser(@PathParam("userId") @UserId String userId, User user);
boolean deleteUser(@PathParam("userId") @UserId String userId, User user);
}
|
在上述簡短的代碼中,我們可以發現有三個自定義的注解,@Group、@Permission和@UserId。
- @Permission,即為每個API(接口方法)定義一個權限,要求有name(英文格式),label(中文格式)和description(權限描述)
- @Group,即定義的權限歸屬哪個權限組,考慮到一個接口中包含很多個API,接口數目又比較多,那么我們可以為每個接口下的所有方法歸為一個組。業務服務可自行定義權限組,也可以選擇不定義,那么會歸屬到默認預定義的權限組中
- l@UserId,即業務服務需要在他們的API上加入用戶ID的參數,當AOP切面攔截做權限驗證時候,用戶ID是需要傳入的必要參數
2、權限入庫和攔截
當API權限定義好以后,我們在權限組件里面加入掃描權限入庫和攔截的算法。采用Spring AutoProxy自動代理的框架來實現我們的掃描算法。
2.1 創建PermissionInterceptor.java繼承org.aopalliance.intercept.MethodInterceptor,步驟如下
- 實現Object invoke(MethodInvocation invocation)方法,獲取注解值
- 根據不同注解進行不同的切面攔截,實現對@Group,@Permission和@UserId三個注解的權限攔截邏輯
2.2 創建PermissionAutoProxy.java繼承Spring的org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator類,步驟如下
- 在構造方法里設置好Interceptor通用代理器(即實現了MethodInterceptor接口的攔截類PermissionInterceptor.java)
- shouldProxyTargetClass用來決定是接口代理,還是類代理。在權限定義的時候,其實我們還支持把注解加在實現類上,而不僅僅在接口上,這樣靈活運用注解放置的方式-
- getAdvicesAndAdvisorsForBean是最核心的方法,用來決定哪個類、哪個方法上的注解要被掃描入庫,也決定哪個類、哪個方法要被代理。如果我們做的更加通用一點,那么可以抽象出三個方法,供getAdvicesAndAdvisorsForBean調用
1
2
3
4
5
6
7
8
|
// 返回攔截類,攔截類必須實現MethodInterceptor接口,即PermissionInterceptor
protected abstract Class<? extends MethodInterceptor> getInterceptorClass();
// 返回接口或者類的方法名上的注解,如果接口或者類中方法名上存在該注解,即認為該接口或者類需要被代理
protected abstract Class<? extends Annotation> getMethodAnnotationClass();
// 掃描到接口或者類的方法名上的注解后,所要做的處理
protected abstract void methodAnnotationScanned(Class<?> targetClass);
|
2.3 創建PermissionScanListener.java實現Spring的org.springframework.context.ApplicationListener.ApplicationListener接口,步驟如下
- 在onApplicationEvent(ContextRefreshedEvent event)方法里實現入庫代碼
-
在微服務的Spring容器啟動的時候,將自動觸發權限數據入庫的事件
通過上述闡述,我們就實現了權限的掃描入庫和攔截
二、API權限所對應的角色(Role)管理
角色是一組API權限的匯總,每個角色也將和微服務名掛鈎。角色組的作用是為了匯總和管理眾多的角色
角色管理需要人工在界面上進行操作,角色管理分為角色組增刪改查,以及每個角色組下的角色增刪改查
三、 API權限所屬的角色和用戶(User)的綁定
權限不能直接和用戶綁定,必須通過角色作為中間橋梁進行關聯。那么我們要實現
- 角色與權限的綁定,即一個角色和多個權限的關聯
- 用戶與角色的綁定,即一個用戶和多個角色的關聯
四、權限系統驗證方式
1、API接入的驗證方式
通過遠程RPC方式的調用
1.1 掃描接入Permission組件的API Resource
1
2
3
4
5
6
7
|
com.omniprimeinc.commonservice.permission.annotations.config.Config.class })
public class Config {
}
|
1.2 通過API Resource去調用RPC接口獲取驗證結果
1
2
3
4
5
6
7
8
9
|
public interface AuthorizationResource {
Boolean authorize(@PathParam("userId") String userId, @PathParam("permissionName") String permissionName, @PathParam("serviceName") String serviceName);
}
|
2、Rest調用的驗證方式
http://host:port/authorization/authorize/{userId}/{permissionName}/{serviceName}
通過User ID、Permission Name(權限名,映射於對應的方法名)、Service Name(應用名)來判斷是否被授權,返回結果是true或者false
五、權限服務和用戶服務的整合
用戶服務即整合了Ldap系統的用戶和橋接業務用戶系統
權限服務接入用戶服務后,可以在權限授權頁面上選取相應的用戶進行權限授權
六、權限服務的安全控制
未來規划,服務之間的調用增加如下機制
- 黑/白IP名單機制。當A服務調用B服務的時候,B服務會實現維護一個黑/白IP列表,表示B服務只允許在某個IP網段的A服務才能有權限調用B服務
- 服務間約定的SecretKey。當A服務調用B服務的時候,兩個服務之間實現實現約定API訪問密鑰,此密鑰不能輕易泄密。這樣就規避了B服務被模擬Rest請求調用(例如通過PostMan調用)
- 服務的API簽名。當A服務調用B服務的時候,A服務需要獲得正確的B服務API的簽名,才有權限去調用
http://blog.springcloud.cn/sc/mfw-jq/