【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)

摘要:
我在网上搜索了很长时间,但没有SpringBoot+Shiro的入门课程。我原本想看《向我学习Shiro》,但我发现这是从一开始,但我需要与SpringBoot合作。我不需要那么多东西。我觉得这是一本很好的参考书,所以我最后把它拼凑起来,删除了我在别人的教程中没有用到的所有内容。这种改变最终达到了我的理想效果。首先,数据库部分并不太复杂,因为它易于实现。用户的三部分--角色--权限。四张桌子。用户和角色之间的关系是多对一(多对多太懒了…)角色和

网上翻了好久 都没有SpringBoot+Shiro的入门教程 原本想看《跟我学Shiro》

然后发现这是从头开始 但是我就需要和SpringBoot整一块 不需要那么多的东西 感觉这个当参考书不错

于是东拼西凑终于整成了 把别人的教程上我用不到的都删了 该改的改 终于拿到了我理想中的效果

先是数据库部分 因为是简单的实现 就没有弄得太复杂

三部分 用户 -- 角色 -- 权限

四张表

用户和角色是多对一的关系(多对多懒得弄...)

角色和权限是多对多的关系(这个没啥好说的)

直接放代码了 那个url忽略就好了 完全没用上 我打算弄菜单的自动获取能访问的列表 这样就不用写死了

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第1张【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第2张
DROP TABLE IF EXISTS tb_user;
CREATE TABLE tb_user (
  `id`          INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
  `username`    VARCHAR(50)     NOT NULL UNIQUE KEY,
  `password`    VARCHAR(255)    NOT NULL,
  `role_id`     INT             NOT NULL,
  `create_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP
);

DROP TABLE IF EXISTS tb_role;
CREATE TABLE tb_role (
  `id`          INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
  `name`        VARCHAR(50)     NOT NULL UNIQUE KEY,
  `desc`        VARCHAR(50)     NOT NULL,
  `create_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP
);

DROP TABLE IF EXISTS tb_permission;
CREATE TABLE tb_permission (
  `id`          INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
  `parent_id`   INT,
  `name`        VARCHAR(50)     NOT NULL UNIQUE KEY,
  `desc`        VARCHAR(50)     NOT NULL,
  `url`         VARCHAR(255)    NOT NULL DEFAULT '#',
  `order_by`    INT,
  `type`        INT             NOT NULL DEFAULT 0,
  `create_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP
);

DROP TABLE IF EXISTS tb_role_permission;
CREATE TABLE tb_role_permission (
  `id`            INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
  `role_id`       INT             NOT NULL,
  `permission_id` INT             NOT NULL,
  `create_time`   DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time`   DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO tb_permission (`id`, `name`, `desc`, url, `order_by`, `type`)
VALUES ('1', 'user:*', '用户管理', '/getAll', '1', '1');
INSERT INTO tb_permission (`id`, `name`, `desc`, url, `order_by`, `type`)
VALUES ('2', 'user:query', '查询用户', '#', '1', '1');
INSERT INTO tb_permission (`id`, `name`, `desc`, url, `order_by`, `type`)
VALUES ('3', 'user:insert', '新增用户', '#', '1', '1');
INSERT INTO tb_permission (`id`, `name`, `desc`, url, `order_by`, `type`)
VALUES ('4', 'user:update', '修改用户', '#', '1', '1');
INSERT INTO tb_permission (`id`, `name`, `desc`, url, `order_by`, `type`)
VALUES ('5', 'user:delete', '删除用户', '#', '1', '1');

INSERT INTO tb_role (`id`, `name`, `desc`) VALUES ('1', 'admin', '系统管理员');
INSERT INTO tb_role (`id`, `name`, `desc`) VALUES ('2', 'users', '普通用户');

INSERT INTO tb_role_permission (`role_id`, `permission_id`) VALUES ('1', '1');
INSERT INTO tb_role_permission (`role_id`, `permission_id`) VALUES ('2', '2');

INSERT INTO tb_user (`id`, `username`, `password`, `role_id`) VALUES ('1', 'admin', 'admin', '1');
INSERT INTO tb_user (`id`, `username`, `password`, `role_id`) VALUES ('2', 'user1', '123456', '2');
数据库初始化

这块设置了俩角色 管理组和用户组 管理组有查询权限 用户组没有查询权限

数据库整完之后就是java部分 先上依赖

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第3张【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第4张
<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    <spring.version>2.2.6.RELEASE</spring.version>
    <nekohtml.version>1.9.22</nekohtml.version>
    <jdbc.version>8.0.16</jdbc.version>
    <druid.version>1.1.10</druid.version>
    <mybatis-spring.version>1.3.0</mybatis-spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.0</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${jdbc.version}</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid.version}</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>${mybatis-spring.version}</version>
    </dependency>

    <!-- thymeleaf网页解析 -->
    <dependency>
        <groupId>net.sourceforge.nekohtml</groupId>
        <artifactId>nekohtml</artifactId>
        <version>${nekohtml.version}</version>
    </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>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
