背景
因項目需要做比較詳細的權限控制,大致瞭解了sprin sercurity、shiro兩個名氣比較大的權限管理框架。
得到的結論是:功能足夠強大,用戶、權限、認證等等,你想到的它全都有,沒想到的也有。但是我並不需要這麼多功能(項目的用戶認證已由其他方案完成,現只需要權限控制)。而且連號稱輕量級的shiro我也覺得太龐大了些。因此,決定根據項目需求自己實現一套極簡權限控制方案。
關於權限的數據庫表
表結構不是本文重點,簡單提一下。與權限相關的表:
- 用戶表
- 角色表
- 權限表(非必要,除非要做細粒度、可配置)
- 用戶角色表
- 角色權限表
基於接口的權限控制
如何實現控制
- 根據當前session提供的用戶信息,查詢到該用戶的角色
- 通過反射獲取到當前訪問的接口的註解,用註解裏面的值去與數據庫中該用戶角色的權限集合(可以緩存)比對,有權限則通過,否則拒絕。
爲什麼要基於controller
客戶端與服務端的交互是以http請求爲單位的,所以基於請求進行權限控制是比較合理的選擇。
實現
寫一個註解類
/**
* 權限控制註解。
* @author yangsheng
* @Date 2019/8/1 14:08
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
int[] allow() default RoleConstant.NORMAL_ROLE;
}
寫攔截器
這裏的攔截器也可以替換爲切面
/**
* @author
* @program onlineScoring
* @description 權限控制攔截器
* @create 2019-08-01 14:45
**/
@Slf4j
public class PermissionInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Permission permission = null;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
permission = handlerMethod.getMethod().getAnnotation(Permission.class);
}
if (permission == null) {
return true;
}
int allow = permission.allow();
// 這裏可以寫控制邏輯。建議建立專門的權限控制業務邏輯的類來處理。
switch (allow) {
case RoleConstant.SUPER_ADMIN_ROLE:
System.out.println("超級管理員");
default:
System.out.println("emmmm");
}
responseResult(response, new JsonResponse().error("您沒有操作權限"));
return super.preHandle(request, response, handler);
}
private void responseResult(HttpServletResponse response, JsonResponse result) {
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setStatus(200);
try {
response.getWriter().write(JSON.toJSONString(result));
} catch (IOException ex) {
log.error(ex.getMessage());
}
}
}
註冊攔截器
<mvc:interceptors>
<bean class="com.qckj.permission.intercepter.PermissionInterceptor"/>
</mvc:interceptors>
<mvc:annotation-driven />
注意,這裏需要寫<mvc:annotation-driven />,否則handler轉換成HandlerMethod的時候會拋異常,因爲此時的handler實際上是Controller的代理。
如果不寫<mvc:annotation-driven />,也可以這樣寫:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
使用
在Controller方法上面寫上@Permission註解,就完成工作了!
@Permission(allow = RoleConstant.ADMIN_ROLE)
@RequestMapping("/examination")
public void index() {
}
該方案的優缺點
優點
- 權限表結構簡單,數據庫只需要維護用戶-角色關係,可以不關心角色-權限的關係(這個關係已經交給註解維護)
- 實現非常簡單,無需寫過多的代碼或是接入龐大的相關框架
- 高效。角色的權限集合是直接通過聲明的註解獲取的,無需查詢數據庫或緩存。
缺點
- 因爲不關心角色-權限的關係,所以角色和權限的關係基本上是死的(通過註解綁定死了),不能通過頁面操作隨時變更角色的權限。
此方案更適合於中小型、用戶角色與功能權限的關係比較穩定、對性能要求高的系統。