这个案例基于上一个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的留言私发!!!