JavaWeb許可權設計原理
每個系統都有許可權設計,本篇主要將初始的許可權設計的原理,不依賴任何框架,以直觀的角度剖析web的許可權設計。
許可權設計的原理知識
什麼是許可權管理
只要有使用者參與的系統一般都有許可權管理,許可權管理實現對使用者訪問系統的控制。按照安全規則或者安全策略控制使用者可以訪問而且只能訪問自己被授權的資源。
許可權管理包括使用者認證和使用者授權兩部分。
使用者認證
概念
使用者認證-----使用者訪問系統,系統需要驗證使用者身份的合法性。常用的驗證方法:1.使用者名稱密碼驗證。2.指紋驗證。3.證照驗證。系統驗證使用者身份合法,使用者才可以訪問資源。
使用者認證的流程
關鍵物件
subject :主體,理解為使用者,可能是程式,都要去訪問系統的資源,系統需要對subject進行身份驗證。
principal :身份資訊,通常是唯一的,一個主題可以有多個身份資訊,但是隻能有一個主身份資訊(primary principal)。
credential :憑證資訊,可以是密碼,證照,指紋等。
主體在進行身份認證時需要提供身份資訊和憑證資訊。
使用者授權
概念
使用者授權,簡單理解為訪問控制,在使用者認證通過後,系統對使用者訪問資源進行控制,當使用者具有資源的訪問許可權方可訪問。
授權流程
關鍵物件
授權的過程可以理解為 who 對 what(which)進行how操作
who : 主體,即subject,subject在認證通過後,系統可以進行訪問控制。
what(which): 資源(Resource),subject必須具備資源訪問許可權才可以訪問改許可權。資源包括很多方面,比如:使用者列表頁面,商品修改選單等。資源分為資源型別和資源例項:
例如系統的使用者資訊就是資源型別,相當於java類。
系統中id為1的使用者就是資源例項,相當於java物件。
how : 許可權(permission),針對資源的許可權或許可,subject必須具有permission方可訪問資源,如何訪問/操作需要定義permission,許可權比如:使用者新增,修改,刪除等。
許可權模型
主體(賬號、密碼)
資源(資源名稱、訪問地址)
許可權(許可權名稱、資源id)
角色(角色名稱)
角色和許可權的關係,使用者和角色的關係。
如下圖:
通常企業開發中將資源和許可權合併成一張許可權表,如下:
資源(資源名稱,訪問地址)
許可權(許可權名稱,資源id)
合併為:
許可權(許可權名稱,資源名稱,資源訪問地址)
上圖是許可權管理的通用模型,當然在實際開發中也可以根據自己的需要修改。
分配許可權
使用者需要分配相應的許可權才可以訪問相應的資源。許可權是對資源的操作許可。
通常給使用者分配資源許可權需要將許可權資訊持久化,比如儲存到關聯式資料庫中。
把使用者資訊,許可權管理,角色資訊寫入資料庫中。
基於角色的訪問控制
RBAC(role based access control),基於角色的訪問控制。
比如:
系統角色包括 :部門經理、總經理。。(角色針對使用者來劃分)
系統程式碼中實現:
//如果該user是部門經理則可以訪問if中的程式碼
if(user.hasRole('部門經理')){
//系統資源內容
//使用者報表檢視
}
問題:
角色針對人劃分的,人作為使用者在系統中屬於活動內容,如果該 角色可以訪問的資源出現變更,需要修改你的程式碼了,比如:需要變更為部門經理和總經理都可以進行使用者報表檢視,程式碼改為:
if(user.hasRole('部門經理') || user.hasRole('總經理') ){
//系統資源內容
//使用者報表檢視
}
基於角色的訪問控制是不利於系統維護(可擴充套件性不強)。
基於資源的訪問控制
RBAC(Resource based access control),基於資源的訪問控制。
資源在系統中是不變的,比如資源有:類中的方法,頁面中的按鈕等。
對資源的訪問需要具有permission許可權,程式碼可以寫成:
if(user.hasPermission ('使用者報表檢視(許可權識別符號)')){
//系統資源內容
//使用者報表檢視
}
上邊的方法就可以解決使用者角色變更不用修改上邊許可權控制的程式碼。
如果需要變更許可權只需要在分配許可權模組去操作,給部門經理或總經理增或刪除許可權。
建議使用基於資源的訪問控制實現許可權管理。
許可權管理解決方案
粗粒度和細粒度許可權
粗粒度許可權管理,對資源型別的許可權管理。資源型別比如:選單,url連線,使用者新增頁面,使用者資訊,類方法,頁面按鈕。
粗粒度許可權管理比如:超級管理員可以訪問使用者新增頁面,使用者資訊等全部頁面。
部門管理員可以訪問使用者資訊頁面,包括頁面中的按鈕。
細粒度許可權管理,對資源例項的許可權管理。資源例項就是資源型別的具體化,比如:行政部門的員工,id為1的使用者的檢視頁面等。
細粒度許可權管理就是資料級別的許可權管理
細粒度許可權管理比如:部門經理只可以訪問本部門的員工資訊,使用者只可以看到自己的選單,大區經理只能檢視本轄區的銷售訂單。。
粗粒度和細粒度例子:
系統有一個使用者列表查詢頁面,對使用者列表查詢分許可權,如果粗顆粒管理,張三和李四都有使用者列表查詢的許可權,張三和李四都可以訪問使用者列表查詢。
進一步進行細顆粒管理,張三(行政部)和李四(開發部)只可以查詢自己本部門的使用者資訊。張三隻能檢視行政部 的使用者資訊,李四隻能檢視開發部門的使用者資訊。
如何實現粗粒度和細粒度許可權管理
如何實現粗粒度許可權管理?
粗粒度許可權管理比較容易將許可權管理的程式碼抽取出來在系統架構級別統一處理。比如:通過springmvc的攔截器實現授權。
如何實現細粒度許可權管理?
對細粒度許可權管理在資料級別是沒有共性可言,針對細粒度許可權管理就是系統業務邏輯的一部分,如果在業務層去處理相對比較簡單,如果將細粒度許可權管理統一在系統架構級別去抽取,比較困難,即使抽取的功能可能也存在擴充套件不強。
建議細粒度許可權管理在業務層去控制。
比如:部門經理只查詢本部門員工資訊,在service介面提供一個部門id的引數,controller中根據當前使用者的資訊得到該 使用者屬於哪個部門,呼叫service時將部門id傳入service,實現該使用者只查詢本部門的員工。
基於url攔截的方式實現
基於url攔截的方式實現在實際開發中比較常用的一種方式。
對於web系統,通過filter過慮器實現url攔截,也可以springmvc的攔截器實現基於url的攔截。
基於許可權框架的方式實現
對於粗粒度許可權管理,建議使用優秀許可權管理框架來實現,節省開發成功,提高開發效率。
shiro就是一個優秀許可權管理框架。
基於url的許可權管理
基於url的許可權管理流程
搭建環境
資料庫
mysql資料庫中建立表:使用者表、角色表、許可權表(實質上是許可權和資源的結合 )、使用者角色表、角色許可權表。
建立好的表如下:
shiro_sql_table.sql:
CREATE TABLE `sys_permission` (
`id` bigint(20) NOT NULL COMMENT '主鍵',
`name` varchar(128) NOT NULL COMMENT '資源名稱',
`type` varchar(32) NOT NULL COMMENT '資源型別:menu,button,',
`url` varchar(128) DEFAULT NULL COMMENT '訪問url地址',
`percode` varchar(128) DEFAULT NULL COMMENT '許可權程式碼字串',
`parentid` bigint(20) DEFAULT NULL COMMENT '父結點id',
`parentids` varchar(128) DEFAULT NULL COMMENT '父結點id列表串',
`sortstring` varchar(128) DEFAULT NULL COMMENT '排序號',
`available` char(1) DEFAULT NULL COMMENT '是否可用,1:可用,0不可用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Table structure for table `sys_role` */
CREATE TABLE `sys_role` (
`id` varchar(36) NOT NULL,
`name` varchar(128) NOT NULL,
`available` char(1) DEFAULT NULL COMMENT '是否可用,1:可用,0不可用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Table structure for table `sys_role_permission` */
CREATE TABLE `sys_role_permission` (
`id` varchar(36) NOT NULL,
`sys_role_id` varchar(32) NOT NULL COMMENT '角色id',
`sys_permission_id` varchar(32) NOT NULL COMMENT '許可權id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Table structure for table `sys_user` */
CREATE TABLE `sys_user` (
`id` varchar(36) NOT NULL COMMENT '主鍵',
`usercode` varchar(32) NOT NULL COMMENT '賬號',
`username` varchar(64) NOT NULL COMMENT '姓名',
`password` varchar(32) NOT NULL COMMENT '密碼',
`salt` varchar(64) DEFAULT NULL COMMENT '鹽',
`locked` char(1) DEFAULT NULL COMMENT '賬號是否鎖定,1:鎖定,0未鎖定',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Table structure for table `sys_user_role` */
CREATE TABLE `sys_user_role` (
`id` varchar(36) NOT NULL,
`sys_user_id` varchar(32) NOT NULL,
`sys_role_id` varchar(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
shiro_sql_table_data.sql
insert into `sys_permission`(`id`,`name`,`type`,`url`,`percode`,`parentid`,`parentids`,`sortstring`,`available`) values
(1,'許可權','','',NULL,0,'0/','0','1'),(11,'商品管理','menu','/item/queryItem.action',NULL,1,'0/1/','1.','1'),
(12,'商品新增','permission','/item/add.action','item:create',11,'0/1/11/','','1'),
(13,'商品修改','permission','/item/editItem.action','item:update',11,'0/1/11/','','1'),
(14,'商品刪除','permission','','item:delete',11,'0/1/11/','','1'),
(15,'商品查詢','permission','/item/queryItem.action','item:query',11,'0/1/15/',NULL,'1'),
(21,'使用者管理','menu','/user/query.action','user:query',1,'0/1/','2.','1'),
(22,'使用者新增','permission','','user:create',21,'0/1/21/','','1'),
(23,'使用者修改','permission','','user:update',21,'0/1/21/','','1'),
(24,'使用者刪除','permission','','user:delete',21,'0/1/21/','','1');
/*Data for the table `sys_role` */
insert into `sys_role`(`id`,`name`,`available`) values
('ebc8a441-c6f9-11e4-b137-0adc305c3f28','商品管理員','1'),
('ebc9d647-c6f9-11e4-b137-0adc305c3f28','使用者管理員','1');
/*Data for the table `sys_role_permission` */
insert into `sys_role_permission`(`id`,`sys_role_id`,`sys_permission_id`) values
('ebc8a441-c6f9-11e4-b137-0adc305c3f21','ebc8a441-c6f9-11e4-b137-0adc305c','12'),
('ebc8a441-c6f9-11e4-b137-0adc305c3f22','ebc8a441-c6f9-11e4-b137-0adc305c','11'),
('ebc8a441-c6f9-11e4-b137-0adc305c3f24','ebc9d647-c6f9-11e4-b137-0adc305c','21'),
('ebc8a441-c6f9-11e4-b137-0adc305c3f25','ebc8a441-c6f9-11e4-b137-0adc305c','15'),
('ebc9d647-c6f9-11e4-b137-0adc305c3f23','ebc9d647-c6f9-11e4-b137-0adc305c','22'),
('ebc9d647-c6f9-11e4-b137-0adc305c3f26','ebc8a441-c6f9-11e4-b137-0adc305c','13');
/*Data for the table `sys_user` */
insert into `sys_user`(`id`,`usercode`,`username`,`password`,`salt`,`locked`) values
('lisi','lisi','李四','bf07fd8bbc73b6f70b8319f2ebb87483','uiwueylm','0'),
('zhangsan','zhangsan','張三','cb571f7bd7a6f73ab004a70322b963d5','eteokues','0');
/*Data for the table `sys_user_role` */
insert into `sys_user_role`(`id`,`sys_user_id`,`sys_role_id`) values
('ebc8a441-c6f9-11e4-b137-0adc305c3f28','zhangsan','ebc8a441-c6f9-11e4-b137-0adc305c'),
('ebc9d647-c6f9-11e4-b137-0adc305c3f28','lisi','ebc9d647-c6f9-11e4-b137-0adc305c');
整個工程如下:
系統登陸
系統登入相當於使用者身份認證,使用者登入成功,要在Session中記錄使用者的身份資訊。
操作流程:
使用者進入登入頁面。
輸入使用者名稱和密碼進行登陸。
進行使用者名稱和密碼校驗。
如果校驗通過,在Session中記錄使用者身份資訊。
使用者身份資訊
建立專門類用於記錄使用者身份資訊。
/**
* 使用者身份資訊,存入Session 由於Tomcat正常關閉時會將Session序列化的本地硬碟上,所以實現Serializable介面
* @author xushu
*
*/
public class ActiveUser implements Serializable {
private String userid; //使用者id(主鍵)
private String usercode; // 使用者賬號
private String username; // 使用者姓名
....
....
}
mapper
mapper介面:根據使用者賬號查詢使用者(sys_user)資訊 (使用逆向工程生成許可權相關的PO類和mapper介面)
如下所示:
service(進行使用者名稱和密碼校驗)
介面功能:根據使用者的身份和密碼進行認證,如果認證通過,返回使用者身份資訊。
認證過程:
根據使用者身份(賬號)查詢資料庫,如果查詢不到 則丟擲使用者不存在
對輸入的密碼和資料庫密碼進行比對,如果一致,認證通過。
新建許可權管理Service介面 新增身份認證方法
/**
* 認證授權服務介面
* @author liuxun
*
*/
public interface SysService {
//根據使用者的身份和密碼進行認證,如果認證通過,返回使用者身份資訊
public ActiveUser authenticat(String usercode,String password) throws Exception;
//根據使用者賬號查詢使用者資訊
public SysUser findSysUserByUserCode(String userCode) throws Exception;
......
}
方法實現:
public class SysServiceImpl implements SysService {
@Autowired
private SysUserMapper sysUserMapper;
public ActiveUser authenticat(String usercode, String password) throws Exception {
/**
* 認證過程: 根據使用者身份(賬號)查詢資料庫,如果查詢不到則使用者不存在
* 對輸入的密碼和資料庫密碼進行比對,如果一致則認證通過
*/
// 根據使用者賬號查詢資料庫
SysUser sysUser = this.findSysUserByUserCode(usercode);
if (sysUser == null) {
// 丟擲異常
throw new CustomException("使用者賬號不存在");
}
// 資料庫密碼(MD5加密後的密碼)
String password_db = sysUser.getPassword();
// 對輸入的密碼和資料庫密碼進行比對,如果一致,認證通過
// 對頁面輸入的密碼進行MD5加密
String password_input_md5 = new MD5().getMD5ofStr(password);
if (!password_db.equalsIgnoreCase(password_input_md5)) {
//丟擲異常
throw new CustomException("使用者名稱或密碼錯誤");
}
//得到使用者id
String userid = sysUser.getId();
//認證通過,返回使用者身份資訊
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(userid);
activeUser.setUsercode(usercode);
activeUser.setUsername(sysUser.getUsername());
return activeUser;
}
public SysUser findSysUserByUserCode(String userCode) throws Exception {
SysUserExample sysUserExample = new SysUserExample();
SysUserExample.Criteria criteria = sysUserExample.createCriteria();
criteria.andUsercodeEqualTo(userCode);
List<SysUser> list = sysUserMapper.selectByExample(sysUserExample);
if (list != null && list.size() > 0) {
return list.get(0);
}
return null;
}
......
}
配置Service,往類Service中使用@Autowire 需要註冊Service 註冊有兩種方法(註解或配置檔案),在架構時沒有配置掃描Service 需要在配置檔案中註冊Service
<!-- 認證和授權的Service -->
<bean id="sysService" class="liuxun.ssm.service.impl.SysServiceImpl"></bean>
controller(記錄Session)
//使用者登入提交方法
@RequestMapping("/login")
public String login(HttpSession session,String randomcode,String usercode,String password) throws Exception{
// 校驗驗證碼,防止惡性攻擊
// 從Session中獲取正確的驗證碼
String validateCode = (String) session.getAttribute("validateCode");
//輸入的驗證碼和Session中的驗證碼進行對比
if (!randomcode.equalsIgnoreCase(validateCode)) {
//丟擲異常
throw new CustomException("驗證碼輸入錯誤");
}
//呼叫Service校驗使用者賬號和密碼的正確性
ActiveUser activeUser = sysService.authenticat(usercode, password);
//如果Service校驗通過,將使用者身份記錄到Session
session.setAttribute("activeUser", activeUser);
//重定向到商品查詢頁面
return "redirect:/first.action";
}
使用者認證攔截器
anonymousURL.properties配置匿名URL
配置可以匿名訪問的URL。這個意思是,還沒有登陸就能訪問的連線,一般的後臺管理系統就是登陸註冊連結。
編寫身份認真攔截器
//用於使用者認證校驗、使用者許可權校驗
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//得到請求的url
String url = request.getRequestURI();
//判斷是否是公開地址
//實際開發中需要將公開地址配置在配置檔案中
//從配置檔案中取出可以匿名訪問的URL
List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");
for (String open_url : open_urls) {
if (url.indexOf(open_url)>=0) {
//如果是公開地址 則放行
return true;
}
}
//判斷使用者身份在Session中是否存在
HttpSession session = request.getSession();
ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
//如果使用者身份在session中存在則放行
if (activeUser!=null) {
return true;
}
//執行到這裡攔截,跳轉到登入頁面,使用者進行身份認證
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
//如果返回false表示攔截器不繼續執行handler,如果返回true表示放行
return false;
}
配置認證攔截器
<!-- 攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 使用者認證攔截 -->
<mvc:mapping path="/**"/>
<bean class="xushu.ssm.controller.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
使用者授權
commonURL.properties配置公用訪問地址
在此配置檔案中配置公用訪問地址,公用訪問地址只需要通過使用者認證,不需要對公用訪問地址分配許可權即可訪問。這個意思是,只要登陸進去了,不管什麼使用者都可以進行訪問的連結
獲取使用者許可權範圍的選單
思路:
在使用者認證時,認證通過,根據使用者id從資料庫獲取使用者許可權範圍內的選單,將選單的集合儲存在Session中。
編輯儲存使用者身份資訊的類ActiveUser 如下所示:
public class ActiveUser implements Serializable {
private String userid; //使用者id(主鍵)
private String usercode; // 使用者賬號
private String username; // 使用者姓名
private List<SysPermission> menus; //選單
//......setter和getter方法
}
自定義許可權Mapper
因為使用逆向工程生成的Mapper是不建議去修改的 因為它的程式碼聯絡非常緊密,一旦修改錯誤 就會牽一髮而動全身。所以需要自定義一個許可權的Mapper(SysPermissionMapperCustom)
在SysPermissionMapperCustom.xml中新增根據使用者id查詢使用者許可權的選單
<!-- 根據使用者id查詢選單 -->
<select id="findMenuListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission">
SELECT
*
FROM
sys_permission
WHERE TYPE = 'menu'
AND id IN
(SELECT
sys_permission_id
FROM
sys_role_permission
WHERE sys_role_id IN
(SELECT
sys_role_id
FROM
sys_user_role
WHERE sys_user_id = #{userid}))
</select>
在SysPermissionMapperCustom.java介面中新增對應的方法
public interface SysPermissionMapperCustom {
//根據使用者id查詢選單
public List<SysPermission> findMenuListByUserId(String userid) throws Exception;
.......
}
在許可權Service介面中新增對應的方法 在實現中注入SysPermissionMapperCustom
SysServiceImpl.java中新增如下內容
@Override
public List<SysPermission> findMenuListByUserId(String userid) throws Exception {
return sysPermissionMapperCustom.findMenuListByUserId(userid);
}
獲取使用者許可權範圍的URL
思路:
在使用者認證時,認證通過後,根據使用者id從資料庫中獲取使用者許可權範圍的URL,將URL的集合儲存在Session中。
修改ActiveUser 新增URL的許可權集合
public class ActiveUser implements Serializable {
private String userid; //使用者id(主鍵)
private String usercode; // 使用者賬號
private String username; // 使用者姓名
private List<SysPermission> menus; //選單
private List<SysPermission> permissions; //許可權
//...setter和getter方法
}
在SysPermissionMapperCustom.xml中新增根據使用者id查詢使用者許可權的URL
<!-- 根據使用者id查詢URL -->
<select id="findPermissionListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission">
SELECT
*
FROM
sys_permission
WHERE TYPE = 'permission'
AND id IN
(SELECT
sys_permission_id
FROM
sys_role_permission
WHERE sys_role_id IN
(SELECT
sys_role_id
FROM
sys_user_role
WHERE sys_user_id = #{userid}))
</select>
在SysPermissionMapperCustom.java介面中新增對應的方法
//根據使用者id查詢許可權URL
public List<SysPermission> findPermissionListByUserId(String userid) throws Exception;
SysServiceImpl.java中新增如下內容
@Override
public List<SysPermission> findPermissionListByUserId(String userid) throws Exception {
return sysPermissionMapperCustom.findPermissionListByUserId(userid);
}
使用者認證通過後取出選單和URL放入Session
修改許可權SysServiceImpl中使用者認證方法的程式碼
//得到使用者id
String userid = sysUser.getId();
//根據使用者id查詢選單
List<SysPermission> menus = this.findMenuListByUserId(userid);
//根據使用者id查詢許可權url
List<SysPermission> permissions = this.findPermissionListByUserId(userid);
//認證通過,返回使用者身份資訊
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(userid);
activeUser.setUsercode(usercode);
activeUser.setUsername(sysUser.getUsername());
//放入許可權範圍的選單和url
activeUser.setMenus(menus);
activeUser.setPermissions(permissions);
選單動態顯示
<c:if test="${activeUser.menus!=null }">
<ul>
<c:forEach items="${activeUser.menus }" var="menu">
<li><div>
<a title="${menu.name }" ref="1_1" href="#"
rel="${baseurl }/${menu.url }" icon="icon-log"><span
class="icon icon-log"> </span><span class="nav"><a href=javascript:addTab('${menu.name }','${baseurl }/${menu.url }')>${menu.name }</a></span></a>
</div></li>
</c:forEach>
</ul>
</c:if>
授權攔截器
public class PermissionInterceptor implements HandlerInterceptor{
//在執行handler之前執行的
//用於使用者認證校驗、使用者許可權校驗
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//得到請求的url
String url = request.getRequestURI();
//判斷是否是公開地址
//實際開發中需要將公開地址配置在配置檔案中
//從配置檔案中取出可以匿名訪問的URL
List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");
for (String open_url : open_urls) {
if (url.indexOf(open_url)>=0) {
//如果是公開地址 則放行
return true;
}
}
//從配置檔案中獲取公用訪問url
List<String> common_urls = ResourcesUtil.getKeyList("commonURL");
//遍歷公用地址 如果是公開地址則放行
for (String common_url : common_urls) {
if (url.indexOf(common_url)>0) {
//如果是公開,則放行
return true;
}
}
//判斷使用者身份在Session中是否存在
HttpSession session = request.getSession();
ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
//從Session中取出許可權範圍的URL
List<SysPermission> permissions = activeUser.getPermissions();
for (SysPermission sysPermission : permissions) {
//許可權url
String permission_url = sysPermission.getUrl();
if (url.indexOf(permission_url)>0) {
return true;
}
}
//執行到這裡攔截,跳轉到無權訪問的提示頁面
request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response);
//如果返回false表示攔截器不繼續執行handler,如果返回true表示放行
return false;
}
......
}
配置授權攔截器
注意:要將授權攔截器配置在使用者認證攔截器的下邊,這是因為SpringMVC攔截器的放行方法是順序執行的,如果是Struts的話則正好相反。
<!-- 攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 使用者認證攔截 -->
<mvc:mapping path="/**"/>
<bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<!-- 資源攔截 -->
<mvc:mapping path="/**"/>
<bean class="liuxun.ssm.controller.interceptor.PermissionInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
關鍵程式碼如下
PO類ActiveUser.java 存放使用者身份和許可權資訊的類
package liuxun.ssm.po;
import java.io.Serializable;
import java.util.List;
/**
* 使用者身份資訊,存入Session 由於Tomcat正常關閉時會將Session序列化的本地硬碟上,所以實現Serializable介面
* @author liuxun
*
*/
public class ActiveUser implements Serializable {
private static final long serialVersionUID = 1L;
private String userid; //使用者id(主鍵)
private String usercode; // 使用者賬號
private String username; // 使用者姓名
private List<SysPermission> menus; //選單
private List<SysPermission> permissions; //許可權
// 提供對應setter和getter方法
......
}
自定義許可權的Mapper
SysPermissionMapperCustom.java
package liuxun.ssm.mapper;
import java.util.List;
import liuxun.ssm.po.SysPermission;
import liuxun.ssm.po.SysPermissionExample;
import org.apache.ibatis.annotations.Param;
/**
* 許可權mapper
* @author liuxun
*
*/
public interface SysPermissionMapperCustom {
//根據使用者id查詢選單
public List<SysPermission> findMenuListByUserId(String userid) throws Exception;
//根據使用者id查詢許可權URL
public List<SysPermission> findPermissionListByUserId(String userid) throws Exception;
}
SysPermissionMapperCustom.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="liuxun.ssm.mapper.SysPermissionMapperCustom">
<!-- 根據使用者id查詢選單 -->
<select id="findMenuListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission">
SELECT
*
FROM
sys_permission
WHERE TYPE = 'menu'
AND id IN
(SELECT
sys_permission_id
FROM
sys_role_permission
WHERE sys_role_id IN
(SELECT
sys_role_id
FROM
sys_user_role
WHERE sys_user_id = #{userid}))
</select>
<!-- 根據使用者id查詢URL -->
<select id="findPermissionListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission">
SELECT
*
FROM
sys_permission
WHERE TYPE = 'permission'
AND id IN
(SELECT
sys_permission_id
FROM
sys_role_permission
WHERE sys_role_id IN
(SELECT
sys_role_id
FROM
sys_user_role
WHERE sys_user_id = #{userid}))
</select>
</mapper>
自定義許可權的Service介面以及實現類
SysService.java
package liuxun.ssm.service;
import java.util.List;
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.po.SysPermission;
import liuxun.ssm.po.SysUser;
/**
* 認證授權服務介面
* @author liuxun
*
*/
public interface SysService {
//根據使用者的身份和密碼進行認證,如果認證通過,返回使用者身份資訊
public ActiveUser authenticat(String usercode,String password) throws Exception;
//根據使用者賬號查詢使用者資訊
public SysUser findSysUserByUserCode(String userCode) throws Exception;
//根據使用者id查詢許可權範圍內的選單
public List<SysPermission> findMenuListByUserId(String userid) throws Exception;
//根據使用者id查詢許可權範圍內的url
public List<SysPermission> findPermissionListByUserId(String userid) throws Exception;
}
SysServiceImpl.java
package liuxun.ssm.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import liuxun.ssm.exception.CustomException;
import liuxun.ssm.mapper.SysPermissionMapperCustom;
import liuxun.ssm.mapper.SysUserMapper;
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.po.SysPermission;
import liuxun.ssm.po.SysUser;
import liuxun.ssm.po.SysUserExample;
import liuxun.ssm.service.SysService;
import liuxun.ssm.util.MD5;
public class SysServiceImpl implements SysService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysPermissionMapperCustom sysPermissionMapperCustom;
public ActiveUser authenticat(String usercode, String password) throws Exception {
/**
* 認證過程: 根據使用者身份(賬號)查詢資料庫,如果查詢不到則使用者不存在
* 對輸入的密碼和資料庫密碼進行比對,如果一致則認證通過
*/
// 根據使用者賬號查詢資料庫
SysUser sysUser = this.findSysUserByUserCode(usercode);
if (sysUser == null) {
// 丟擲異常
throw new CustomException("使用者賬號不存在");
}
// 資料庫密碼(MD5加密後的密碼)
String password_db = sysUser.getPassword();
// 對輸入的密碼和資料庫密碼進行比對,如果一致,認證通過
// 對頁面輸入的密碼進行MD5加密
String password_input_md5 = new MD5().getMD5ofStr(password);
if (!password_db.equalsIgnoreCase(password_input_md5)) {
//丟擲異常
throw new CustomException("使用者名稱或密碼錯誤");
}
//得到使用者id
String userid = sysUser.getId();
//根據使用者id查詢選單
List<SysPermission> menus = this.findMenuListByUserId(userid);
//根據使用者id查詢許可權url
List<SysPermission> permissions = this.findPermissionListByUserId(userid);
//認證通過,返回使用者身份資訊
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(userid);
activeUser.setUsercode(usercode);
activeUser.setUsername(sysUser.getUsername());
//放入許可權範圍的選單和url
activeUser.setMenus(menus);
activeUser.setPermissions(permissions);
return activeUser;
}
public SysUser findSysUserByUserCode(String userCode) throws Exception {
SysUserExample sysUserExample = new SysUserExample();
SysUserExample.Criteria criteria = sysUserExample.createCriteria();
criteria.andUsercodeEqualTo(userCode);
List<SysUser> list = sysUserMapper.selectByExample(sysUserExample);
if (list != null && list.size() > 0) {
return list.get(0);
}
return null;
}
@Override
public List<SysPermission> findMenuListByUserId(String userid) throws Exception {
return sysPermissionMapperCustom.findMenuListByUserId(userid);
}
@Override
public List<SysPermission> findPermissionListByUserId(String userid) throws Exception {
return sysPermissionMapperCustom.findPermissionListByUserId(userid);
}
}
登入控制器
package liuxun.ssm.controller;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import liuxun.ssm.exception.CustomException;
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.service.SysService;
/**
* 登入和退出
* @author liuxun
*
*/
@Controller
public class LoginController {
@Autowired
private SysService sysService;
//使用者登入提交方法
@RequestMapping("/login")
public String login(HttpSession session,String randomcode,String usercode,String password) throws Exception{
// 校驗驗證碼,防止惡性攻擊
// 從Session中獲取正確的驗證碼
String validateCode = (String) session.getAttribute("validateCode");
//輸入的驗證碼和Session中的驗證碼進行對比
if (!randomcode.equalsIgnoreCase(validateCode)) {
//丟擲異常
throw new CustomException("驗證碼輸入錯誤");
}
//呼叫Service校驗使用者賬號和密碼的正確性
ActiveUser activeUser = sysService.authenticat(usercode, password);
//如果Service校驗通過,將使用者身份記錄到Session
session.setAttribute("activeUser", activeUser);
//重定向到商品查詢頁面
return "redirect:/first.action";
}
//使用者退出
@RequestMapping("/logout")
public String logout(HttpSession session) throws Exception{
//session失效
session.invalidate();
//重定向到商品查詢頁面
return "redirect:/first.action";
}
}
身份認證攔截器LoginInterceptor.java
package liuxun.ssm.controller.interceptor;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.util.ResourcesUtil;
/**
* 測試攔截器1
* @author liuxun
*
*/
public class LoginInterceptor implements HandlerInterceptor{
//在執行handler之前執行的
//用於使用者認證校驗、使用者許可權校驗
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//得到請求的url
String url = request.getRequestURI();
//判斷是否是公開地址
//實際開發中需要將公開地址配置在配置檔案中
//從配置檔案中取出可以匿名訪問的URL
List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");
for (String open_url : open_urls) {
if (url.indexOf(open_url)>=0) {
//如果是公開地址 則放行
return true;
}
}
//判斷使用者身份在Session中是否存在
HttpSession session = request.getSession();
ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
//如果使用者身份在session中存在則放行
if (activeUser!=null) {
return true;
}
//執行到這裡攔截,跳轉到登入頁面,使用者進行身份認證
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
//如果返回false表示攔截器不繼續執行handler,如果返回true表示放行
return false;
}
//在執行handler返回modelAndView之前執行
//如果需要向頁面提供一些公用的資料或配置一些檢視資訊,使用此方法實現 從modelAndView入手
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
System.out.println("HandlerInterceptor2...postHandle");
}
//執行handler之後執行此方法
//作為系統統一異常處理,進行方法執行效能監控,在preHandler中設定一個時間點 在afterCompletion設定一個時間點 二者時間差就是執行時長
//實現系統,統一日誌記錄
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
throws Exception {
System.out.println("HandlerInterceptor2...afterCompletion");
}
}
資源授權攔截器PermissionInterceptor
package liuxun.ssm.controller.interceptor;
import java.security.acl.Permission;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.po.SysPermission;
import liuxun.ssm.util.ResourcesUtil;
/**
* 授權攔截器
* @author liuxun
*
*/
public class PermissionInterceptor implements HandlerInterceptor{
//在執行handler之前執行的
//用於使用者認證校驗、使用者許可權校驗
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//得到請求的url
String url = request.getRequestURI();
//判斷是否是公開地址
//實際開發中需要將公開地址配置在配置檔案中
//從配置檔案中取出可以匿名訪問的URL
List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");
for (String open_url : open_urls) {
if (url.indexOf(open_url)>=0) {
//如果是公開地址 則放行
return true;
}
}
//從配置檔案中獲取公用訪問url
List<String> common_urls = ResourcesUtil.getKeyList("commonURL");
//遍歷公用地址 如果是公開地址則放行
for (String common_url : common_urls) {
if (url.indexOf(common_url)>0) {
//如果是公開,則放行
return true;
}
}
//判斷使用者身份在Session中是否存在
HttpSession session = request.getSession();
ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
//從Session中取出許可權範圍的URL
List<SysPermission> permissions = activeUser.getPermissions();
for (SysPermission sysPermission : permissions) {
//許可權url
String permission_url = sysPermission.getUrl();
if (url.indexOf(permission_url)>0) {
return true;
}
}
//執行到這裡攔截,跳轉到無權訪問的提示頁面
request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response);
//如果返回false表示攔截器不繼續執行handler,如果返回true表示放行
return false;
}
//在執行handler返回modelAndView之前執行
//如果需要向頁面提供一些公用的資料或配置一些檢視資訊,使用此方法實現 從modelAndView入手
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
System.out.println("HandlerInterceptor2...postHandle");
}
//執行handler之後執行此方法
//作為系統統一異常處理,進行方法執行效能監控,在preHandler中設定一個時間點 在afterCompletion設定一個時間點 二者時間差就是執行時長
//實現系統,統一日誌記錄
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
throws Exception {
System.out.println("HandlerInterceptor2...afterCompletion");
}
}
攔截器配置
<!-- 攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 使用者認證攔截 -->
<mvc:mapping path="/**"/>
<bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<!-- 資源攔截 -->
<mvc:mapping path="/**"/>
<bean class="liuxun.ssm.controller.interceptor.PermissionInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
總結
使用基於url攔截的許可權管理方式,實現起來比較簡單,不依賴框架,使用web提供filter就可以實現。
問題:
需要將所有的url全部配置起來,有些繁瑣,不易維護,url(資源)和許可權表示方式不規範。
相關文章
- 許可權設計
- 許可權系統:許可權應用服務設計
- android 許可權元件設計Android元件
- 許可權系統:6個許可權概念模型設計模型
- 許可權系統:許可權應用服務設計Tu
- 授權許可權服務設計解析
- PingCode Wiki 許可權設計之ACLGC
- 選單許可權和按鈕許可權設定
- 基於RBAC的許可權設計模型模型
- 許可權安全管控的設計想法
- django開發之許可權管理(一)——許可權管理詳解(許可權管理原理以及方案)、不使用許可權框架的原始授權方式詳解Django框架
- 基於SSM框架的JavaWeb通用許可權管理系統SSM框架JavaWeb
- 許可權之選單許可權
- linux 檔案許可權 s 許可權和 t 許可權解析Linux
- Rbac使用者角色許可權表設計
- .NET視覺化許可權功能介面設計視覺化
- 基於位運算的許可權設計
- 如何用 Vue 實現前端許可權控制(路由許可權 + 檢視許可權 + 請求許可權)Vue前端路由
- ubuntu 許可權管理設定Ubuntu
- 許可權系統:一文搞懂功能許可權、資料許可權
- Linux 許可權控制的基本原理Linux
- jenkins原理篇——成員許可權管理Jenkins
- PostgreSQL物件許可權如何在後設資料中獲取-許可權解讀、定製化匯出許可權SQL物件
- 淺談許可權管理的設計與實現
- Spring Security + jwt 許可權系統設計,包含SQLSpringJWTSQL
- 後臺許可權設計問題,請教思路
- 分散式系統中,許可權設計實踐分散式
- 資料許可權就該這麼設計,yyyds!
- 關於系統許可權的設計-位操作
- 微服務中如何設計一個許可權授權服務微服務
- 小程式許可權設定(位置)
- Linux 如何設定特殊許可權?Linux
- Linux特殊許可權之suid、sgid、sbit許可權LinuxUI
- learun通用許可權系統框架功能實現設計框架
- SAP Basis DEBUG改表資料許可權角色設計
- 手把手擼套框架-許可權系統設計框架
- 管理系統之許可權的設計和實現
- 通用許可權系統之資料庫表設計資料庫