這個案例基於上一個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的留言私發!!!