• 【原】无脑操作:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf实现基础授权权限


    上一篇《【原】无脑操作:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf实现基础认证权限》介绍了实现Shiro的基础认证。本篇谈谈实现Shiro的基础授权。

    需求:

    ① 某系统有公共模块、领导模块、管理员模块三个业务模块,均需要登录系统后才可以访问。

    ② admin、leader、employee三个人职位分别是管理员、领导、员工,均可登录系统。

    ③ 不同职位的人登录系统后,能看到的功能模块不同。管理员可以访问全部三个模块。领导可以访问除去管理员模块外的两个模块。员工只能访问公共模块。

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    分析:

    典型的运用授权权限的需求,继续考虑使用Shiro。

    问题1、认证和授权怎么理解呢?

    答:一点粗浅理解,比如通过了美国的签证能进入美国了,这就是获得了认证。

    但是进入美国了,也只能去有授权的地方玩玩,五角大楼能进么?没有授权是不给进的。

    所以,授权是在认证获得后进一步的安全管理。

    问题2、需求在描述什么场景?

    答:需求中包含了基于角色的权限访问控制RBAC(Role-Based Access Control)的设计思路。

    简单来说,单个人对某某资源可操作。

    进一步考虑,如果是多个人对某某资源可操作呢?需要重复的这样设置么?运用归纳思想,把这样的多个人归为一类,形成了角色的概念。即这一角色的多个人对某某资源可操作。

    RBAC认为权限授权实际上是Who、What、How的问题。在RBAC模型中,who、what、how构成了访问权限三元组,也就是“Who对What(Which)进行How的操作”。

    问题3、针对本需求的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;

    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>

    2、编写项目配置文件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

    3、编写项目启动类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 }

    4、编写全局异常处理类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 }

    5、编写错误页配置类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 }

    6、编写错误页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

    6、编写登录页面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>
    管理员模块页

    7、编写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     // 1、创建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     // 2、创建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     // 3、创建Realm
     90     @Bean(name = "myRealm")
     91     public MyRealm getRealm() {
     92         return new MyRealm();
     93     }
     94 
     95     // 4、配置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         // 1、判断账号
    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         // 2、判断密码
    67         // 只做认证,principal可以设置为空字符串
    68 //        return new SimpleAuthenticationInfo("", user.getPassword(), "");
    69         // 认证后做授权处理,需要将获得认证的用户对象赋值给principal,授权处理时会用到
    70         return new SimpleAuthenticationInfo(user, user.getPassword(), "");
    71     }
    72 }

    8、编写用户实体类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 }

    9、编写用户控制器类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         // 1、获取Subject
    46         Subject subject = SecurityUtils.getSubject();
    47 
    48         // 2、封装用户数据
    49         UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    50 
    51         // 3、执行登录
    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         // 1、获取Subject
    77         Subject subject = SecurityUtils.getSubject();
    78 
    79         // 2、执行注销
    80         try {
    81             subject.logout();
    82         } catch (Exception ex) {
    83             ex.printStackTrace();
    84         } finally {
    85             return "login";
    86         }
    87     }
    88 }

    10、编写用户数据访问接口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 }

    11、项目结构

    12、运行效果

  • 相关阅读:
    linux下mysql的root密码忘记解决方法
    基于NIO2的遍历文件夹简单复制
    bootstrap插件bootstrapValidator常用验证规则总结
    《程序员修炼之道》-读书笔记六-工厂模式下的伪DI依赖注入
    cpu 突增问题排查
    一次线上OOM 的定位
    熔断、限流、降级的区别
    Mac 下 idea 在同一个窗口下打开多个项目
    No operations allowed after connection closed
    java 死锁演示
  • 原文地址:https://www.cnblogs.com/iflytek/p/9847809.html
Copyright © 2020-2023  润新知