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


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

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

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

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

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

    四张表

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

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

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

    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部分 先上依赖

    <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还要考虑缩进

    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相关的代码 需要自己去实现的部分

     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的配置

     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

    这是控制器

     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异常 这样就舒服了

     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登录后 查询用户信息 就会返回这条信息

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

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

    这是谷歌的

     这是火狐的

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

    来自1942年冬季攻势中的中央集团军的037号17吨救援拖车
  • 相关阅读:
    HDFS源码分析(六)-----租约
    YARN源码分析(一)-----ApplicationMaster
    YARN源码分析(一)-----ApplicationMaster
    YARN源码分析(一)-----ApplicationMaster
    YARN源码分析(二)-----ResourceManager中的NM节点管理
    YARN源码分析(二)-----ResourceManager中的NM节点管理
    Confluence 6 如何备份和恢复
    Confluence 6 那些文件需要备份
    Confluence 6 确定一个生产系统备份方案
    Confluence 6 生产环境备份策略
  • 原文地址:https://www.cnblogs.com/panther1942/p/12841243.html
Copyright © 2020-2023  润新知