上一篇《【原】無腦操做:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf實現基礎認證權限》介紹了實現Shiro的基礎認證。本篇談談實現Shiro的基礎受權。html
需求:java
① 某系統有公共模塊、領導模塊、管理員模塊三個業務模塊,均須要登陸系統後才能夠訪問。mysql
② admin、leader、employee三我的職位分別是管理員、領導、員工,都可登陸系統。git
③ 不一樣職位的人登陸系統後,能看到的功能模塊不一樣。管理員能夠訪問所有三個模塊。領導能夠訪問除去管理員模塊外的兩個模塊。員工只能訪問公共模塊。github
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------web
分析:spring
典型的運用受權權限的需求,繼續考慮使用Shiro。sql
問題一、認證和受權怎麼理解呢?數據庫
答:一點粗淺理解,好比經過了美國的簽證能進入美國了,這就是得到了認證。apache
可是進入美國了,也只能去有受權的地方玩玩,五角大樓能進麼?沒有受權是不給進的。
因此,受權是在認證得到後進一步的安全管理。
問題二、需求在描述什麼場景?
答:需求中包含了基於角色的權限訪問控制RBAC(Role-Based Access Control)的設計思路。
簡單來講,單我的對某某資源可操做。
進一步考慮,若是是多我的對某某資源可操做呢?須要重複的這樣設置麼?運用概括思想,把這樣的多我的歸爲一類,造成了角色的概念。即這一角色的多我的對某某資源可操做。
RBAC認爲權限受權其實是Who、What、How的問題。在RBAC模型中,who、what、how構成了訪問權限三元組,也就是「Who對What(Which)進行How的操做」。
問題三、針對本需求的RBAC設計是怎麼樣的?
答:簡化設計爲:用戶和角色爲多對一關係、角色和資源爲多對多關係
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0、數據庫建表init.sql
1 -- 初始化 2 DROP TABLE sys_user; 3 DROP TABLE sys_role; 4 DROP TABLE sys_resource; 5 DROP TABLE sys_role_resource; 6 7 -- 用戶信息表 8 CREATE TABLE sys_user 9 ( 10 userid INT AUTO_INCREMENT PRIMARY KEY COMMENT '用戶編號', 11 username VARCHAR(10) NOT NULL COMMENT '用戶名稱', 12 `password` VARCHAR(10) NOT NULL COMMENT '用戶密碼', 13 roleid INT NOT NULL COMMENT '角色編號' 14 ); 15 16 INSERT INTO sys_user VALUES(NULL, 'admin', '123', 1), (NULL, 'leader', '456', 2), (NULL, 'employee', '789', 3); 17 18 SELECT * FROM sys_user; 19 20 -- 角色信息表 21 CREATE TABLE sys_role 22 ( 23 roleid INT AUTO_INCREMENT PRIMARY KEY COMMENT '角色編號', 24 rolename VARCHAR(10) NOT NULL COMMENT '角色名稱' 25 ); 26 27 INSERT INTO sys_role VALUES(NULL, '管理員'), (NULL, '領導'), (NULL, '員工'); 28 29 SELECT * FROM sys_role; 30 31 -- 資源信息表 32 CREATE TABLE sys_resource 33 ( 34 resourceid INT AUTO_INCREMENT PRIMARY KEY COMMENT '資源編號', 35 resourcename VARCHAR(10) NOT NULL COMMENT '資源名稱', 36 resourceurl VARCHAR(50) NOT NULL COMMENT '資源URL' 37 ); 38 39 INSERT INTO sys_resource VALUES 40 (NULL, '公共模塊', 'publicModule'), 41 (NULL, '領導模塊', 'leaderModule'), 42 (NULL, '管理員模塊', 'adminModule'); 43 44 SELECT * FROM sys_resource; 45 46 -- 角色資源關聯表 47 CREATE TABLE sys_role_resource 48 ( 49 id INT AUTO_INCREMENT PRIMARY KEY COMMENT '關聯編號', 50 roleid INT NOT NULL COMMENT '角色編號', 51 resourceid INT NOT NULL COMMENT '資源編號' 52 ); 53 54 INSERT INTO sys_role_resource VALUES 55 (NULL, 1, 1), (NULL, 1, 2), (NULL, 1, 3), 56 (NULL, 2, 1), (NULL, 2, 2), 57 (NULL, 3, 1); 58 59 SELECT * FROM sys_role_resource; 60 61 -- 獲取用戶能訪問的資源URL 62 SELECT u.userid, rs.resourceurl 63 FROM sys_role_resource AS rr 64 INNER JOIN sys_resource AS rs ON rr.resourceid = rs.resourceid 65 INNER JOIN sys_role AS r ON rr.roleid = r.roleid 66 INNER JOIN sys_user AS u ON u.roleid = r.roleid 67 WHERE u.userid = 1;
一、編寫項目對象模型文件pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>cn.temptation</groupId> 8 <artifactId>studyShiro</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 11 <parent> 12 <groupId>org.springframework.boot</groupId> 13 <artifactId>spring-boot-starter-parent</artifactId> 14 <version>2.0.4.RELEASE</version> 15 </parent> 16 17 <dependencies> 18 <!-- web --> 19 <dependency> 20 <groupId>org.springframework.boot</groupId> 21 <artifactId>spring-boot-starter-web</artifactId> 22 </dependency> 23 <!-- thymeleaf --> 24 <dependency> 25 <groupId>org.springframework.boot</groupId> 26 <artifactId>spring-boot-starter-thymeleaf</artifactId> 27 </dependency> 28 <!-- spring data jpa --> 29 <dependency> 30 <groupId>org.springframework.boot</groupId> 31 <artifactId>spring-boot-starter-data-jpa</artifactId> 32 </dependency> 33 <!-- mariadb --> 34 <dependency> 35 <groupId>org.mariadb.jdbc</groupId> 36 <artifactId>mariadb-java-client</artifactId> 37 <version>2.2.5</version> 38 </dependency> 39 <!-- shiro --> 40 <dependency> 41 <groupId>org.apache.shiro</groupId> 42 <artifactId>shiro-spring</artifactId> 43 <version>1.4.0</version> 44 </dependency> 45 <!-- thymeleaf-extras-shiro --> 46 <dependency> 47 <groupId>com.github.theborakompanioni</groupId> 48 <artifactId>thymeleaf-extras-shiro</artifactId> 49 <version>2.0.0</version> 50 </dependency> 51 <!-- 熱啓動 --> 52 <dependency> 53 <groupId>org.springframework.boot</groupId> 54 <artifactId>spring-boot-devtools</artifactId> 55 <optional>true</optional> 56 </dependency> 57 </dependencies> 58 </project>
二、編寫項目配置文件application.properties
1 # 數據庫訪問配置 2 # 對應MariaDB驅動 3 spring.datasource.driverClassName=org.mariadb.jdbc.Driver 4 5 # 數據源配置 6 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test 7 spring.datasource.username=root 8 spring.datasource.password=sa 9 10 # 配置Springboot默認支持的Hikari數據庫鏈接池 11 spring.datasource.type=com.zaxxer.hikari.HikariDataSource 12 spring.datasource.hikari.minimum-idle=5 13 spring.datasource.hikari.maximum-pool-size=15 14 spring.datasource.hikari.auto-commit=true 15 spring.datasource.hikari.idle-timeout=30000 16 spring.datasource.hikari.pool-name=DatebookHikariCP 17 spring.datasource.hikari.max-lifetime=1800000 18 spring.datasource.hikari.connection-timeout=30000 19 spring.datasource.hikari.connection-test-query=SELECT 1 20 21 # Spring Data JPA配置 22 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect 23 spring.jpa.properties.hibernate.hbm2ddl.auto=update 24 spring.jpa.show-sql=true 25 spring.jpa.properties.hibernate.format_sql=true 26 27 # 格式化輸出的json字符串 28 spring.jackson.serialization.indent_output=true 29 30 # 設置控制檯彩色打印 31 spring.output.ansi.enabled=ALWAYS
三、編寫項目啓動類Application.java
1 package cn.temptation; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 6 @SpringBootApplication 7 public class Application { 8 public static void main(String[] args) { 9 // SpringBoot項目啓動 10 SpringApplication.run(Application.class, args); 11 } 12 }
四、編寫全局異常處理類GlobalExceptionHandler.java
1 package cn.temptation.util; 2 3 import org.springframework.web.bind.annotation.ControllerAdvice; 4 import org.springframework.web.bind.annotation.ExceptionHandler; 5 6 /** 7 * 全局異常處理類 8 */ 9 @ControllerAdvice 10 public class GlobalExceptionHandler { 11 @ExceptionHandler(value = Exception.class) 12 public String errorHandler(Exception exception) { 13 return "redirect:/error/500"; 14 } 15 }
五、編寫錯誤頁配置類ErrorPageConfig.java 和 錯誤頁控制器ErrorController.java
錯誤頁配置類ErrorPageConfig.java
1 package cn.temptation.util; 2 3 import org.springframework.boot.web.server.ErrorPage; 4 import org.springframework.boot.web.server.ErrorPageRegistrar; 5 import org.springframework.boot.web.server.ErrorPageRegistry; 6 import org.springframework.http.HttpStatus; 7 import org.springframework.stereotype.Component; 8 9 /** 10 * 錯誤頁配置類 11 */ 12 @Component 13 public class ErrorPageConfig implements ErrorPageRegistrar { 14 @Override 15 public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { 16 // 錯誤類型爲401(無訪問權限),顯示401.html頁面 17 ErrorPage errorPage401 = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/401"); 18 19 // 錯誤類型爲404(找不到資源),顯示404.html頁面 20 ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"); 21 22 // 錯誤類型爲500(服務器內部錯誤),顯示500.html頁面 23 ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"); 24 25 errorPageRegistry.addErrorPages(errorPage401, errorPage404, errorPage500); 26 } 27 }
錯誤頁控制器ErrorController.java
1 package cn.temptation.util; 2 3 import org.springframework.stereotype.Controller; 4 import org.springframework.web.bind.annotation.GetMapping; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 7 /** 8 * 錯誤頁控制器 9 */ 10 @Controller 11 @RequestMapping("/error") 12 public class ErrorController { 13 // 401頁面 14 @GetMapping(value = "/401") 15 public String error_401() { 16 return "error/error_401"; 17 } 18 19 // 404頁面 20 @GetMapping(value = "/404") 21 public String error_404() { 22 return "error/error_404"; 23 } 24 25 // 500頁面 26 @GetMapping(value = "/500") 27 public String error_500() { 28 return "error/error_500"; 29 } 30 }
六、編寫錯誤頁error_401.html、error_404.html 和 error_500.html
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta http-equiv="refresh" content="5;URL=/login"> 6 <title>401</title> 7 <style> 8 ::-moz-selection { 9 background: #b3d4fc; 10 text-shadow: none; 11 } 12 13 ::selection { 14 background: #b3d4fc; 15 text-shadow: none; 16 } 17 18 html { 19 padding: 30px 10px; 20 font-size: 20px; 21 line-height: 1.4; 22 color: #737373; 23 background: #f0f0f0; 24 font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 25 -webkit-text-size-adjust: 100%; 26 -ms-text-size-adjust: 100%; 27 } 28 29 body { 30 max-width: 550px; 31 _width: 550px; 32 padding: 30px 20px 50px; 33 border: 1px solid #b3b3b3; 34 border-radius: 4px; 35 margin: 0 auto; 36 box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff; 37 background: #fcfcfc; 38 } 39 40 h1 { 41 margin: 0 10px; 42 font-size: 50px; 43 text-align: center; 44 } 45 46 h1 span { 47 color: #bbb; 48 } 49 50 h3 { 51 margin: 1.5em 0 0.5em; 52 } 53 54 p { 55 margin: 1em 0; 56 } 57 58 ul { 59 padding: 0 0 0 40px; 60 margin: 1em 0; 61 } 62 63 .container { 64 max-width: 500px; 65 _width: 500px; 66 margin: 0 auto; 67 } 68 </style> 69 </head> 70 <body> 71 <div class="container"> 72 <h1>沒有受權</h1> 73 <p>抱歉,您沒有受權訪問該頁面</p> 74 </div> 75 </body> 76 </html>
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta http-equiv="refresh" content="5;URL=/login"> 6 <title>404</title> 7 <style> 8 ::-moz-selection { 9 background: #b3d4fc; 10 text-shadow: none; 11 } 12 13 ::selection { 14 background: #b3d4fc; 15 text-shadow: none; 16 } 17 18 html { 19 padding: 30px 10px; 20 font-size: 20px; 21 line-height: 1.4; 22 color: #737373; 23 background: #f0f0f0; 24 font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 25 -webkit-text-size-adjust: 100%; 26 -ms-text-size-adjust: 100%; 27 } 28 29 body { 30 max-width: 550px; 31 _width: 550px; 32 padding: 30px 20px 50px; 33 border: 1px solid #b3b3b3; 34 border-radius: 4px; 35 margin: 0 auto; 36 box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff; 37 background: #fcfcfc; 38 } 39 40 h1 { 41 margin: 0 10px; 42 font-size: 50px; 43 text-align: center; 44 } 45 46 h1 span { 47 color: #bbb; 48 } 49 50 h3 { 51 margin: 1.5em 0 0.5em; 52 } 53 54 p { 55 margin: 1em 0; 56 } 57 58 ul { 59 padding: 0 0 0 40px; 60 margin: 1em 0; 61 } 62 63 .container { 64 max-width: 500px; 65 _width: 500px; 66 margin: 0 auto; 67 } 68 </style> 69 </head> 70 <body> 71 <div class="container"> 72 <h1>沒有找到<span>:(</span></h1> 73 <p>抱歉,您試圖訪問的頁面不存在</p> 74 <p>多是以下緣由:</p> 75 <ul> 76 <li>一個錯誤的地址</li> 77 <li>一個過期的連接</li> 78 </ul> 79 </div> 80 </body> 81 </html>
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta http-equiv="refresh" content="5;URL=/login"> 6 <title>500</title> 7 <style> 8 ::-moz-selection { 9 background: #b3d4fc; 10 text-shadow: none; 11 } 12 13 ::selection { 14 background: #b3d4fc; 15 text-shadow: none; 16 } 17 18 html { 19 padding: 30px 10px; 20 font-size: 20px; 21 line-height: 1.4; 22 color: #737373; 23 background: #f0f0f0; 24 font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 25 -webkit-text-size-adjust: 100%; 26 -ms-text-size-adjust: 100%; 27 } 28 29 body { 30 max-width: 550px; 31 _width: 550px; 32 padding: 30px 20px 50px; 33 border: 1px solid #b3b3b3; 34 border-radius: 4px; 35 margin: 0 auto; 36 box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff; 37 background: #fcfcfc; 38 } 39 40 h1 { 41 margin: 0 10px; 42 font-size: 50px; 43 text-align: center; 44 } 45 46 h1 span { 47 color: #bbb; 48 } 49 50 h3 { 51 margin: 1.5em 0 0.5em; 52 } 53 54 p { 55 margin: 1em 0; 56 } 57 58 ul { 59 padding: 0 0 0 40px; 60 margin: 1em 0; 61 } 62 63 .container { 64 max-width: 500px; 65 _width: 500px; 66 margin: 0 auto; 67 } 68 </style> 69 </head> 70 <body> 71 <div class="container"> 72 <h1>內部錯誤</h1> 73 <p>抱歉,服務器上出現了錯誤......</p> 74 </div> 75 </body> 76 </html>
六、編寫登陸頁面login.html、首頁頁面index.html、公共模塊頁page_public.html、領導模塊頁page_leader.html 和 管理員模塊頁page_admin.html
1 <!DOCTYPE html> 2 <html xmlns:th="http://www.thymeleaf.org"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>系統登陸</title> 6 </head> 7 <body> 8 <div th:text="${msg}" style="color: red"></div> 9 <form action="doLogin" method="post"> 10 賬號:<input type="text" id="txtUsername" name="username" /><br/> 11 密碼:<input type="password" id="txtPassword" name="password" /><br/><br/> 12 <input type="submit" value="提交" /> <input type="reset" value="重置" /> 13 </form> 14 </body> 15 </html>
1 <!DOCTYPE html> 2 <html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>系統首頁</title> 6 </head> 7 <body> 8 <div th:text="${'歡迎您,' + currentuser}" style="color: red;float: left;"></div> 9 <div style="color: red;float: right;"><a href="doLogout">註銷</a></div> 10 <!-- 11 Thymeleaf中使用Shiro標籤,具有受權才能看見 12 注意:若是不適用Shiro標籤,沒有受權的訪問將產生401響應嗎,執行ErrorPageConfig類 和 ErrorController類處理 13 --> 14 <!--<div style="clear: both;">公共模塊:<a href="publicModule">公共模塊</a></div>--> 15 <!--<div style="clear: both;">領導模塊:<a href="leaderModule">領導模塊</a></div>--> 16 <!--<div style="clear: both;">管理員模塊:<a href="adminModule">管理員模塊</a></div>--> 17 <div style="clear: both;" shiro:hasPermission="user:publicModule">公共模塊:<a href="publicModule">公共模塊</a></div> 18 <div style="clear: both;" shiro:hasPermission="user:leaderModule">領導模塊:<a href="leaderModule">領導模塊</a></div> 19 <div style="clear: both;" shiro:hasPermission="user:adminModule">管理員模塊:<a href="adminModule">管理員模塊</a></div> 20 </body> 21 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>公共模塊</title> 6 </head> 7 <body> 8 公共模塊(管理員、領導、員工都可訪問) 9 </body> 10 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>領導模塊</title> 6 </head> 7 <body> 8 領導模塊(管理員、領導都可訪問) 9 </body> 10 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>管理員模塊</title> 6 </head> 7 <body> 8 管理員模塊(管理員可訪問) 9 </body> 10 </html>
七、編寫Shiro框架用配置類ShiroConfig.java 和 自定義Realm類MyRealm.java
配置類ShiroConfig.java
1 package cn.temptation.shiro; 2 3 import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; 4 import cn.temptation.dao.ResourceDao; 5 import cn.temptation.domain.Resource; 6 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 7 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.beans.factory.annotation.Qualifier; 10 import org.springframework.context.annotation.Bean; 11 import org.springframework.context.annotation.Configuration; 12 13 import java.util.LinkedHashMap; 14 import java.util.List; 15 import java.util.Map; 16 17 /** 18 * Shiro配置類 19 */ 20 @Configuration 21 public class ShiroConfig { 22 @Autowired 23 private ResourceDao resourceDao; 24 25 // 一、建立ShiroFilterFactoryBean 26 @Bean 27 public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { 28 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 29 // 設置安全管理器 30 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); 31 32 // 設置登陸跳轉頁面 33 shiroFilterFactoryBean.setLoginUrl("/login"); 34 35 /** 36 * Shiro內置過濾器:實現權限相關的攔截 37 * 經常使用過濾器: 38 * anon(認證用):無需認證(登陸)便可訪問 39 * authc(認證用):必須認證纔可訪問 40 * user(少用):使用rememberMe功能能夠訪問 41 * perms(受權用):必須獲得資源權限纔可訪問 42 * role(受權用):必須獲得角色權限纔可訪問 43 */ 44 Map<String, String> filterMap = new LinkedHashMap<>(); 45 46 // 放行登陸請求 47 filterMap.put("/doLogin", "anon"); 48 49 // 配置退出過濾器,退出代碼Shiro已經實現 50 filterMap.put("/logout", "logout"); 51 52 // 配置受權過濾器 53 54 // 先代碼寫死,測試下 55 // filterMap.put("/publicModule", "perms[user:publicModule]"); 56 // filterMap.put("/leaderModule", "perms[user:leaderModule]"); 57 // filterMap.put("/adminModule", "perms[user:adminModule]"); 58 59 // 獲取全部資源,並配置須要進行受權過濾的資源 60 List<Resource> resources = resourceDao.findAll(); 61 resources.forEach(item -> { 62 if (!"".equals(item.getResourceurl())) { 63 filterMap.put("/" + item.getResourceurl(), "perms[user:" + item.getResourceurl() + "]"); 64 } 65 }); 66 67 // 過濾鏈定義,從上向下順序執行,通常將/*放在最下邊 68 filterMap.put("/*", "authc"); 69 70 // 設置未受權界面 71 shiroFilterFactoryBean.setUnauthorizedUrl("/error/401"); 72 73 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); 74 75 return shiroFilterFactoryBean; 76 } 77 78 // 二、建立DefaultWebSecurityManager 79 @Bean(name = "securityManager") 80 public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") MyRealm myRealm) { 81 DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); 82 83 // 關聯Realm 84 defaultWebSecurityManager.setRealm(myRealm); 85 86 return defaultWebSecurityManager; 87 } 88 89 // 三、建立Realm 90 @Bean(name = "myRealm") 91 public MyRealm getRealm() { 92 return new MyRealm(); 93 } 94 95 // 四、配置ShiroDialect後,能夠在頁面使用Shiro標籤 96 @Bean 97 public ShiroDialect getShiroDialect() { 98 return new ShiroDialect(); 99 } 100 }
自定義Realm類MyRealm.java
1 package cn.temptation.shiro; 2 3 import cn.temptation.dao.ResourceDao; 4 import cn.temptation.dao.UserDao; 5 import cn.temptation.domain.User; 6 import org.apache.shiro.authc.*; 7 import org.apache.shiro.authz.AuthorizationInfo; 8 import org.apache.shiro.authz.SimpleAuthorizationInfo; 9 import org.apache.shiro.realm.AuthorizingRealm; 10 import org.apache.shiro.subject.PrincipalCollection; 11 import org.springframework.beans.factory.annotation.Autowired; 12 13 import java.util.List; 14 15 /** 16 * 自定義Realm 17 */ 18 public class MyRealm extends AuthorizingRealm { 19 @Autowired 20 private UserDao userDao; 21 @Autowired 22 private ResourceDao resourceDao; 23 24 // 受權處理 25 @Override 26 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 27 // 獲取當前登陸得到認證的用戶 28 User user = (User) principalCollection.getPrimaryPrincipal(); 29 // 下句語句會拋出異常交由ErrorController類根據ErrorPageConfig類中註冊的響應碼和錯誤頁面處理 30 // System.out.println(1 / 0); 31 32 if (user != null) { 33 // 給資源受權 34 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 35 36 // 先代碼寫死,測試下 37 // info.addStringPermission("user:publicModule"); 38 // info.addStringPermission("user:leaderModule"); 39 // info.addStringPermission("user:adminModule"); 40 41 // 根據得到認證的用戶編號查詢該用戶具有的資源URL集合 42 List<String> resourceurls = resourceDao.findByUserid(user.getUserid()); 43 44 // 遍歷集合,組裝成知足受權過濾器過濾格式,並添加到資源信息中 45 resourceurls.forEach(item -> info.addStringPermission("user:" + item)); 46 47 return info; 48 } 49 50 return null; 51 } 52 53 // 認證處理 54 @Override 55 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 56 // 編寫Shiro判斷邏輯,判斷帳號和密碼 57 // 一、判斷帳號 58 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; 59 60 User user = userDao.findByUsername(token.getUsername()); 61 if (user == null) { 62 // 帳號錯誤,Shiro底層會拋出UnknownAccountException異常 63 return null; 64 } 65 66 // 二、判斷密碼 67 // 只作認證,principal能夠設置爲空字符串 68 // return new SimpleAuthenticationInfo("", user.getPassword(), ""); 69 // 認證後作受權處理,須要將得到認證的用戶對象賦值給principal,受權處理時會用到 70 return new SimpleAuthenticationInfo(user, user.getPassword(), ""); 71 } 72 }
八、編寫用戶實體類User.java、角色實體類Role.java 和 資源實體類Resource.java
用戶實體類User.java
1 package cn.temptation.domain; 2 3 import javax.persistence.*; 4 5 @Entity 6 @Table(name = "sys_user") 7 public class User { 8 @Id 9 @GeneratedValue(strategy = GenerationType.IDENTITY) 10 @Column(name = "userid") 11 private Integer userid; 12 13 @Column(name = "username") 14 private String username; 15 16 @Column(name = "password") 17 private String password; 18 19 @ManyToOne 20 @JoinColumn(name = "roleid", foreignKey = @ForeignKey(name = "none")) 21 private Role role; 22 23 public Integer getUserid() { 24 return userid; 25 } 26 27 public void setUserid(Integer userid) { 28 this.userid = userid; 29 } 30 31 public String getUsername() { 32 return username; 33 } 34 35 public void setUsername(String username) { 36 this.username = username; 37 } 38 39 public String getPassword() { 40 return password; 41 } 42 43 public void setPassword(String password) { 44 this.password = password; 45 } 46 47 public Role getRole() { 48 return role; 49 } 50 51 public void setRole(Role role) { 52 this.role = role; 53 } 54 }
角色實體類Role.java
1 package cn.temptation.domain; 2 3 import javax.persistence.*; 4 import java.util.Set; 5 6 @Entity 7 @Table(name = "sys_role") 8 public class Role { 9 @Id 10 @GeneratedValue(strategy = GenerationType.IDENTITY) 11 @Column(name = "roleid") 12 private Integer roleid; 13 14 @Column(name = "rolename") 15 private String rolename; 16 17 @ManyToMany 18 @JoinTable(name = "sys_role_resource", 19 joinColumns = {@JoinColumn(name = "roleid", referencedColumnName = "roleid", foreignKey = @ForeignKey(name = "none"))}, 20 inverseJoinColumns = {@JoinColumn(name = "resourceid", referencedColumnName = "resourceid", foreignKey = @ForeignKey(name = "none"))}) 21 private Set<Resource> resources; 22 23 public Integer getRoleid() { 24 return roleid; 25 } 26 27 public void setRoleid(Integer roleid) { 28 this.roleid = roleid; 29 } 30 31 public String getRolename() { 32 return rolename; 33 } 34 35 public void setRolename(String rolename) { 36 this.rolename = rolename; 37 } 38 }
資源實體類Resource.java
1 package cn.temptation.domain; 2 3 import javax.persistence.*; 4 5 @Entity 6 @Table(name = "sys_resource") 7 public class Resource { 8 @Id 9 @GeneratedValue(strategy = GenerationType.IDENTITY) 10 @Column(name = "resourceid") 11 private Integer resourceid; 12 13 @Column(name = "resourcename") 14 private String resourcename; 15 16 @Column(name = "resourceurl") 17 private String resourceurl; 18 19 public Integer getResourceid() { 20 return resourceid; 21 } 22 23 public void setResourceid(Integer resourceid) { 24 this.resourceid = resourceid; 25 } 26 27 public String getResourcename() { 28 return resourcename; 29 } 30 31 public void setResourcename(String resourcename) { 32 this.resourcename = resourcename; 33 } 34 35 public String getResourceurl() { 36 return resourceurl; 37 } 38 39 public void setResourceurl(String resourceurl) { 40 this.resourceurl = resourceurl; 41 } 42 }
九、編寫用戶控制器類UserController.java
1 package cn.temptation.web; 2 3 import org.apache.shiro.SecurityUtils; 4 import org.apache.shiro.authc.IncorrectCredentialsException; 5 import org.apache.shiro.authc.UnknownAccountException; 6 import org.apache.shiro.authc.UsernamePasswordToken; 7 import org.apache.shiro.subject.Subject; 8 import org.springframework.stereotype.Controller; 9 import org.springframework.ui.Model; 10 import org.springframework.web.bind.annotation.RequestMapping; 11 12 @Controller 13 public class UserController { 14 // 訪問登陸頁 15 @RequestMapping("/login") 16 public String login() { 17 // 下句語句會拋出異常交由GlobalExceptionHandler類的errorHandler方法處理 18 // System.out.println(1 / 0); 19 20 return "login"; 21 } 22 23 // 訪問首頁 24 @RequestMapping("/index") 25 public String index() { 26 return "index"; 27 } 28 29 // 訪問公共模塊 30 @RequestMapping("/publicModule") 31 public String publicModule() { 32 return "page_public"; 33 } 34 35 // 訪問私密模塊 36 @RequestMapping("/privateModule") 37 public String privateModule() { 38 return "page_leader"; 39 } 40 41 // 登陸處理 42 @RequestMapping("/doLogin") 43 public String doLogin(String username, String password, Model model) { 44 // 使用Shiro編寫認證處理 45 // 一、獲取Subject 46 Subject subject = SecurityUtils.getSubject(); 47 48 // 二、封裝用戶數據 49 UsernamePasswordToken token = new UsernamePasswordToken(username, password); 50 51 // 三、執行登陸 52 try { 53 // 登陸成功 54 subject.login(token); 55 56 // 返回當前用戶的賬號 57 model.addAttribute("currentuser", token.getUsername()); 58 59 return "index"; 60 } catch (UnknownAccountException exception) { 61 // 返回錯誤信息 62 model.addAttribute("msg", "帳號錯誤!"); 63 64 return "login"; 65 } catch (IncorrectCredentialsException exception) { 66 // 返回錯誤信息 67 model.addAttribute("msg", "密碼錯誤!"); 68 69 return "login"; 70 } 71 } 72 73 // 註銷處理 74 @RequestMapping("/doLogout") 75 public String doLogout() { 76 // 一、獲取Subject 77 Subject subject = SecurityUtils.getSubject(); 78 79 // 二、執行註銷 80 try { 81 subject.logout(); 82 } catch (Exception ex) { 83 ex.printStackTrace(); 84 } finally { 85 return "login"; 86 } 87 } 88 }
十、編寫用戶數據訪問接口UserDao.java、角色數據訪問接口RoleDao.java 和 資源數據訪問接口ResourceDao.java
用戶數據訪問接口UserDao.java
1 package cn.temptation.dao; 2 3 import cn.temptation.domain.User; 4 import org.springframework.data.jpa.repository.JpaRepository; 5 import org.springframework.data.jpa.repository.Query; 6 import org.springframework.data.repository.query.Param; 7 8 public interface UserDao extends JpaRepository<User, Integer> { 9 // 根據帳號查詢用戶 10 @Query(value = "SELECT * FROM sys_user WHERE username=:username", nativeQuery = true) 11 User findByUsername(@Param("username") String username); 12 }
角色數據訪問接口RoleDao.java
1 package cn.temptation.dao; 2 3 import cn.temptation.domain.Role; 4 import org.springframework.data.jpa.repository.JpaRepository; 5 6 public interface RoleDao extends JpaRepository<Role, Integer> { 7 8 }
資源數據訪問接口ResourceDao.java
1 package cn.temptation.dao; 2 3 import cn.temptation.domain.Resource; 4 import org.springframework.data.jpa.repository.JpaRepository; 5 import org.springframework.data.jpa.repository.Query; 6 import org.springframework.data.repository.query.Param; 7 8 import java.util.List; 9 10 public interface ResourceDao extends JpaRepository<Resource, Integer> { 11 @Query(value = "SELECT rs.resourceurl FROM sys_role_resource AS rr " + 12 "INNER JOIN sys_resource AS rs ON rr.resourceid = rs.resourceid " + 13 "INNER JOIN sys_role AS r ON rr.roleid = r.roleid " + 14 "INNER JOIN sys_user AS u ON u.roleid = r.roleid WHERE u.userid = :userid ", nativeQuery = true) 15 List<String> findByUserid(@Param("userid") Integer userid); 16 }
十一、項目結構
十二、運行效果