乐趣区

八-SpringBoot起飞之路整合Shiro详细教程MyBatisThymeleaf

趣味的敌人能够去理解一下前几篇,你的赞就是对我最大的反对,感激大家!

(一) SpringBoot 腾飞之路 -HelloWorld

(二) SpringBoot 腾飞之路 - 入门原理剖析

(三) SpringBoot 腾飞之路 -YAML 配置小结(入门必知必会)

(四) SpringBoot 腾飞之路 - 动态资源解决

(五) SpringBoot 腾飞之路 -Thymeleaf 模板引擎

(六) SpringBoot 腾飞之路 - 整合 JdbcTemplate-Druid-MyBatis

(七) SpringBoot 腾飞之路 - 整合 SpringSecurity

阐明:

  • 这一篇的目标还是整合,也就是一个具体的实操体验,原理性的没波及到,我自身也没有深入研究过,就不献丑了
  • SpringBoot 腾飞之路 系列文章的源码,均同步上传到 github 了,有须要的小伙伴,随便去 down

    • https://github.com/ideal-20/S…
  • 满腹经纶,就会点肤浅的常识,大家权当一篇工具文来看啦,不喜勿愤哈 ~

(一) 初识 Shiro

(1) 引言

权限以及平安问题,尽管并不是一个影响到程序、我的项目运行的必须条件,然而却是开发中的一项重要思考因素,例如某些资源咱们不想被拜访到或者咱们某些办法想要满足指定身份才能够拜访,咱们能够应用 AOP 或者过滤器来实现要求,然而实际上,如果代码波及的逻辑比拟多当前,代码是极其繁琐,冗余的,而有很多开发框架,例如 Spring Security,Shiro,曾经为咱们提供了这种性能,咱们只须要晓得如何正确配置以及应用它了

(2) 根本介绍

官网: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 和 Spring Security 性质是一样的,都是一款权限框架,用来保障利用的权限平安问题
  • Shiro 可执行身份验证、受权、加密和会话治理,Web 集成,缓存等
  • Shiro 不仅能够利用到 JavaEE 环境下,甚至 JavaSE 也能够

(3) 基本功能

这部分的内容,说实话,刚入门简略扫两眼就行了,只有你真的敲过一次代码了,你才大略对其中某些局部能有个印象,再持续深入研究才可能有比拟好的把握

A:官网架构图

  • Authentication:用户认证就是指这个用户身份是否非法,个别咱们的用户认证就是通过校验用户名明码,来判断用户身份的合法性,确定身份非法后,用户就能够拜访该零碎
  • Authorization:如果不同的用户须要有不同等级的权限,就波及到用户受权,用户受权就是对用户能拜访的资源,所能执行的操作进行管制,依据不同用户角色或者对应不同权限来划分不同的权限
  • SessionManager:Shior 官网说其提供了一个残缺的会话治理解决方案,它的所会话能够是一般的 Java SE 环境,也能够是 Web 环境,不过我有点思维定式了,还是用习惯的形式,这块没怎么钻研
  • Cryptography:加密明文明码,爱护数据安全
  • WebSupport:字面意思,其对 Web 的反对,使得其能够非常容易的集成到 Web 环境;
  • Caching:缓存,比方用户登录后,其用户信息,领有的角色、权限不用每次去查,效率上会好一点
  • Concurrency:Shiro 反对多线程利用的并发验证,即,如在一个线程中开启另一个线程,能把权限主动传过来
  • Testing:没什么好说的,就是反对测试
  • Run As:容许一个用户伪装为另一个用户(容许的条件下)的身份进行拜访资源申请
  • Remember Me:它也有,记住我这个性能

B:三大外围组件

Shiro 框架中有三个外围组件:Subject,SecurityManager 和 Realms

  1. Subject 是一个平安术语,代表认证主体,一般来说能够简略的了解为,以后操作的用户,不过用户这个概念实际上也不是很精确,因为 Subject 实际上不肯定是人,也能够是一些例如第三方过程或者定时作业等等的事物,也就是了解为,以后同软件交互的事物。

    • 每一个 Subject 对象都必须被 SecurityManager 进行治理
  2. Subject 承受 SecurityManager 的治理,因为 SecurityManager 治理所有用户的平安操作,其外部援用了很多平安相干的组件,然而都不对外开放,开发人员更多的是应用 Subject
  3. Realms 这个概念也是重要的,其能够了解为 Shiro 与 数据之间的沟通器与两头桥梁认证受权时,就会去此局部找一些内容,从实质上 Realm 就是一个通过了大量封装的平安 Dao

