兴趣的朋友能够去了解一下前几篇,你的赞就是对我最大的支持,感谢你们!css
(一) SpringBoot起飞之路-HelloWorldhtml
(二) SpringBoot起飞之路-入门原理分析前端
(三) SpringBoot起飞之路-YAML配置小结(入门必知必会)java
(四) SpringBoot起飞之路-静态资源处理mysql
(五) SpringBoot起飞之路-Thymeleaf模板引擎git
(六) SpringBoot起飞之路-整合JdbcTemplate-Druid-MyBatisgithub
(七) SpringBoot起飞之路-整合SpringSecurityweb
说明:spring
SpringBoot 起飞之路 系列文章的源码,均同步上传到 github 了,有须要的小伙伴,随意去 downsql
权限以及安全问题,虽然并非一个影响到程序、项目运行的必须条件,可是倒是开发中的一项重要考虑因素,例如某些资源咱们不想被访问到或者咱们某些方法想要知足指定身份才能够访问,咱们可使用 AOP 或者过滤器来实现要求,可是实际上,若是代码涉及的逻辑比较多之后,代码是极其繁琐,冗余的,而有不少开发框架,例如 Spring Security,Shiro,已经为咱们提供了这种功能,咱们只须要知道如何正确配置以及使用它了
官网:http://shiro.apache.org/
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro™是一个功能强大且易于使用的Java安全框架,可执行身份验证、受权、加密和会话管理。经过Shiro易于理解的API,您能够快速、轻松地保护任何应用程序——从最小的移动应用程序到最大的web和企业应用程序。
简单梳理一下:
这部分的内容,说实话,刚入门简单扫两眼就好了,只有你真的敲过一次代码了,你才大概对其中某些部分能有个印象,再继续深刻研究才可能有比较好的掌握
Shiro框架中有三个核心组件:Subject ,SecurityManager和Realms
Subject 是一个安全术语,表明认证主体,通常来讲能够简单的理解为,当前操做的用户,不过用户这个概念实际上也不是很准确,由于 Subject 实际上不必定是人,也能够是一些例如第三方进程或者定时做业等等的事物,也就是理解为,当前同软件交互的事物。
既然 Shiro 是一个安全权限技术,简单来讲,就是对程序中被访问的资源或者请求进行必定程度的控制,而如何划分就涉及到这三个概念:用户、角色、权限
用户(User):没啥好说的,表明当前 Subject 认证主体,例如某些内容必须用户登陆后才能够访问
角色(Role):这表明用户担任的角色,身份,一个角色能够有多个权限,例如这一块只有管理员能够访问
权限(Permission):也就是操做资源的具体的权利,例如对数据进行添加、修改、删除、查看操做
补充:其实能够简单的理解,角色就是一些权限的集合组成的,正是这一堆权限已经将这个角色能作的事情限定死了,不用每次都说明这个角色能够作什么
页面是我本身临时弄得,有须要的朋友能够去我 GitHub:ideal-20 下载源码,简单说明一下这个页面
作一个静态页面若是嫌麻烦,也能够单纯的本身建立一些简单的页面,写几个标题文字,能体现出当前是哪一个页面就行了
我代码中用的这些页面,就是拿开源的前端组件框架进行了一点的美化,而后方便讲解一些功能,页面模板主要是配合 Thymeleaf
一、目录结构
├── index.html // 首页 ├── images // 首页图片,仅美观,无实际做用 ├── css ├── js ├── views // 总子页面文件夹,权限验证的关键页面 │ ├── login.html // 登陆页面 │ ├── success.html // 成功页面 │ ├── unauthorized.html // 未受权页面:此部分未受权的用户访问资源,跳转到此页面 │ ├── L-A // L-A 子页面文件夹,下含 a b c 三个子页面 │ │ ├── a.html │ │ ├── b.html │ │ ├── c.html | ├── L-B // L-B 子页面文件夹,下含 a b c 三个子页面 │ │ ├── a.html │ │ ├── b.html │ │ ├── c.html | ├── L-C // L-C 子页面文件夹,下含 a b c 三个子页面 │ │ ├── a.html │ │ ├── b.html │ │ ├── c.html
主要就是把基本一些连接,引入什么的先替换成 Thymeleaf 的标签格式,这里语法用的不是特别多,即便对于 Thymeleaf 不是很熟悉也是很容易看懂的,固然若是仍然感受有点吃力,能够单纯的作成 html,将就一下,或者去看一下我之前的文章哈,里面有关于 Thymeleaf 入门的讲解
css、image、js 放到 resources --> static 下 ,views 和 index.html 放到 resources --> templates下
这一部分引入也好,初始化项目的时候,勾选好自动生成也好,只要依赖正常导入了便可
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency>
关键的依赖主要就是上面这个启动器,可是还有一些就是常规或者补充的了,例如 web、thymeleaf、devtools 等等,还有一些例如 Mybatis 等我都放进来了,下面的依赖基本已经全了,具体讲到某块,具体再说
thymeleaf-extras-shiro 这个后面讲解中会提到,是用来配合 Thymeleaf 整合 Shiro 的
<dependencies> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
由于咱们用了模板,页面的跳转就须要交给 Controller 了,很简单,首先是首页的,固然关于页面这个就无所谓了,我随便跳转到了个人博客,接着还有登陆页面、成功,未受权页面的跳转
有一个小 Tip 须要提一下,由于 L-A、L-B、L-C 文件夹下都有3个页面 a.html 、b.html 、c.html,因此能够利用 @PathVariable 写一个较为通用的跳转方法
@Controller public class PageController { @RequestMapping({"/", "index"}) public String index() { return "index"; } @RequestMapping("/about") public String toAboutPage() { return "redirect:http://www.ideal-20.cn"; } @RequestMapping("/toLoginPage") public String toLoginPage() { return "views/login"; } @RequestMapping("/levelA/{name}") public String toLevelAPage(@PathVariable("name") String name) { return "views/L-A/" + name; } @RequestMapping("/levelB/{name}") public String toLevelBPage(@PathVariable("name") String name) { return "views/L-B/" + name; } @RequestMapping("/levelC/{name}") public String toLevelCPage(@PathVariable("name") String name) { return "views/L-C/" + name; } @RequestMapping("/unauthorized") public String toUnauthorizedPage() { return "views/unauthorized"; } @RequestMapping("/success") public String toSuccessPage() { return "views/success"; } }
一、首页
二、子页面
L-A、L-B、L-C 下的 a.html 、b.html 、c.html 都是同样的,只是文字有一点变化
三、登录页面
四、成功及未受权页面
我截了个图,把两个页面拼接到一块儿了,没啥好说的,就是两个很普通的H5页面
-- ---------------------------- -- Table structure for role -- ---------------------------- CREATE TABLE `role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色表主键', `role_name` varchar(32) DEFAULT NULL COMMENT '角色名称', PRIMARY KEY (`id`) ); -- ---------------------------- -- Records of role -- ---------------------------- INSERT INTO `role` VALUES (1, 'SUPER_ADMIN'); INSERT INTO `role` VALUES (2, 'ADMIN'); INSERT INTO `role` VALUES (3, 'USER'); -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键', `username` varchar(32) NOT NULL COMMENT '用户名', `password` varchar(32) NOT NULL COMMENT '密码', `role_id` int(11) DEFAULT NULL COMMENT '与role角色表联系的外键', PRIMARY KEY (`id`), CONSTRAINT `user_role_on_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ); -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (1, 'BWH_Steven', '666666', 1); INSERT INTO `user` VALUES (2, 'admin', '666666', 2); INSERT INTO `user` VALUES (3, 'zhangsan', '666666', 3); -- ---------------------------- -- Table structure for permission -- ---------------------------- CREATE TABLE `permission` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限表主键', `permission_name` varchar(50) NOT NULL COMMENT '权限名', `role_id` int(11) DEFAULT NULL COMMENT '与role角色表联系的外键', PRIMARY KEY (`id`), CONSTRAINT `permission_role_on_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ); -- ---------------------------- -- Records of permission -- ---------------------------- INSERT INTO `permission` VALUES (1, 'user:*', 1); INSERT INTO `permission` VALUES (2, 'user:*', 2); INSERT INTO `permission` VALUES (3, 'user:queryAll', 3);
在数据库中角色表,在用户表和权限表分别是有一个外键的概念,因此在实体中就写成了引用的形式
角色类
@Data @AllArgsConstructor @NoArgsConstructor @Getter @Setter public class Role { private int id; private String roleName; }
用户类,说明:因为我在其余模块下有一些同名的类,调用的时候常常会有一些误会,因此就稍微改了下名字 --> UserPojo,这里你们起 User 就 OK
@Data @AllArgsConstructor @NoArgsConstructor @Getter @Setter public class UserPojo { private int id; private String username; private String password; private Role role; }
权限类
@Data @AllArgsConstructor @NoArgsConstructor @Getter @Setter public class Permission { private Integer id; private String permissionName; private Role role; }
今天要作的内容,实际上本身随便模拟两个数据也是能够的,不过为了贴近现实,仍是引入了 Mybaits
先引入 MyBatis 依赖,还有驱动依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
链接池啥的就不折腾了,想本身换就本身配置一下哈
spring: datasource: username: root password: root99 url: jdbc:mysql://localhost:3306/springboot_shiro_test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*Mapper.xml type-aliases-package: cn.ideal.pojo server: port: 8080
具体的 Mapper 这里还没写,讲解的过程当中,按照流须要,再写上去
由于代码是在文章以前写好的,咱们在后面会用到利用 username 进行查询用户和权限的方法,因此,咱们就按这样写就行了
@Mapper public interface UserMapper { UserPojo queryUserByUsername(@Param("username") String username); Permission queryPermissionByUsername(@Param("username") String username); }
具体的 XML 配置 sql
这部分涉及到多表的一个稍复杂的查询,若是感受有点吃力,能够去回顾一下前面的知识,或者干脆无论也能够,接着看后面的,纯了解 Shiro 也能够
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="cn.ideal.mapper.UserMapper"> <!-- 定义封装 User和 role 的 resultMap --> <resultMap id="userRoleMap" type="cn.ideal.pojo.UserPojo"> <id property="id" column="id"/> <result property="username" column="username"></result> <result property="password" column="password"></result> <!-- 配置封装 UserPojo 的内容 --> <association property="role" javaType="cn.ideal.pojo.Role"> <id property="id" column="id"></id> <result property="roleName" column="role_name"></result> </association> </resultMap> <!-- 定义封装 permission 和 role 的 resultMap --> <resultMap id="permissionRoleMap" type="cn.ideal.pojo.Permission"> <id property="id" column="id"/> <result property="permissionName" column="permission_name"></result> <!-- 配置封装 Role 的内容 --> <association property="role" javaType="cn.ideal.pojo.Role"> <id property="id" column="id"></id> <result property="roleName" column="role_name"></result> </association> </resultMap> <select id="queryUserByUsername" resultMap="userRoleMap"> SELECT u.*,r.role_name FROM `user` u, `role` r WHERE username = #{username} AND u.role_id = r.id; </select> <select id="queryPermissionByUsername" resultMap="permissionRoleMap"> SELECT p.* ,r.role_name FROM `user` u, `role` r, `permission` p WHERE username = #{username} AND u.role_id = r.id AND p.role_id = r.id; </select> </mapper>
@SpringBootTest class Springboot13ShiroMybatisApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads() { UserPojo admin = userMapper.queryUserByUsername("admin"); System.out.println(admin.toString()); Permission permission = userMapper.queryPermissionByUsername("admin"); System.out.println(permission.toString()); } }
首先咱们须要建立Shiro的配置类,在config包下建立一个名为 ShiroConfig 的配置类
@Configuration public class ShiroConfig { // 一、ShiroFilterFactoryBean // 二、DefaultWebSecurityManager // 三、Realm 对象(自定义) }
上面注释能够看出,咱们须要在配置类中建立这样几个内容,因为他们几个之间存在关联,例如在 Manager 中关联本身建立的 Realm,在最上面的过滤器,又关联了中间这个 Manager,因此咱们选择倒着写,先写后面的(也就是被引用最先的 Realm),这样就能够一层一层的在前面引用后面已经写好的,会更舒服一些
首先,在 ShiroConfig 配置类中编写一个方法用来获取 Realm ,直接返回一个实例化的 userRealm() 就能够了
/** * 建立 realm 对象,须要本身定义 * * @return */ @Bean public UserRealm userRealm() { return new UserRealm(); }
具体内容,咱们须要建立一个新的类来定义
咱们自定义了一个 UserRealm类,同时继承 AuthorizingRealm 类,接着就须要实现两个方法:
根据上面的介绍也很好理解,确定是认证先行,接着才会执行受权方法,因此咱们先来编写认证的代码
认证首先就要先获取到咱们前台传来的数据,这块很显然,交给 Controller 来作,咱们先来完成这个内容,再回来编写认证
说明:获取前台的数据就是下面的 login 方法,同时在其中调用了认证的方法,其余几个方法,只是为了后期演示的时候使用,一块给出来了,同时下面登陆方法中我捕获了全部异常,你们能够本身更细致的划分,同时因为为了演示重点,我前台没有作太多的处理,例如session中传入一些登陆失败等的字符串,彻底不写也是能够的哈
@Controller public class UserController { @RequestMapping("/user/queryAll") @ResponseBody public String queryAll() { return "这是 user/queryAll 方法"; } @RequestMapping("/user/admin/add") @ResponseBody public String adminAdd() { return "这是 user/adminAdd 方法"; } @RequestMapping("/login") public String login(String username, String password, HttpServletRequest request) { // 因为是根据name参数获取的,我这里封装了一下 UserPojo user = new UserPojo(); user.setUsername(username); user.setPassword(password); // 建立出一个 Token 内容本质基于前台的用户名和密码(不必定正确) UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 获取 subject 认证主体(这里也就是如今登陆的用户) Subject subject = SecurityUtils.getSubject(); try{ // 认证开始,这里会跳转到自定义的 UserRealm 中 subject.login(token); // 能够存储到 session 中 request.getSession().setAttribute("user", user); return "views/success"; }catch(Exception e){ // 捕获异常 e.printStackTrace(); request.getSession().setAttribute("user", user); request.setAttribute("errorMsg", "兄弟,用户名或密码错误"); return "views/login"; } } }
UserRealm 下的认证方法:
说明:经过方法参数中的 token 就能够获取到咱们刚才的那个 token信息,最方便的方法就是下面,直接经过 getPrincipal() 获取到用户名(Object 转 String),还有一种方法就是,将 Token 强转了 UsernamePasswordToken 类型,接着须要用户名或者密码等信息均可以经过 getxxx 的方法获取到
能够看到,咱们只须要将数据库中查询到的数据交给 Shiro 去作认证就能够了,具体细节都被封装了
补充:userService.queryUserByUsername(username) 方法只是调用返回了 UserMapper 中根据用户名查询用户信息的方法,只是为告终构完整,没涉及任何业务,若是不清楚,能够去 GitHub 看一下源码
/** * 认证 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 根据在接受前台数据建立的 Token 获取用户名 String username = (String) authenticationToken.getPrincipal(); // UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; // System.out.println(userToken.getPrincipal()); // System.out.println(userToken.getUsername()); // System.out.println(userToken.getPassword()); // 经过用户名查询相关的用户信息(实体) UserPojo user = userService.queryUserByUsername(username); if (user != null) { // 存入 Session,可选 SecurityUtils.getSubject().getSession().setAttribute("user", user); // 密码认证的工做,Shiro 来作 AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "userRealm"); return authenticationInfo; } else { // 返回 null 即会抛异常 return null; } }
受权,也就是在用户认证后,来设置用户的权限或者角色信息,这里主要是获取到用户名之后,经过 service 中调用 mapper 接着根据用户名查询用户或者权限,因为返回的是用户或者权限实体对象,因此配合 getxxx等方法就能够获取到须要的值了
固然了,最主要的仍是根据本身 mapper 以及表的返回状况设置,这里只要能获取到角色以及权限信息(这里是 String 类型)就能够了,若是是多个角色,就要使用 setRoles() 方法了,具体须要能够看参数和返回值,或者查阅文档,这里演示都是单个的
/** * 受权 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 获取用户名信息 String username = (String) principalCollection.getPrimaryPrincipal(); // 建立一个简单受权验证信息 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 给这个用户设置从 role 表获取到的角色信息 authorizationInfo.addRole(userService.queryUserByUsername(username).getRole().getRoleName()); //给这个用户设置从 permission 表获取的权限信息 authorizationInfo.addStringPermission(userService.queryPermissionByUsername(username).getPermissionName()); return authorizationInfo; }
受权和配置就写好了,也就是说 Realm 完事了,一个大头内容完成了,咱们接着就能够回到 Shiro 的配置中去了,继续倒着写,开始写关于第二点 Manager 的内容
@Configuration public class ShiroConfig { // 一、ShiroFilterFactoryBean // 二、DefaultWebSecurityManager // 三、Realm 对象(自定义) @Bean public UserRealm userRealm() { return new UserRealm(); } }
接着就来配置安全管理器(SecurityManager),这里就须要将刚才写好的 Realm 引入进来,这样 Shiro 就能够访问 Realm 了,而后接着返回
/** * 配置安全管理器 SecurityManager * * @return */ @Bean public DefaultWebSecurityManager securityManager() { // 将自定义 Realm 加进来 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 关联 Realm securityManager.setRealm(userRealm()); return securityManager; }
若是,setRealm 的时候直接调用下面的 userRealm() 出现了问题,那么能够考虑在方法参数中配合 @Qualifier 使用,它会自动去找下面 public UserRealm userRealm() 方法的方法名 userRealm,userRealm 中的注解不指定name也行,这里只是为了让你们看得更明白
@Bean public DefaultWebSecurityManager securityManager(@Qualifier("userRealm") UserRealm userRealm) { // 将自定义 Realm 加进来 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 关联 Realm securityManager.setRealm(userRealm); return securityManager; } @Bean(name="userRealm") public UserRealm userRealm() { return new UserRealm(); }
这又是一个关键的地方,首先建立一个 ShiroFilterFactoryBean 确定是毋庸置疑的,最后毕竟要返回这个对象,首先就是将刚才的 securityManager 关联进来了,也就是说层层调用,最终把 Realm 关联过来了,接着要写的就是重头戏了,咱们接着须要设置一些本身定义的内容
重点说一下拦截放行(Map)这块:经过 map 键值对的形式存储,key 存储 URL ,value 存储对应的一些权限或者角色等等,其实 key 这块仍是很好理解的,例如 :/css/**
、/user/admin/**
分别表明 css 文件夹下的全部文件,以及请求路径前缀为 /user/admin/
URL,而对应的 value 就有必定的规范了
关键:
补充:
/** * 配置 Shiro 过滤器 * * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { // 定义 shiroFactoryBean ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 关联 securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 自定义登陆页面,若是登陆的时候,就会执行这个请求,即跳转到登陆页 shiroFilterFactoryBean.setLoginUrl("/toLoginPage"); // 指定成功页面 shiroFilterFactoryBean.setSuccessUrl("/success"); // 指定未受权界面 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); // LinkedHashMap 是有序的,进行顺序拦截器配置 Map<String, String> filterChainMap = new LinkedHashMap<>(); // 配置能够匿名访问的地址,能够根据实际状况本身添加,放行一些静态资源等,anon 表示放行 filterChainMap.put("/css/**", "anon"); filterChainMap.put("/img/**", "anon"); filterChainMap.put("/js/**", "anon"); // 指定页面放行,例如登陆页面容许全部人登陆 filterChainMap.put("/toLoginPage", "anon"); // 以“/user/admin” 开头的用户须要身份认证,authc 表示要进行身份认证 filterChainMap.put("/user/admin/**", "authc"); filterChainMap.put("/levelA/**", "roles[USER]"); filterChainMap.put("/levelB/**", "roles[ADMIN]"); filterChainMap.put("/levelC/**", "roles[SUPER_ADMIN]"); // /user/admin/ 下的全部请求都要通过权限认证,只有权限为 user:[*] 的能够访问,也能够具体设置到 user:xxx filterChainMap.put("/user/admin/**", "perms[user:*]"); // 配置注销过滤器 filterChainMap.put("/logout", "logout"); // 将Map 存入过滤器 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap); return shiroFilterFactoryBean; }
其实上面的内容已经基本健全了,可是还有一个很棘手的问题,那就是,例如我主页中的三个模块,超级管理员A、B、C均可以访问,管理员能访问 A 和 B,而登陆后的普通用户只能访问 A,如何写呢?是否是像下面这样呢?
filterChainMap.put("/levelA/**", "roles[USER,ADMIN,SUPER_ADMIN]"); filterChainMap.put("/levelB/**", "roles[ADMIN,SUPER_ADMIN]"); filterChainMap.put("/levelC/**", "roles[SUPER_ADMIN]");
可是你一用,确定会发现问题,咱们来看一下关于 Role相关的过滤器代码,很显然关于 Role 的验证居然是经过 hasAllRoles 实现的,也就是说,咱们要知足全部的身份才能访问,不能达到,任选其一便可的效果
/** * Filter that allows access if the current user has the roles specified by the mapped value, or denies access * if the user does not have all of the roles specified. * * @since 0.9 */ public class RolesAuthorizationFilter extends AuthorizationFilter { //TODO - complete JavaDoc @SuppressWarnings({"unchecked"}) public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = getSubject(request, response); String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { //no roles specified, so nothing to check - allow access. return true; } Set<String> roles = CollectionUtils.asSet(rolesArray); return subject.hasAllRoles(roles); } }
自定义一个 Fileter,从新定义关于 Role 的验证方式,改为 hasRole 的方式
public class MyRolesAuthorizationFilter extends AuthorizationFilter { @SuppressWarnings({"unchecked"}) public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = getSubject(request, response); String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { return false; } List<String> roles = CollectionUtils.asList(rolesArray); boolean[] hasRoles = subject.hasRoles(roles); for (boolean hasRole : hasRoles) { if (hasRole) { return true; } } return false; } }
有了这个从新修改了规则的角色过滤器,咱们就能够继续回到配置中去,经过下面三行代码就能够讲这个新的规则的过滤器设置进去
// 设置自定义 filter Map<String, Filter> filterMap = new LinkedHashMap<>(); filterMap.put("anyRoleFilter", new MyRolesAuthorizationFilter()); shiroFilterFactoryBean.setFilters(filterMap);
天然,原来相应的Map定义就要变化了,配合自定义过滤器,改为多个角色的的形式
// 页面 -用户须要角色认证 filterChainMap.put("/levelA/**", "anyRoleFilter[USER,ADMIN,SUPER_ADMIN]"); filterChainMap.put("/levelB/**", "anyRoleFilter[ADMIN,SUPER_ADMIN]"); filterChainMap.put("/levelC/**", "anyRoleFilter[SUPER_ADMIN]");
主要内容已经结束了,不过由于在前面 Spring Security 中,讲过如何搭配 Thymeleaf 使用,因此接着补充一点关于如何用 Shiro 配合 Thymeleaf 的方法
首先引入二者整合的依赖:
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
这个版本已是最新的了(仍是很旧)2016年,具体能够去 maven repository 官网中查一下
注:这个依赖须要 thymeleaf 是 3.0 的版本,咱们的 Springboot 是用的最新的启动器,天然是 3.0 不过仍是提一下
接着在 Shiro 的主配置 ShiroConfig 类中加入这样的代码,这样,咱们就能够在 thymeleaf 中使用 Shiro 的自定义标签
/** * 整合 thymeleaf * @return */ @Bean(name = "shiroDialect") public ShiroDialect shiroDialect(){ return new ShiroDialect(); }
操做结束后,咱们就能够开始修改页面了,首先引入头部约束 xmlns:shiro="http://www.pollix.at/thymeleaf/shiro“
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
这里解决的问题,主要是登陆先后,顶部导航栏的一个显示问题,例如登陆前就应该显示登录,登陆后,就显示用户名和注销,若是须要更多的信息,我就建议存到 session ,这里我是直接使用 shiro:principal 标签获取的用户名
<div> <!-- 这里表明别的代码,下面只是节选 --> <!--登陆注销--> <div class="right menu"> <!--若是未登陆--> <!--<div shiro:authorize="!isAuthenticated()">--> <div shiro:notAuthenticated=""> <a class="item" th:href="@{/toLoginPage}"> <i class="address card icon"></i> 登陆 </a> </div> <!--若是已登陆--> <div shiro:authenticated=""> <a class="item"> <i class="address card icon"></i> 用户名:<span shiro:principal></span> <!--角色:<span sec:authentication="principal.authorities"></span>--> </a> </div> <div shiro:authenticated=""> <a class="item" th:href="@{/logout}"> <i class="address card icon"></i> 注销 </a> </div> </div> </div>
下面就是用来只显示对应模块的,例如用户登陆就只有 A能够访问,因此 B 和 C模块 就不给他显示了,反正这个模块他也不能访问
<div class="ui stackable three column grid"> <div class="column" shiro:hasAnyRoles="USER,ADMIN,SUPER_ADMIN"> <div class="ui raised segments"> <div class="ui segment"> <a th:href="@{/levelA/a}">L-A-a</a> </div> <div class="ui segment"> <a th:href="@{/levelA/b}">L-A-b</a> </div> <div class="ui segment"> <a th:href="@{/levelA/c}">L-A-c</a> </div> </div> </div> <div class="column" shiro:hasAnyRoles="ADMIN,SUPER_ADMIN"> <div class="ui raised segments"> <div class="ui segment"> <a th:href="@{/levelB/a}">L-B-a</a> </div> <div class="ui segment"> <a th:href="@{/levelB/b}">L-B-b</a> </div> <div class="ui segment"> <a th:href="@{/levelB/c}">L-B-c</a> </div> </div> </div> <div class="column" shiro:hasRole="SUPER_ADMIN"> <div class="ui raised segments"> <div class="ui segment"> <a th:href="@{/levelC/a}">L-C-a</a> </div> <div class="ui segment"> <a th:href="@{/levelC/b}">L-C-b</a> </div> <div class="ui segment"> <a th:href="@{/levelC/c}">L-C-c</a> </div> </div> </div> </div>
普通管理员登陆后,显示帐号和注销,同时只有超级管理员才能访问的 C模块 就不给予显示
若是文章中有什么不足,欢迎你们留言交流,感谢朋友们的支持!
若是能帮到你的话,那就来关注我吧!若是您更喜欢微信文章的阅读方式,能够关注个人公众号
在这里的咱们素不相识,却都在为了本身的梦而努力 ❤一个坚持推送原创开发技术文章的公众号:理想二旬不止