【原】無腦操做:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf實現基礎受權權限

上一篇《【原】無腦操做: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>
error_401
 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>
error_404
 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>
error_500

 

六、編寫登陸頁面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="提交" />&nbsp;<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 }

 

十一、項目結構

 

 

十二、運行效果

 

相關文章
相關標籤/搜索