(4) 用户 | 角色 | 权限的概念

既然 Shiro 是一个平安权限技术,简略来说,就是对程序中被拜访的资源或者申请进行肯定水平的管制,而如何划分就波及到这三个概念:用户、角色、权限

用户(User):没啥好说的,代表以后 Subject 认证主体,例如某些内容必须用户登录后才能够拜访

角色(Role):这代表用户负责的角色,身份,一个角色能够有多个权限,例如这一块只有管理员能够拜访

权限(Permission):也就是操作资源的具体的权力,例如对数据进行增加、批改、删除、查看操作

补充:其实能够简略的了解,角色就是一些权限的汇合组成的,正是这一堆权限曾经将这个角色能做的事件限定死了,不必每次都阐明这个角色能够做什么

(二) 动态页面导入 And 页面环境搭建

(1) 对于动态页面

A:页面介绍

页面是我本人长期弄得,有须要的敌人能够去我 GitHub:ideal-20 下载源码,简略阐明一下这个页面

做一个动态页面如果嫌麻烦,也能够单纯的本人创立一些简略的页面,写几个题目文字,能体现出以后是哪个页面就好了

我代码中用的这些页面,就是拿开源的前端组件框架进行了一点的丑化,而后不便解说一些性能,页面模板次要是配合 Thymeleaf

1、目录构造

├── 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

B:导入到我的项目

次要就是把根本一些链接,引入什么的先替换成 Thymeleaf 的标签格局,这里语法用的不是特地多,即便对于 Thymeleaf 不是很相熟也是很容易看懂的,当然如果依然感觉有点吃力,能够单纯的做成 html,将就一下,或者去看一下我以前的文章哈,外面有对于 Thymeleaf 入门的解说

css、image、js 放到 resources –> static 下,views 和 index.html 放到 resources –> templates 下

(2) 环境搭建

A:引入依赖

这一部分引入也好,初始化我的项目的时候,勾选好主动生成也好,只有依赖失常导入了即可

  • 引入 Spring Security 模块
<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>

B:页面跳转 Controller

因为咱们用了模板,页面的跳转就须要交给 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";}
}

C:环境搭建最终成果

  • 为了贴图不便,我把页面拉窄了一点
  • 首页右上角应该为登录的链接,这里是因为,我运行的是曾经写好的代码,不登录页面例如 L-A-a 等模块就显示不进去,所以拿一个定义好的管理员身份登陆了
  • 对于如何使其主动切换显示登陆还是登录后信息,在前面会解说

1、首页

2、子页面

L-A、L-B、L-C 下的 a.html、b.html、c.html 都是一样的,只是文字有一点变动

3、登陆页面

4、胜利及未受权页面

我截了个图,把两个页面拼接到一起了,没啥好说的,就是两个很一般的 H5 页面

(三) 创立数据库及实体

(1) 创立数据库以及表

-- ----------------------------
-- 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);

(2) 实体

在数据库中角色表,在用户表和权限表别离是有一个外键的概念,所以在实体中就写成了援用的模式

角色类

@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;
}

(四) 整合 MyBatis

明天要做的内容,实际上本人轻易模仿两个数据也是能够的,不过为了贴近事实,还是引入了 Mybaits

(1) 引入依赖及进行配置

先引入 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 这里还没写,解说的过程中,依照流须要,再写上去

(2) 编写 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>

(3) 代码测试

@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());
    }
}

(五) Spring Boot 整合 Shiro

(1) 自定义认证和受权(Realm)

首先咱们须要创立 Shiro 的配置类,在 config 包下创立一个名为 ShiroConfig 的配置类