pom.xml中的依赖部分

springboot的配置文件 现在咋都好用yml了 properties多好 yml还要考虑缩进

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第5张【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第6张
server:
    port: 8080
    tomcat:
        uri-encoding: utf-8

spring:
    thymeleaf:
        mode: LEGACYHTML5
        cache: false
    datasource:
        url: jdbc:mysql://localhost/db_test
        username: test
        password: test
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource

mybatis:
    typeAliasesPackage: cn.erika.user.model
    mapper-locations: classpath:static/mybatis/*Mapper.xml

shiro:
    url:
        login: login
        index: index
        unauthorized: login
    session:
        timeout: 30
        validationInterval: 15
    cookie:
        domain:
        path: /
        timeout: 7
application.yml

shiro部分除了url 其他的都没用上 但还是配上了

鉴权的数据来自与数据库 所以首先要把数据查询部分搞好

mybatis咋玩在这就不写了 在这个实验性质的程序上 至少要实现根据用户查角色和根据用户查权限

我的一个用户只有一个角色 所以根据用户名查到用户就行

查到用户之后就可以根据用户查权限 这是服务层的这个方法的签名

public Set<String> getPermissionsByUserId(Integer userId);

返回值是字符串的set集 注意是set 我习惯性的返回list之后 写realm的时候发现他只接受set

下面这些都是shiro相关的代码 需要自己去实现的部分

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第7张【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第8张
 1 package cn.erika.shiro.realm;
 2 
 3 import cn.erika.user.model.User;
 4 import cn.erika.user.service.IUserService;
 5 import org.apache.shiro.authc.*;
 6 import org.apache.shiro.authz.AuthorizationInfo;
 7 import org.apache.shiro.authz.SimpleAuthorizationInfo;
 8 import org.apache.shiro.realm.AuthorizingRealm;
 9 import org.apache.shiro.subject.PrincipalCollection;
10 import org.springframework.beans.factory.annotation.Autowired;
11 import org.springframework.stereotype.Component;
12 
13 /**
14  * 这个就是认证的实现类
15  * 你得自己去实现如何去认证
16  * 这里实现了使用用户名和密码进行认证
17  */
18 @Component
19 public class UserRealm extends AuthorizingRealm {
20 
21     @Autowired
22     private IUserService userService;
23 
24     // 这块是权限鉴定 就是登录成功后把权限查出来
25     @Override
26     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
27         // 首先要把用户查出来
28         User user = userService.getUserByUsername(principalCollection.toString());
29         // 然后把他的角色和权限查出来 扔进
30         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
31         info.setRoles(user.getRoleNames());
32         info.setStringPermissions(userService.getPermissionsByUserId(user.getId()));
33         return info;
34     }
35 
36     // 这块是身份鉴定 相当于登录
37     @Override
38     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
39         UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
40         User user = userService.getUserByUsername(token.getUsername());
41         // 我这块就写了一个异常
42         if (user == null) {
43             throw new UnknownAccountException();
44         }
45         // 实际上有好些异常可以往外抛
46         // IncorrectCredentialsException 凭证错误 可以理解为密码错误
47         // DisabledAccountException 账号被禁用
48         // LockedAccountException 账号被锁定
49         // ExcessiveAttemptsException 登录失败次数超过限制
50         // ... 还有好些 他们都是 AuthenticationException的子类
51 
52         // 这里返回一个认证信息
53         return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
54     }
55 }
UserRealm.java

Shiro的配置

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第9张【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第10张
 1 package cn.erika.shiro;
 2 
 3 import cn.erika.shiro.realm.UserRealm;
 4 import org.apache.shiro.mgt.SecurityManager;
 5 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
 6 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
 7 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 8 import org.springframework.beans.factory.annotation.Value;
 9 import org.springframework.context.annotation.Bean;
10 import org.springframework.context.annotation.Configuration;
11 
12 import java.util.HashMap;
13 
14 @Configuration
15 public class ShiroConfig {
16     // 登录页面的URL
17     @Value("${shiro.url.login}")
18     private String loginUrl;
19 
20     // 主页URL 认证成功会跳到这里
21     @Value("${shiro.url.index}")
22     private String indexUrl;
23 
24     // 认证失败的URL 我这里其实还是跳到了登录页面
25     @Value("${shiro.url.unauthorized}")
26     private String unauthorizedUrl;
27 
28     @Bean
29     public SecurityManager securityManager(UserRealm userRealm) {
30         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
31         securityManager.setRealm(userRealm);
32         return securityManager;
33     }
34 
35     @Bean
36     public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
37         ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
38         factoryBean.setSecurityManager(securityManager);
39         factoryBean.setLoginUrl(loginUrl);
40         factoryBean.setUnauthorizedUrl(unauthorizedUrl);
41 
42         HashMap<String, String> filterChain = new HashMap<>();
43         filterChain.put("/favicon.ico", "anon");
44         filterChain.put("/login", "anon");
45 
46         filterChain.put("/logout", "logout");
47         filterChain.put("/**","authc");
48 
49         factoryBean.setFilterChainDefinitionMap(filterChain);
50 
51         return factoryBean;
52     }
53 
54     @Bean
55     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
56         AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
57         authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
58         return authorizationAttributeSourceAdvisor;
59     }
60 }
ShiroConfig.java

这是控制器

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第11张【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第12张
 1 package cn.erika.user.controller;
 2 
 3 import cn.erika.user.dao.UserDao;
 4 import cn.erika.user.model.User;
 5 import org.apache.shiro.SecurityUtils;
 6 import org.apache.shiro.authc.AuthenticationException;
 7 import org.apache.shiro.authc.IncorrectCredentialsException;
 8 import org.apache.shiro.authc.UnknownAccountException;
 9 import org.apache.shiro.authc.UsernamePasswordToken;
10 import org.apache.shiro.authz.annotation.RequiresPermissions;
11 import org.apache.shiro.subject.Subject;
12 import org.springframework.beans.factory.annotation.Autowired;
13 import org.springframework.stereotype.Controller;
14 import org.springframework.web.bind.annotation.*;
15 import org.springframework.web.servlet.mvc.support.RedirectAttributes;
16 
17 import java.util.List;
18 
19 @Controller
20 public class UserController {
21 
22     @Autowired
23     private UserDao userDao;
24 
25     @RequestMapping({"", "index"})
26     public String index() {
27         return "index";
28     }
29 
30     @RequiresPermissions("user:query")
31     @RequestMapping("/users")
32     @ResponseBody
33     public List<User> getUsers() {
34         return userDao.getUsers();
35     }
36 
37     @GetMapping("/login")
38     public String loginFrom() {
39         return "login";
40     }
41 
42     @PostMapping("/login")
43     public String login(User user) {
44         String username = user.getUsername();
45         System.out.println("用户名: " + username);
46         UsernamePasswordToken token = new UsernamePasswordToken(username, user.getPassword());
47 
48         Subject subject = SecurityUtils.getSubject();
49         try {
50             System.out.println("登录验证: " + username);
51             subject.login(token);
52             System.out.println("验证通过: " + username);
53         } catch (UnknownAccountException e) {
54             System.err.println("未知的账户");
55         } catch (IncorrectCredentialsException e) {
56             System.err.println("密码错误");
57         } catch (AuthenticationException e) {
58             System.err.println("其他错误");
59             e.printStackTrace();
60         }
61         if (subject.isAuthenticated()) {
62             System.out.println("登录成功");
63             return "redirect:/index";
64         } else {
65             token.clear();
66             return "redirect:/login";
67         }
68     }
69 
70     @GetMapping("/logout")
71     public String logout(RedirectAttributes redirectAttributes) {
72         SecurityUtils.getSubject().logout();
73         redirectAttributes.addFlashAttribute("message", "退出登录");
74         return "redirect:/login";
75     }
76 
77 
78 }
UserController.java

