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 }
View Code

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 }
View Code

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 }
View Code

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 }
View Code

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>
View Code

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>
View Code

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>
View Code

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>
View Code

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>
View Code

打開瀏覽器測試,finish

重啟Tomcat測試,業務測試地址:

http://127.0.0.1:8080/shirossm_dynamicURL/index

權限配置測試地址: 

http://127.0.0.1:8080/shirossm_dynamicURL/config/listUser

為什么角色不對應URL

權限通過url進行靈活配置了。 但是角色還沒有和url對應起來。 為什么不把角色也對應起來呢?
從代碼開發的角度講是可以做的,無非就是在 role表上增加一個url 字段。 但是從權限管理本身的角度看,當一個url 既對應權限表的數據,又對應角色表的數據,反而容易產生混淆。
反倒是現在這種,url地址,僅僅和權限表關聯起來,在邏輯上明晰簡單,也更容易維護。 所以就放棄了角色表也進行url維護的做法了。

 最后,同樣地,需要demo的留言私發!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM