Shiro集成SSM基于动态URL权限管理(二)
这个案例基于上一个demo扩展而来。所以数据库表,在shiro集成ssm基于url权限管理(一)开篇的一致。如果上个demo操作的建议重新导入一次,避免出现问题。
而这次都不是通过固定写在方法上的注解实现的,而是通过权限灵活配置实现的。
pagecontroller.java
首先是pagecontroller.java 里原本通过注解方式的@requirespermissions和@requiresroles 注释掉了。
1 import org.springframework.stereotype.controller; 2 import org.springframework.web.bind.annotation.requestmapping; 3 import org.springframework.web.bind.annotation.requestmethod; 4 5 //专门用于显示页面的控制器 6 @controller 7 @requestmapping("") 8 public class pagecontroller { 9 10 @requestmapping("index") 11 public string index(){ 12 return "index"; 13 } 14 15 // @requirespermissions("deleteorder") 16 @requestmapping("deleteorder") 17 public string deleteorder(){ 18 return "deleteorder"; 19 } 20 // @requiresroles("productmanager") 21 @requestmapping("deleteproduct") 22 public string deleteproduct(){ 23 return "deleteproduct"; 24 } 25 @requestmapping("listproduct") 26 public string listproduct(){ 27 return "listproduct"; 28 } 29 30 @requestmapping(value="/login",method=requestmethod.get) 31 public string login(){ 32 return "login"; 33 } 34 @requestmapping("unauthorized") 35 public string noperms(){ 36 return "unauthorized"; 37 } 38 39 }
permissionservice.java
增加了两个方法 needinterceptor,listpermissionurls
代码如下:
1 import java.util.list; 2 import java.util.set; 3 4 import com.how2java.pojo.permission; 5 import com.how2java.pojo.role; 6 7 public interface permissionservice { 8 public set<string> listpermissions(string username); 9 10 public list<permission> list(); 11 12 public void add(permission permission); 13 14 public void delete(long id); 15 16 public permission get(long id); 17 18 public void update(permission permission); 19 20 public list<permission> list(role role); 21 22 public boolean needinterceptor(string requesturi); 23 24 public set<string> listpermissionurls(string username); 25 26 }
permissionserviceimpl.java
为两个方法 needinterceptor,listpermissionurls 增加了实现
needinterceptor 表示是否要进行拦截,判断依据是如果访问的某个url,在权限系统里存在,就要进行拦截。 如果不存在,就放行了。
这一种策略,也可以切换成另一个,即,访问的地址如果不存在于权限系统中,就提示没有拦截。 这两种做法没有对错之分,取决于业务上希望如何制定权限策略。
listpermissionurls(user user) 用来获取某个用户所拥有的权限地址集合
1 import java.util.arraylist; 2 import java.util.hashset; 3 import java.util.list; 4 import java.util.set; 5 6 import org.springframework.beans.factory.annotation.autowired; 7 import org.springframework.stereotype.service; 8 9 import com.how2java.mapper.permissionmapper; 10 import com.how2java.mapper.rolepermissionmapper; 11 import com.how2java.pojo.permission; 12 import com.how2java.pojo.permissionexample; 13 import com.how2java.pojo.role; 14 import com.how2java.pojo.rolepermission; 15 import com.how2java.pojo.rolepermissionexample; 16 import com.how2java.service.permissionservice; 17 import com.how2java.service.roleservice; 18 import com.how2java.service.userservice; 19 20 @service 21 public class permissionserviceimpl implements permissionservice { 22 23 @autowired 24 permissionmapper permissionmapper; 25 @autowired 26 userservice userservice; 27 @autowired 28 roleservice roleservice; 29 @autowired 30 rolepermissionmapper rolepermissionmapper; 31 32 @override 33 public set<string> listpermissions(string username) { 34 set<string> result = new hashset<>(); 35 list<role> roles = roleservice.listroles(username); 36 37 list<rolepermission> rolepermissions = new arraylist<>(); 38 39 for (role role : roles) { 40 rolepermissionexample example = new rolepermissionexample(); 41 example.createcriteria().andridequalto(role.getid()); 42 list<rolepermission> rps = rolepermissionmapper.selectbyexample(example); 43 rolepermissions.addall(rps); 44 } 45 46 for (rolepermission rolepermission : rolepermissions) { 47 permission p = permissionmapper.selectbyprimarykey(rolepermission.getpid()); 48 result.add(p.getname()); 49 } 50 51 return result; 52 } 53 54 @override 55 public void add(permission u) { 56 permissionmapper.insert(u); 57 } 58 59 @override 60 public void delete(long id) { 61 permissionmapper.deletebyprimarykey(id); 62 } 63 64 @override 65 public void update(permission u) { 66 permissionmapper.updatebyprimarykeyselective(u); 67 } 68 69 @override 70 public permission get(long id) { 71 return permissionmapper.selectbyprimarykey(id); 72 } 73 74 @override 75 public list<permission> list() { 76 permissionexample example = new permissionexample(); 77 example.setorderbyclause("id desc"); 78 return permissionmapper.selectbyexample(example); 79 80 } 81 82 @override 83 public list<permission> list(role role) { 84 list<permission> result = new arraylist<>(); 85 rolepermissionexample example = new rolepermissionexample(); 86 example.createcriteria().andridequalto(role.getid()); 87 list<rolepermission> rps = rolepermissionmapper.selectbyexample(example); 88 for (rolepermission rolepermission : rps) { 89 result.add(permissionmapper.selectbyprimarykey(rolepermission.getpid())); 90 } 91 92 return result; 93 } 94 95 @override 96 public boolean needinterceptor(string requesturi) { 97 list<permission> ps = list(); 98 for (permission p : ps) { 99 if (p.geturl().equals(requesturi)) 100 return true; 101 } 102 return false; 103 } 104 105 @override 106 public set<string> listpermissionurls(string username) { 107 set<string> result = new hashset<>(); 108 list<role> roles = roleservice.listroles(username); 109 110 list<rolepermission> rolepermissions = new arraylist<>(); 111 112 for (role role : roles) { 113 rolepermissionexample example = new rolepermissionexample(); 114 example.createcriteria().andridequalto(role.getid()); 115 list<rolepermission> rps = rolepermissionmapper.selectbyexample(example); 116 rolepermissions.addall(rps); 117 } 118 119 for (rolepermission rolepermission : rolepermissions) { 120 permission p = permissionmapper.selectbyprimarykey(rolepermission.getpid()); 121 result.add(p.geturl()); 122 } 123 124 return result; 125 } 126 127 }
urlpathmatchingfilter.java
pathmatchingfilter 是shiro 内置过滤器 pathmatchingfilter 继承了这个它。
基本思路如下:
1. 如果没登录就跳转到登录
2. 如果当前访问路径没有在权限系统里维护,则允许访问
3. 当前用户所拥有的权限如果不包含当前的访问地址,则跳转到/unauthorized,否则就允许访问。
代码如下:
1 import java.util.set; 2 3 import javax.servlet.servletrequest; 4 import javax.servlet.servletresponse; 5 6 import org.apache.shiro.securityutils; 7 import org.apache.shiro.authz.unauthorizedexception; 8 import org.apache.shiro.subject.subject; 9 import org.apache.shiro.web.filter.pathmatchingfilter; 10 import org.apache.shiro.web.util.webutils; 11 import org.springframework.beans.factory.annotation.autowired; 12 13 import com.how2java.service.permissionservice; 14 15 public class urlpathmatchingfilter extends pathmatchingfilter { 16 @autowired 17 permissionservice permissionservice; 18 19 @override 20 protected boolean onprehandle(servletrequest request, servletresponse response, object mappedvalue) 21 throws exception { 22 string requesturi = getpathwithinapplication(request); 23 24 system.out.println("requesturi:" + requesturi); 25 26 subject subject = securityutils.getsubject(); 27 // 如果没有登录,就跳转到登录页面 28 if (!subject.isauthenticated()) { 29 webutils.issueredirect(request, response, "/login"); 30 return false; 31 } 32 33 // 看看这个路径权限里有没有维护,如果没有维护,一律放行(也可以改为一律不放行) 34 boolean needinterceptor = permissionservice.needinterceptor(requesturi); 35 if (!needinterceptor) { 36 return true; 37 } else { 38 boolean haspermission = false; 39 string username = subject.getprincipal().tostring(); 40 set<string> permissionurls = permissionservice.listpermissionurls(username); 41 for (string url : permissionurls) { 42 // 这就表示当前用户有这个权限 43 if (url.equals(requesturi)) { 44 haspermission = true; 45 break; 46 } 47 } 48 49 if (haspermission) 50 return true; 51 else { 52 unauthorizedexception ex = new unauthorizedexception("当前用户没有访问路径 " + requesturi + " 的权限"); 53 54 subject.getsession().setattribute("ex", ex); 55 56 webutils.issueredirect(request, response, "/unauthorized"); 57 return false; 58 } 59 60 } 61 62 } 63 }
applicationcontext-shiro.xml
首先声明urlpathmatchingfilter 过滤器
<bean id="urlpathmatchingfilter" class="com.how2java.filter.urlpathmatchingfilter"/>
在shiro中使用这个过滤器
<entry key="url" value-ref="urlpathmatchingfilter" />
过滤规则是所有访问
/** = url
代码如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <beans xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" 3 xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" 4 xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" 5 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" 6 xmlns:aop="http://www.springframework.org/schema/aop" 7 xsi:schemalocation="http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/tx 9 http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context 10 http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc 11 http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop 12 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/util 13 http://www.springframework.org/schema/util/spring-util.xsd"> 14 15 <!-- url过滤器 --> 16 <bean id="urlpathmatchingfilter" class="com.how2java.filter.urlpathmatchingfilter"/> 17 18 <!-- 配置shiro的过滤器工厂类,id- shirofilter要和我们在web.xml中配置的过滤器一致 --> 19 <bean id="shirofilter" class="org.apache.shiro.spring.web.shirofilterfactorybean"> 20 <!-- 调用我们配置的权限管理器 --> 21 <property name="securitymanager" ref="securitymanager" /> 22 <!-- 配置我们的登录请求地址 --> 23 <property name="loginurl" value="/login" /> 24 <!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 --> 25 <property name="unauthorizedurl" value="/unauthorized" /> 26 <!-- 退出 --> 27 <property name="filters"> 28 <util:map> 29 <entry key="logout" value-ref="logoutfilter" /> 30 <entry key="url" value-ref="urlpathmatchingfilter" /> 31 </util:map> 32 </property> 33 <!-- 权限配置 --> 34 <property name="filterchaindefinitions"> 35 <value> 36 <!-- anon表示此地址不需要任何权限即可访问 --> 37 /login=anon 38 /index=anon 39 /static/**=anon 40 <!-- 只对业务功能进行权限管理,权限配置本身不需要没有做权限要求,这样做是为了不让初学者混淆 --> 41 /config/**=anon 42 /dologout=logout 43 <!--所有的请求(除去配置的静态资源请求或请求地址为anon的请求)都要通过过滤器url --> 44 /** = url 45 </value> 46 </property> 47 </bean> 48 <!-- 退出过滤器 --> 49 <bean id="logoutfilter" class="org.apache.shiro.web.filter.authc.logoutfilter"> 50 <property name="redirecturl" value="/index" /> 51 </bean> 52 53 <!-- 会话id生成器 --> 54 <bean id="sessionidgenerator" 55 class="org.apache.shiro.session.mgt.eis.javauuidsessionidgenerator" /> 56 <!-- 会话cookie模板 关闭浏览器立即失效 --> 57 <bean id="sessionidcookie" class="org.apache.shiro.web.servlet.simplecookie"> 58 <constructor-arg value="sid" /> 59 <property name="httponly" value="true" /> 60 <property name="maxage" value="-1" /> 61 </bean> 62 <!-- 会话dao --> 63 <bean id="sessiondao" 64 class="org.apache.shiro.session.mgt.eis.enterprisecachesessiondao"> 65 <property name="sessionidgenerator" ref="sessionidgenerator" /> 66 </bean> 67 <!-- 会话验证调度器,每30分钟执行一次验证 ,设定会话超时及保存 --> 68 <bean name="sessionvalidationscheduler" 69 class="org.apache.shiro.session.mgt.executorservicesessionvalidationscheduler"> 70 <property name="interval" value="1800000" /> 71 <property name="sessionmanager" ref="sessionmanager" /> 72 </bean> 73 <!-- 会话管理器 --> 74 <bean id="sessionmanager" 75 class="org.apache.shiro.web.session.mgt.defaultwebsessionmanager"> 76 <!-- 全局会话超时时间(单位毫秒),默认30分钟 --> 77 <property name="globalsessiontimeout" value="1800000" /> 78 <property name="deleteinvalidsessions" value="true" /> 79 <property name="sessionvalidationschedulerenabled" value="true" /> 80 <property name="sessionvalidationscheduler" ref="sessionvalidationscheduler" /> 81 <property name="sessiondao" ref="sessiondao" /> 82 <property name="sessionidcookieenabled" value="true" /> 83 <property name="sessionidcookie" ref="sessionidcookie" /> 84 </bean> 85 86 <!-- 安全管理器 --> 87 <bean id="securitymanager" class="org.apache.shiro.web.mgt.defaultwebsecuritymanager"> 88 <property name="realm" ref="databaserealm" /> 89 <property name="sessionmanager" ref="sessionmanager" /> 90 </bean> 91 <!-- 相当于调用securityutils.setsecuritymanager(securitymanager) --> 92 <bean 93 class="org.springframework.beans.factory.config.methodinvokingfactorybean"> 94 <property name="staticmethod" 95 value="org.apache.shiro.securityutils.setsecuritymanager" /> 96 <property name="arguments" ref="securitymanager" /> 97 </bean> 98 99 <!-- 密码匹配器 --> 100 <bean id="credentialsmatcher" class="org.apache.shiro.authc.credential.hashedcredentialsmatcher"> 101 <property name="hashalgorithmname" value="md5"/> 102 <property name="hashiterations" value="2"/> 103 <property name="storedcredentialshexencoded" value="true"/> 104 </bean> 105 106 <bean id="databaserealm" class="com.how2java.realm.databaserealm"> 107 <property name="credentialsmatcher" ref="credentialsmatcher"/> 108 </bean> 109 110 <!-- 保证实现了shiro内部lifecycle函数的bean执行 --> 111 <bean id="lifecyclebeanpostprocessor" class="org.apache.shiro.spring.lifecyclebeanpostprocessor" /> 112 </beans>
jsp做了些文字上的改动
index.jsp
1 <%@ page language="java" contenttype="text/html; charset=utf-8" 2 pageencoding="utf-8"%> 3 <html> 4 <head> 5 <meta http-equiv="content-type" content="text/html; charset=utf-8"> 6 7 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 8 9 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 10 11 </head> 12 <body> 13 14 <div class="workingroom"> 15 <div class="logindiv"> 16 17 <c:if test="${empty subject.principal}"> 18 <a href="login">登录</a><br> 19 </c:if> 20 <c:if test="${!empty subject.principal}"> 21 <span class="desc">你好,${subject.principal},</span> 22 <a href="dologout">退出</a><br> 23 </c:if> 24 25 <a href="listproduct">查看产品</a><span class="desc">(要有查看产品权限, zhang3有,li4 有)</span><br> 26 <a href="deleteproduct">删除产品</a><span class="desc">(要有删除产品权限, zhang3有,li4 有)</span><br> 27 <a href="deleteorder">删除订单</a><span class="desc">(要有删除订单权限, zhang3有,li4没有)</span><br> 28 </div> 29 30 </body> 31 </html>
deleteorder.jsp
1 <%@ page language="java" contenttype="text/html; charset=utf-8" 2 pageencoding="utf-8" import="java.util.*"%> 3 4 <!doctype html> 5 6 <meta http-equiv="content-type" content="text/html; charset=utf-8"> 7 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 8 9 <div class="workingroom"> 10 11 deleteorder.jsp,能进来<br>就表示拥有 deleteorder 权限 12 <br> 13 <a href="#" onclick="javascript:history.back()">返回</a> 14 </div>
deleteproduct.jsp
1 <%@ page language="java" contenttype="text/html; charset=utf-8" 2 pageencoding="utf-8" import="java.util.*"%> 3 4 <!doctype html> 5 6 <meta http-equiv="content-type" content="text/html; charset=utf-8"> 7 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 8 9 <div class="workingroom"> 10 11 deleteproduct.jsp,能进来<br>就表示拥有 deleteproduct 权限 12 <br> 13 <a href="#" onclick="javascript:history.back()">返回</a> 14 </div>
listproduct.jsp
1 <%@ page language="java" contenttype="text/html; charset=utf-8" 2 pageencoding="utf-8" import="java.util.*"%> 3 4 <!doctype html> 5 6 <meta http-equiv="content-type" content="text/html; charset=utf-8"> 7 <link rel="stylesheet" type="text/css" href="static/css/style.css" /> 8 9 <div class="workingroom"> 10 11 listproduct.jsp,能进来<br>就表示拥有 listproduct 权限 12 <br> 13 <a href="#" onclick="javascript:history.back()">返回</a> 14 </div>
打开浏览器测试,finish。
重启tomcat测试,业务测试地址:
权限配置测试地址:
为什么角色不对应url
权限通过url进行灵活配置了。 但是角色还没有和url对应起来。 为什么不把角色也对应起来呢?
从代码开发的角度讲是可以做的,无非就是在 role表上增加一个url 字段。 但是从权限管理本身的角度看,当一个url 既对应权限表的数据,又对应角色表的数据,反而容易产生混淆。
反倒是现在这种,url地址,仅仅和权限表关联起来,在逻辑上明晰简单,也更容易维护。 所以就放弃了角色表也进行url维护的做法了。
最后,同样地,需要demo的留言私发!!!