【原】无脑操作: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>
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>
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="提交" /> <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、运行效果