页面我就不放了 没啥看头

主页就是俩按钮 一个指向users 一个指向logout

登录页面就是用户名和密码 指向login

其实到这里功能已经实现了 登录和权限鉴定都有了

但是很不美 因为如果没有权限 他直接蹦403 你要是ajax肯定不爽

所以搞了一下Spring的异常统一处理 捕获了一下AuthorizationException异常 这样就舒服了

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第13张【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第14张
 1 package cn.erika.exception;
 2 
 3 import cn.erika.model.Message;
 4 import org.apache.shiro.authz.AuthorizationException;
 5 import org.springframework.web.bind.annotation.ExceptionHandler;
 6 import org.springframework.web.bind.annotation.RestControllerAdvice;
 7 
 8 @RestControllerAdvice
 9 public class DefaultExceptionHandler {
10 
11     @ExceptionHandler(AuthorizationException.class)
12     public Message authorizationException(AuthorizationException e) {
13         return Message.failed("没有操作权限");
14     }
15 }
DefaultExceptionHandler

那个Message类就俩属性

int code;

String message;

我写了一堆静态方法方便调用 不用每次都要填错误信息了

我用user1登录后 查询用户信息 就会返回这条信息

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第15张

开发的时候还是火狐好用 自带json格式化和上色 这多舒服

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第16张

数据少的不明显 你看看数据多的时候 

这是谷歌的

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第17张

 这是火狐的

【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)第18张

 忘了说了 我user里面roleNames这个属性是为了图省事 不然就得调 user.getRole().getName(); 总觉得这样用的不爽

免责声明:文章转载自《【笔记】SpringBoot+Shiro 实现简单权限管理(使用mysql数据库)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇缓存DataSet以提高性能C++ ORM ODB入门下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

fastJSON 使用总结

1.介绍Fastjson Fastjson是一个Java语言编写的JSON处理器。 如果获得Fastjson?https://github.com/alibaba/fastjson 2.使用Fastjson Json互转List<T> 比如说List<Strudent> List转Json List<Student>...

容器编排系统K8s之包管理器Helm基础使用(一)

前文我们了解了k8s上的hpa资源的使用,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14293237.html;今天我们来聊一下k8s包管理器helm的相关话题; helm是什么? 如果我们把k8s的资源清单类比成centos上的rpm包,那么helm的作用就如同yum;简单讲helm就是类似yum这样的包管理...

使用sonar进行代码质量检查

最近公司要求,学习了一下使用sonar进行代码分析检查。其中走了许多坑,配置也崩溃好几次。。。因此写下详细步骤以防以后再碰到这样的坑 一、安装Sonar 环境:Windows10,sonarqube6.7,java1.8,MySQL5.7.1  (注:用来检查java1.8以下的项目依旧可以使用,但是环境变量得是1.8的才能启动,我本次使用检查的项目就是j...

PostgreSQL 字符串操作函数 迎客

函数:string || string 说明:String concatenation 字符串连接操作例子:'Post' || 'greSQL' = PostgreSQL 函数:string || non-string or non-string || string说明:String concatenation with one non-string i...

asp.net中序列化和反序列化json的两种常用方式

使用System.Web.Script.Serialization.JavaScriptSerializer类       JavaScriptSerializer类为.net类库自带,.net3.5及以后版本都可以使用,该类位于System.Web.Extensions.dll中,如需使用该类,必须添加引用。      (1) 序列化 p...

存储过程中SELECT INTO的使用

在MySQL存储过程中使用SELECT …INTO语句为变量赋值:   用来将查询返回的一行的各个列值保存到局部变量中。 要求:   查询的结果集中只能有1行。 SELECT col_name[,...] INTO var_name[,...] table_expr 使用SELECT …INTO语句在数据库中进行查询,并将得到的结果赋值给变量。   ①co...