SpringCloud 微服務數據權限控制的實現
舉個例子:
有一批業務員跟進全國的銷售訂單。他們被按城市進行劃分,一個業務員跟進3個城市的訂單,為瞭保護公司的業務數據不能被所有人都掌握,故每個業務員隻能看到自己負責城市的訂單數據。所以從系統來講每個業務員都有訪問銷售訂單的功能,然後再需要配置每個業務員負責的城市,以此對訂單數據進行篩選。
要實現此功能有很多方法,如果系統中多個地方都需要類似的需求,那我們就可以將其提出來做成一個通用的功能。這裡我介紹一個相對簡單的解決方案,以供參考。
一、 整體架構
數據權限為作一個註解的形式掛在每一個需要數據權限控制的Controller上,由於和具體的程序邏輯有關故有一定的入侵性,且需要數據庫配合使用。
二、 實現流程
1.瀏覽器傳帶查詢權限范圍參數訪問Controller,如cities
POST http://127.0.0.1:8000/order/query accept: */* Content-Type: application/json token: 1e2b2298-8274-4599-a26f-a799167cc82f {"cities":["cq","cd","bj"],"userName":"string"}
2.通過註解攔截權限范圍參數,並根據預授權范圍比較,回寫在授權范圍內的權限范圍參數
cities = ["cq","cd"]
3.通過參數傳遞到DAO層,在SQL語句中拼裝出查詢條件,實現數據的過濾
select * from order where city in ('cq','cd')
三、 實現步驟
1. 註解實現
註解的完整代碼,請詳見源代碼
1)創建註解
@Retention(value = RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @Documented public @interface ScopeAuth { String token() default "AUTH_TOKEN"; String scope() default ""; String[] scopes() default {}; }
此註解為運行時RetentionPolicy.RUNTIME
作用在方法上ElementType.METHOD
的
token
:獲取識別唯一用戶的標識,與用戶數據權限存儲有關
scope
,scopes
:預請求的數據權限范圍
2) AOP實現註解
public class ScopeAuthAdvice { @Around("@annotation(scopeAuth)") public Object before(ProceedingJoinPoint thisJoinPoint, ScopeAuth scopeAuth) throws Throwable { // ... 省略過程 // 獲取token String authToken = getToken(args, scopeAuth.token(), methodSignature.getMethod()); // 回寫范圍參數 setScope(scopeAuth.scope(), methodSignature, args, authToken); return thisJoinPoint.proceed(); } /** * 設置范圍 */ private void setScope(String scope, MethodSignature methodSignature, Object[] args, String authToken) { // 獲取請求范圍 Set<String> requestScope = getRequestScope(args, scope, methodSignature.getMethod()); ScopeAuthAdapter adapter = new ScopeAuthAdapter(supplier); // 已授權范圍 Set<String> authorizedScope = adapter.identifyPermissionScope(authToken, requestScope); // 回寫新范圍 setRequestScope(args, scope, authorizedScope, methodSignature.getMethod()); } /** * 回寫請求范圍 */ private void setRequestScope(Object[] args, String scopeName, Collection<String> scopeValues, Method method) { // 解析 SPEL 表達式 if (scopeName.indexOf(SPEL_FLAG) == 0) { ParseSPEL.setMethodValue(scopeName, scopeValues, method, args); } } }
此為演示代碼省略瞭過程,主要功能為通過token拿到預先授權的數據范圍,再與本次請求的范圍做交集,最後回寫回原參數。
過程中用到瞭較多的SPEL表達式,用於計算表達式結果,具體請參考ParseSPEL文件
3)權限范圍交集計算
public class ScopeAuthAdapter { private final AuthQuerySupplier supplier; public ScopeAuthAdapter(AuthQuerySupplier supplier) { this.supplier = supplier; } /** * 驗證權限范圍 * @param token * @param requestScope * @return */ public Set<String> identifyPermissionScope(String token, Set<String> requestScope) { Set<String> authorizeScope = supplier.queryScope(token); String ALL_SCOPE = "AUTH_ALL"; String USER_ALL = "USER_ALL"; if (authorizeScope == null) { return null; } if (authorizeScope.contains(ALL_SCOPE)) { // 如果是全開放則返回請求范圍 return requestScope; } if (requestScope == null) { return null; } if (requestScope.contains(USER_ALL)){ // 所有授權的范圍 return authorizeScope; } // 移除不同的元素 requestScope.retainAll(authorizeScope); return requestScope; } }
此處為瞭方便設置,有兩個關鍵字范圍
AUTH_ALL
:預設所有范圍,全開放的意思,為數據庫預先設置值,請求傳什麼值都通過USER_ALL
:請求所有授權的范圍,請求時傳此值則會以數據庫預設值為準
4) spring.factories自動導入類配置
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector=\ fun.barryhome.cloud.annotation.ScopeAuthAdvice
如果註解功能是單獨項目存在,在使用時有可能會存在找不到引入文件的問題,可通過此配置文件自動載入需要初始化的類
2. 註解使用
@ScopeAuth(scopes = {"#orderDTO.cities"}, token = "#request.getHeader(\"X-User-Name\")") @PostMapping(value = "/query") public String query(@RequestBody OrderDTO orderDTO, HttpServletRequest request) { return Arrays.toString(orderDTO.getCities()); }
在需要使用數據權限的controller方法上增加@ScopeAuth註解
scopes = {"#orderDTO.cities"}
:表示取輸入參數orderDTO的cities值,這裡是表達式必須加#
實際開發過程中,需要將orderDTO.getCities()帶入後續邏輯中,在DAO層將此拼裝在SQL中,以實現數據過濾功能
3. 實現AuthStoreSupplier
AuthStoreSupplier
接口為數據權限的存儲接口,與AuthQuerySupplier配合使用,可按實際情況實現
此接口為非必要接口,可由數據庫或Redis存儲(推薦),一般在登錄的同時保存在Redis中
4. 實現AuthQuerySupplier
AuthQuerySupplier
接口為數據權限查詢接口,可按存儲方法進行查詢,推薦使用Redis
@Component public class RedisAuthQuerySupplier implements AuthQuerySupplier { @Autowired private RedisTemplate<String, String> redisTemplate; /** * 查詢范圍 */ @Override public Set<String> queryScope(String key) { String AUTH_USER_KEY = "auth:logic:user:%s"; String redisKey = String.format(AUTH_USER_KEY, key); List<String> range = redisTemplate.opsForList().range(redisKey, 0, -1); if (range != null) { return new HashSet<>(range); } else { return null; } } }
在分佈式結構裡,也可將此實現提出到權限模塊,采用遠程調用方式,進一步解耦
5. 開啟數據權限
@EnableScopeAuth @EnableDiscoveryClient @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
四、 綜述
至此數據權限功能就實現瞭。在微服務器架構中為瞭實現功能的復用,將註解的創建和AuthQuerySupplier的實現提取到公共模塊中,那麼在具體的使用模塊就簡單得多瞭。隻需增加@ScopeAuth
註解,配置好查詢方法就可以使用。
五、源代碼
文中代碼由於篇幅原因有一定省略並不是完整邏輯,如有興趣請Fork源代碼
到此這篇關於SpringCloud 微服務數據權限控制的實現的文章就介紹到這瞭,更多相關SpringCloud內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- swagger如何返回map字段註釋
- Java8的Optional如何幹掉空指針(示例詳解)
- java構建OAuth2授權服務器
- Spring IOC中的Bean對象用法
- SpringBoot集成JWT實現登陸驗證的方法詳解