【筆記】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(); 總覺得這樣用的不爽

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM