權(quán)限管理中确徙,角色授權(quán)與認證屬于權(quán)限模塊中的關(guān)鍵模塊醒串,角色授權(quán)即是將角色能夠操作的菜單資源分配給指定角色的行為,角色認證即是當用戶扮演指定角色登錄系統(tǒng)后系統(tǒng)對于用戶操作的資源進行權(quán)限校驗的操作鄙皇,意思這里說明白了芜赌,那么在代碼中應該具體怎么實現(xiàn)呢?
角色授權(quán)與認證的方式
前端頁面展示控制
后端權(quán)限訪問控制
案例實操
角色授權(quán)
樹形數(shù)據(jù)展示
完成角色記錄基本 crud 功能之后伴逸,接下來實現(xiàn)角色授權(quán)功能缠沈,這里實現(xiàn)角色授權(quán)首先完成待授權(quán)資源顯示功能。對于資源的顯示错蝴,這里使用開源的 tree 插件 ztree洲愤。
資源數(shù)據(jù)查詢后端實現(xiàn)
前端 ztree 顯示的資源數(shù)據(jù)格式參考這里。
ModuleMapper.xml
<selectid="queryAllModules"resultType="com.xxxx.crm.dto.TreeDto">
?? select
?? id,
?? IFNULL(parent_id,0) as pId,
?? module_name AS name
?? from t_module
?? where is_valid=1
</select>
ModuleService.java
publicList<TreeDto>queryAllModules(){
returnmoduleMapper.queryAllModules();
}
ModuleController.java
@RequestMapping("queryAllModules")
@ResponseBody
publicList<TreeDto>queryAllModules(){
returnmoduleService.queryAllModules();
}
資源數(shù)據(jù) ztree 顯示
role.js 添加授權(quán)點擊事件
//頭工具欄事件
table.on('toolbar(roles)',function(obj){
varcheckStatus=table.checkStatus(obj.config.id);
switch(obj.event){
case"add":
openAddOrUpdateRoleDialog();
break;
case"grant":
openAddGrantDailog(checkStatus.data);
break;
?? };
});
?
functionopenAddGrantDailog(datas){
if(datas.length==0){
layer.msg("請選擇待授權(quán)角色記錄!", {icon:5});
return;
?? }
if(datas.length>1){
layer.msg("暫不支持批量角色授權(quán)!", {icon:5});
return;
?? }
varurl=ctx+"/role/toAddGrantPage?roleId="+datas[0].id;
vartitle="角色管理-角色授權(quán)";
layui.layer.open({
title:title,
type:2,
area:["600px","280px"],
maxmin:true,
content:url
?? });
}
RoleController.java 添加視圖轉(zhuǎn)發(fā)方法
@RequestMapping("toAddGrantPage")
publicStringtoAddGrantPage(IntegerroleId,Modelmodel){
model.addAttribute("roleId",roleId);
return"role/grant";
}
準備顯示資源數(shù)據(jù)模板
views/role 目錄下添加 grant.ftl 模板文件
<html>
<head>
<linkrel="stylesheet"href="${ctx}/static/js/zTree_v3-3.5.32/css/zTreeStyle/zTreeStyle.css"type="text/css">
<scripttype="text/javascript"src="${ctx}/static/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
<scripttype="text/javascript"src="${ctx}/static/js/zTree_v3-3.5.32/js/jquery.ztree.core.js"></script>
<scripttype="text/javascript"src="${ctx}/static/js/zTree_v3-3.5.32/js/jquery.ztree.excheck.js"></script>
</head>
<body>
<divid="test1"class="ztree"></div>
<inputid="roleId"value="${roleId!}"type="hidden">
<scripttype="text/javascript">
varctx="${ctx}";
</script>
<scripttype="text/javascript"src="${ctx}/static/js/role/grant.js"></script>
</body>
</html>
添加 grant.js
varzTreeObj;
$(function() {
loadModuleInfo();
});
functionloadModuleInfo() {
$.ajax({
type:"post",
url:ctx+"/module/queryAllModules"
dataType:"json",
success:function(data) {
// zTree 的參數(shù)配置顷锰,深入使用請參考 API 文檔(setting 配置詳解)
varsetting={
data: {
simpleData: {
enable:true
? ? ? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? },
view:{
showLine:false
// showIcon: false
? ? ? ? ? ? ?? },
check: {
enable:true,
chkboxType: {"Y":"ps","N":"ps"}
? ? ? ? ? ? ?? }
? ? ? ? ?? };
varzNodes=data;
zTreeObj=$.fn.zTree.init($("#test1"),setting,zNodes);
? ? ?? }
?? })
}
角色授權(quán)
權(quán)限記錄添加后端實現(xiàn)
RoleService.java
publicvoidaddGrant(Integer[]mids,IntegerroleId) {
/**
* 核心表-t_permission? t_role(校驗角色存在)
* ? 如果角色存在原始權(quán)限? 刪除角色原始權(quán)限
* ? ? 然后添加角色新的權(quán)限 批量添加權(quán)限記錄到t_permission
*/
Roletemp=selectByPrimaryKey(roleId);
AssertUtil.isTrue(null==roleId||null==temp,"待授權(quán)的角色不存在!");
intcount=permissionMapper.countPermissionByRoleId(roleId);
if(count>0){
AssertUtil.isTrue(permissionMapper.deletePermissionsByRoleId(roleId)<count,"權(quán)限分配失敗!");
? ? ?? }
if(null!=mids&&mids.length>0){
List<Permission>permissions=newArrayList<Permission>();
for(Integermid:mids) {
Permissionpermission=newPermission();
permission.setCreateDate(newDate());
permission.setUpdateDate(newDate());
permission.setModuleId(mid);
permission.setRoleId(roleId);
permission.setAclValue(moduleMapper.selectByPrimaryKey(mid).getOptValue());
permissions.add(permission);
? ? ? ? ?? }
permissionMapper.insertBatch(permissions);
? ? ?? }
}
RoleController.java
@RequestMapping("addGrant")
@ResponseBody
publicResultInfoaddGrant(Integer[]mids,IntegerroleId){
roleService.addGrant(mids,roleId);
returnsuccess("權(quán)限添加成功");
}
權(quán)限記錄添加前端核心 js
修改 grant.js 文件 添加 ztree 復選框點擊回調(diào) onCheck 事件柬赐。
varzTreeObj;
$(function() {
loadModuleInfo();
});
functionloadModuleInfo() {
$.ajax({
type:"post",
url:ctx+"/module/queryAllModules",
dataType:"json",
success:function(data) {
// zTree 的參數(shù)配置,深入使用請參考 API 文檔(setting 配置詳解)
varsetting={
data: {
simpleData: {
enable:true
? ? ? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? },
view:{
showLine:false
// showIcon: false
? ? ? ? ? ? ?? },
check: {
enable:true,
chkboxType: {"Y":"ps","N":"ps"}
? ? ? ? ? ? ?? },
callback: {
onCheck:zTreeOnCheck
? ? ? ? ? ? ?? }
? ? ? ? ?? };
varzNodes=data;
zTreeObj=$.fn.zTree.init($("#test1"),setting,zNodes);
? ? ?? }
?? })
}
?
functionzTreeOnCheck(event,treeId,treeNode) {
varnodes=zTreeObj.getCheckedNodes(true);
varroleId=$("#roleId").val();
varmids="mids=";
for(vari=0;i<nodes.length;i++){
if(i<nodes.length-1){
mids=mids+nodes[i].id+"&mids=";
}else{
mids=mids+nodes[i].id;
? ? ? ? ?? }
? ? ?? }
$.ajax({
type:"post",
url:ctx+"/role/addGrant",
data:mids+"&roleId="+roleId,
dataType:"json",
success:function(data) {
console.log(data);
? ? ? ? ?? }
?? })
}
角色已添加權(quán)限記錄回顯
這里要實現(xiàn)已添加的角色記錄權(quán)限再次查看或授權(quán)時顯示原始權(quán)限的功能官紫,ztree 復選框是否選擇屬性配置參考這里肛宋。
資源查詢后端方法實現(xiàn)
ModuleService.java
publicList<TreeDto>queryAllModules02(IntegerroleId) {
List<TreeDto>treeDtos=moduleMapper.queryAllModules();
// 根據(jù)角色id 查詢角色擁有的菜單id? List<Integer>
List<Integer>roleHasMids=permissionMapper.queryRoleHasAllModuleIdsByRoleId(roleId);
if(null!=roleHasMids&&roleHasMids.size()>0){
treeDtos.forEach(treeDto->{
if(roleHasMids.contains(treeDto.getId())){
//? 說明當前角色 分配了該菜單
treeDto.setChecked(true);
? ? ? ? ?? }
? ? ?? });
?? }
returntreeDtos;
}
角色擁有權(quán)限 sql 查詢
<selectid="queryRoleHasAllModuleIdsByRoleId"parameterType="int"resultType="java.lang.Integer">
?? select module_id from t_permission where role_id=#{roleId}
</select>
ModuleController.java
@RequestMapping("queryAllModules")
@ResponseBody
publicList<TreeDto>queryAllModules(IntegerroleId){
returnmoduleService.queryAllModules02(roleId);
}
權(quán)限回顯前端 js
這里修改 grant.js 查詢資源時傳入當前選擇角色 id
functionloadModuleInfo() {
$.ajax({
type:"post",
url:ctx+"/module/queryAllModules",
data:{
roleId:$("#roleId").val()
? ? ?? },
dataType:"json",
success:function(data) {
// zTree 的參數(shù)配置州藕,深入使用請參考 API 文檔(setting 配置詳解)
varsetting={
data: {
simpleData: {
enable:true
? ? ? ? ? ? ? ? ?? }
? ? ? ? ? ? ?? },
view:{
showLine:false
// showIcon: false
? ? ? ? ? ? ?? },
check: {
enable:true,
chkboxType: {"Y":"ps","N":"ps"}
? ? ? ? ? ? ?? },
callback: {
onCheck:zTreeOnCheck
? ? ? ? ? ? ?? }
? ? ? ? ?? };
varzNodes=data;
zTreeObj=$.fn.zTree.init($("#test1"),setting,zNodes);
? ? ?? }
?? })
}
角色認證
當完成角色權(quán)限添加功能后,下一步就是對角色操作的資源進行認證操作酝陈,這里對于認證包含兩塊:
菜單級別顯示控制
后端方法訪問控制
菜單級別訪問控制實現(xiàn)
系統(tǒng)根據(jù)登錄用戶扮演的不同角色來對登錄用戶操作的菜單進行動態(tài)控制顯示操作床玻,這里顯示的控制使用 freemarker 指令+內(nèi)建函數(shù)實現(xiàn),指令與內(nèi)建函數(shù)操作參考這里后添。
登錄用戶角色擁有權(quán)限查詢實現(xiàn)
IndexController.java
/**
* 后端管理主頁面
* @return
*/
@RequestMapping("main")
publicStringmain(HttpServletRequestrequest){
IntegeruserId=LoginUserUtil.releaseUserIdFromCookie(request);
request.setAttribute("user",userService.selectByPrimaryKey(userId));
List<String>permissions=permissionService.queryUserHasRolesHasPermissions(userId);
request.getSession().setAttribute("permissions",permissions);
return"main";
}
PermissionService.java
@Service
publicclassPermissionServiceextendsBaseService<Permission,Integer>{
?
@Autowired
privatePermissionMapperpermissionMapper;
?
publicList<String>queryUserHasRolesHasPermissions(IntegeruserId) {
returnpermissionMapper.queryUserHasRolesHasPermissions(userId);
?? }
}
PermissionMapper.java & PermissionMapper.xml
publicinterfacePermissionMapperextendsBaseMapper<Permission,Integer>{
List<String>queryUserHasRolesHasPermissions(IntegeruserId);
}
<selectid="queryUserHasRolesHasPermissions"parameterType="int"resultType="java.lang.String">
?? select distinct p.acl_value
?? from t_user_role ur left join? t_permission p on ur.role_id = p.role_id
?? where ur.user_id=#{userId}
</select>
系統(tǒng)主頁面菜單顯示指令控制
這里僅顯示部分菜單控制笨枯。
<#ifpermissions?seq_contains("60")>
<liclass="layui-nav-item">
<ahref="javascript:;"class="layui-menu-tips"><iclass="fa fa-gears"></i><spanclass="layui-left-nav">系統(tǒng)設(shè)置</span><spanclass="layui-nav-more"></span></a>
<dlclass="layui-nav-child">
<#ifpermissions?seq_contains("6010")>
<dd>
<ahref="javascript:;"class="layui-menu-tips"data-type="tabAdd"data-tab-mpi="m-p-i-11"data-tab="user/index"target="_self"><iclass="fa fa-user"></i><spanclass="layui-left-nav">用戶管理</span></a>
</dd>
</#if>
<#ifpermissions?seq_contains("6020")>
<ddclass="">
<ahref="javascript:;"class="layui-menu-tips"data-type="tabAdd"data-tab-mpi="m-p-i-12"data-tab="role/index"target="_self"><iclass="fa fa-tachometer"></i><spanclass="layui-left-nav">角色管理</span></a>
</dd>
</#if>
<#ifpermissions?seq_contains("6030")>
<ddclass="">
<ahref="javascript:;"class="layui-menu-tips"data-type="tabAdd"data-tab-mpi="m-p-i-13"data-tab="module/index"target="_self"><iclass="fa fa-tachometer"></i><spanclass="layui-left-nav">菜單管理</span></a>
</dd>
</#if>
</dl>
</li>
</#if>
后端方法級別訪問控制
實現(xiàn)了菜單級別顯示控制,但最終客戶端有可能會通過瀏覽器來輸入資源地址從而越過 ui 界面來訪問后端資源遇西,所以接下來加入控制方法級別資源的訪問控制操作馅精,這里使用 aop+自定義注解實現(xiàn)
自定義注解@RequirePermission
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceRequirePermission{
Stringcode()default"";
}
方法級別使用注解
@RequestMapping("list")
@ResponseBody
@RequirePermission(code="101001")
publicMap<String,Object>querySaleChancesByParams(Integerflag,HttpServletRequestrequest,SaleChanceQuerysaleChanceQuery){
if(null!=flag&&flag==1){
// 查詢分配給當前登錄用戶 營銷記錄
saleChanceQuery.setAggsinMan(LoginUserUtil.releaseUserIdFromCookie(request));
?? }
returnsaleChanceService.queryByParamsForTable(saleChanceQuery);
}
定義aop切面類攔截指定注解標注的方法
@Component
@Aspect
publicclassPermissionProxy{
?
@Autowired
privateHttpSessionsession;
?
@Around(value="@annotation(com.xxx.sys.annotaions.RequirePermission)")
publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{
List<String>permissions=(List<String>)session.getAttribute("permissions");
if(null==permissions||permissions.size()==0){
thrownewNoPermissionException();
? ? ?? }
Objectresult=null;
MethodSignaturemethodSignature=(MethodSignature)pjp.getSignature();
RequirePermissionrequirePermission=methodSignature.getMethod().getDeclaredAnnotation(RequirePermission.class);
if(!(permissions.contains(requirePermission.code()))){
thrownewNoPermissionException();
? ? ?? }
result=pjp.proceed();
returnresult;
?? }
}
擴展~自定義注解實例
從 JDK5 開始,Java 增加對元數(shù)據(jù)的支持粱檀,也就是注解洲敢,注解與注釋是有一定區(qū)別的,可以把注解理解為代碼里的特殊標記茄蚯,這些標記可以在編譯压彭,類加載,運行時被讀取渗常,并執(zhí)行相應的處理壮不。通過注解開發(fā)人員可以在不改變原有代碼和邏輯的情況下在源代碼中嵌入補充信息。下面我們來看看如何自定義注解皱碘。
創(chuàng)建自定義注解類
packagecom.lebyte.annotations;
?
importjava.lang.annotation.Documented;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Inherited;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
?
/* ?? @Target,@Retention,@Inherited,@Documented
* ? ? 這四個是對注解進行注解的元注解询一,負責自定義的注解的屬性
*/
@Target({ElementType.TYPE,ElementType.METHOD})//表示注解的作用對象,ElementType.TYPE表示類癌椿,ElementType.METHOD表示方法
@Retention(RetentionPolicy.RUNTIME)//表示注解的保留機制健蕊,RetentionPolicy.RUNTIME表示運行時注解
@Inherited//表示該注解可繼承
@Documented//表示該注解可生成文檔
public@interfaceDesign{
Stringauthor();//注解成員,如果注解只有一個成員踢俄,則成員名必須為value()缩功,成員類型只能為原始類型
intdata()default0;//注解成員,默認值為0
}
使用注解
packagecom.lebyte;
?
importcom.lebyte.annotations.Design;
?
@Design(author="lebyte",data=100)//使用自定義注解都办,有默認值的成員可以不用賦值嫡锌,其余成員都要賦值
publicclassPerson{
@Design(author="lebyte",data=90)
publicvoidlive(){
?
?? }
}
解析注解
packagecom.lebyte;
?
importjava.lang.annotation.Annotation;
importjava.lang.reflect.Method;
?
importcom.lebyte.annotations.Design;
?
publicclassMain{
?
publicstaticvoidmain(String[]args)throwsClassNotFoundException{
?
Classc=Class.forName("com.lebyte.Person");//使用類加載器加載類
?
//1、找到類上的注解
if(c.isAnnotationPresent(Design.class)){//判斷類是否被指定注解注解
Designd=(Design)c.getAnnotation(Design.class);//拿到類上的指定注解實例
System.out.println(d.data());
? ? ?? }
?
//2脆丁、找到方法上的注解
Method[]ms=c.getMethods();
for(Methodm:ms){
if(m.isAnnotationPresent(Design.class)){//判斷方法是否被指定注解注解
Designd=m.getAnnotation(Design.class);//拿到類上的指定注解實例
System.out.println(d.data());
? ? ? ? ?? }
? ? ?? }
?
//3世舰、另外一種方法
for(Methodm:ms){
Annotation[]as=m.getAnnotations();//拿到類上的注解集合
for(Annotationa:as){
if(ainstanceofDesign){//判斷指定注解
Designd=(Design)a;
System.out.println(d.data());
? ? ? ? ? ? ?? }
? ? ? ? ?? }
? ? ?? }
?? }
?
}
ooooo