@Configuration
public class ShiroConfig {
    // 1、ShiroFilterFactoryBean
    // 2、DefaultWebSecurityManager
    // 3、Realm 对象(自定义)}

下面正文能够看出,咱们须要在配置类中创立这样几个内容,因为他们几个之间存在关联,例如在 Manager 中关联本人创立的 Realm,在最下面的过滤器,又关联了两头这个 Manager,所以咱们抉择倒着写,先写前面的(也就是被援用最早的 Realm),这样就能够一层一层的在后面援用前面曾经写好的,会更难受一些

首先,在 ShiroConfig 配置类中编写一个办法用来获取 Realm,间接返回一个实例化的 userRealm() 就能够了

/**
 * 创立 realm 对象,须要本人定义
 *
 * @return
 */
@Bean
public UserRealm userRealm() {return new UserRealm();
}

具体内容,咱们须要创立一个新的类来定义

咱们自定义了一个 UserRealm 类,同时继承 AuthorizingRealm 类,接着就须要实现两个办法:

  • doGetAuthenticationInfo() 认证办法:查看用户是否能通过认证,可简略了解为登录是否胜利
  • doGetAuthorizationInfo() 受权办法:给以后曾经登录胜利的用户划分权限以及调配角色

依据下面的介绍也很好了解,必定是认证后行,接着才会执行受权办法,所以咱们先来编写认证的代码

A:认证

认证首先就要先获取到咱们前台传来的数据,这块很显然,交给 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;
     }
}

B:受权

受权,也就是在用户认证后,来设置用户的权限或者角色信息,这里次要是获取到用户名当前,通过 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;
}

(2) Shiro 配置

受权和配置就写好了,也就是说 Realm 完事了,一个大头内容实现了,咱们接着就能够回到 Shiro 的配置中去了,持续倒着写,开始写对于第二点 Manager 的内容

@Configuration
public class ShiroConfig {
    // 1、ShiroFilterFactoryBean
    // 2、DefaultWebSecurityManager
    
    // 3、Realm 对象(自定义)@Bean
    public UserRealm userRealm() {return new UserRealm();
    }
}

A:配置平安管理器

接着就来配置平安管理器(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();
}

B:配置过滤器

这又是一个要害的中央,首先创立一个 ShiroFilterFactoryBean 必定是毋庸置疑的,最初毕竟要返回这个对象,首先就是将方才的 securityManager 关联进来了,也就是说层层调用,最终把 Realm 关联过去了,接着要写的就是重头戏了,咱们接着须要设置一些本人定义的内容

  • 自定义登录页面
  • 胜利页面
  • 未受权界面
  • 一个自定义的 Map 用来存储须要放行或者拦挡的申请
  • 登记页面

重点说一下拦挡放行(Map)这块:通过 map 键值对的模式存储,key 存储 URL,value 存储对应的一些权限或者角色等等,其实 key 这块还是很好了解的,例如:/css/** /user/admin/** 别离代表 css 文件夹下的所有文件,以及申请门路前缀为 /user/admin/ URL,而对应的 value 就有肯定的标准了

要害:

  • anon:无需认证,即可拜访,也就是游客也能够拜访
  • authc:必须认证,能力拜访,也就是例如须要登录后
  • roles[xxx]:比方领有某种角色身份能力拜访,注:xxx 为角色参数
  • perms[xxx]:必须领有对某个申请、资源的相干权限能力拜访,注:xxx 为权限参数

补充:

  • user:必须应用【记住我】这个性能能力拜访
  • logout:登记,执行后跳转到设置好的登录页面去
/**
 * 配置 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;
}

C:解决多身份问题

其实下面的内容曾经根本健全了,然而还有一个很辣手的问题,那就是,例如我主页中的三个模块,超级管理员 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]");

(六) Shiro 整合 Thymeleaf

次要内容曾经完结了,不过因为在后面 Spring Security 中,讲过如何搭配 Thymeleaf 应用,所以接着补充一点对于如何用 Shiro 配合 Thymeleaf 的办法

A:引入

首先引入两者整合的依赖:

<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();
}

B:批改页面

操作完结后,咱们就能够开始批改页面了,首先引入头部束缚 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:看一下成果

一般管理员登录后,显示账号和登记,同时只有超级管理员能力拜访的 C 模块 就不给予显示

(七) 结尾

如果文章中有什么有余,欢送大家留言交换,感激敌人们的反对!

如果能帮到你的话,那就来关注我吧!如果您更喜爱微信文章的浏览形式,能够关注我的公众号

在这里的咱们素不相识,却都在为了本人的梦而致力 ❤

一个保持推送原创开发技术文章的公众号:现实二旬不止

退出移动版