javascript
一篇搞定 SpringBoot+Mybatis+Shiro 实现多角色权限管理
初衷:我在網上想找整合springboot+mybatis+shiro并且多角色認證的博客,發現找了好久也沒有找到想到的,現在自己會了,就打算寫個博客分享出去,希望能幫到你。
原創不易,請點贊支持!
該項目不會將過多基礎,直接實戰,比較使用于有一點基礎的, 并且想整合springboot+mybatis+shiro的朋友們。
代碼和數據庫sql都放在github,鏈接如下:https://github.com/zhiyuwyu/springboot-mybatis-shiro.git
一篇搞定 SpringBoot+Mybatis+Shiro 實現多角色權限管理
- 1、了解需求
- 1.1、了解頁面
- 1.2、需求
- 2、準備數據庫環境
- 3、編寫代碼
- 3.1、新建SpringBoot 工程項目
- 3.2、添加如下依賴
- 3.3、編寫代碼連接數據庫并測試
- 3.3.1、配置數據庫信息
- 3.3.2、編寫實體類 entity
- 3.3.3、mapper
- 3.3.4、service
- 3.4、編寫頁面
- 3.5、編寫 shiro 的有關配置
- 3.6、編寫Controller層代碼
- 4、測試
- 4.0、游客訪問
- 4.1、測試無用戶登錄
- 4.2、測試密碼不正確登錄
- 4.3、測試 rootA用戶正確登錄
- 4.4、測試adminA用戶正確登錄
- 4.5、測試userA用戶正確登錄
- 5、結語
1、了解需求
1.1、了解頁面
登錄頁面如下
首頁頁面如下
分別點擊添加、刪除、查詢、測試超鏈接,展示的內容如下
1.2、需求
- 首頁頁面必須登錄成功之后才能訪問
- 所有用戶、游客等都可訪問登錄頁面、測試頁面,無需登錄
- 擁有 root 角色的用戶可以訪問所有頁面,包括添加頁面、刪除頁面、查詢頁面、測試頁面等
- 擁有admin 角色的用戶可以訪問添加頁面,查詢頁面、測試頁面,除了刪除頁面不能訪問
- 擁有 user 角色的用戶可以訪問 查詢頁面、測試頁面,除了添加頁面、刪除頁面不能訪問
2、準備數據庫環境
新建一個test數據庫,創建兩個表(role、user)并插入數據,sql 如下
/*Navicat Premium Data TransferSource Server : LocalHostSource Server Type : MySQLSource Server Version : 50731Source Host : localhost:3306Source Schema : testTarget Server Type : MySQLTarget Server Version : 50731File Encoding : 65001Date: 12/07/2021 21:08:51 */SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;-- ---------------------------- -- Table structure for role -- ---------------------------- DROP TABLE IF EXISTS `role`; CREATE TABLE `role` (`id` int(2) NOT NULL AUTO_INCREMENT,`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,`remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;-- ---------------------------- -- Records of role -- ---------------------------- INSERT INTO `role` VALUES (1, 'root', '超級管理員'); 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(10) NOT NULL AUTO_INCREMENT,`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,`role_id` int(3) NOT NULL,PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;-- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (1, 'rootA', '123456', 1); INSERT INTO `user` VALUES (2, 'adminA', '123456', 2); INSERT INTO `user` VALUES (3, 'userA', '123456', 3); INSERT INTO `user` VALUES (4, 'userB', '123456', 3);SET FOREIGN_KEY_CHECKS = 1;role 表數據如下:
user 表數據如下:
3、編寫代碼
3.1、新建SpringBoot 工程項目
3.2、添加如下依賴
全部依賴如下
<!-- thymeleaf --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- web --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- mysql--> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId> </dependency> <!--mybatis--> <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version> </dependency> <!-- Spring對Shiro支持 --> <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.7.1</version> </dependency> <!--test單元測試--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope> </dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.14</version> </dependency>3.3、編寫代碼連接數據庫并測試
3.3.1、配置數據庫信息
把 application.properties 文件修改成 application.yml,并添加如下內容
spring:datasource:username: root password: 123456 url: jdbc:mysql://localhost:3306/test # 打印sql語句 logging:level:com:huang:shiro1:mapper: debug3.3.2、編寫實體類 entity
新建一個entity 包,分別添加下面兩個實體類
User.java
package com.huang.springbootmybatisshiro.entity; import lombok.Data; import java.io.Serializable; /*** (User)實體類*/ @Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable {private static final long serialVersionUID = 227751358530931042L;private Integer id;private String username;private String password;private Integer roleId;private Role role; }Role.java
package com.huang.springbootmybatisshiro.entity; import lombok.Data; import java.io.Serializable; /*** (Role)實體類*/ @Data public class Role implements Serializable {private static final long serialVersionUID = -76407922564857637L;private Integer id;private String name;private String remark;}3.3.3、mapper
新建一個 mapper 包,分別創建下面兩個文件
UserMapper.java
public interface UserMapper {/*** 根據 Username 查詢單條數據** @param username* @return*/User queryByUsername(@Param("username") String username);}UserMapper.xml
<?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="com.huang.springbootmybatisshiro.mapper.UserMapper"><resultMap type="com.huang.springbootmybatisshiro.entity.User" id="UserMap"><result property="id" column="id" jdbcType="INTEGER"/><result property="username" column="username" jdbcType="VARCHAR"/><result property="password" column="password" jdbcType="VARCHAR"/><result property="roleId" column="role_id" jdbcType="INTEGER"/></resultMap><resultMap id="UserMapWithRole" type="com.huang.springbootmybatisshiro.entity.User" extends="UserMap"><collection property="role" ofType="com.huang.springbootmybatisshiro.entity.Role"><id property="id" column="rid"></id><result property="name" column="rname"></result><result property="remark" column="rremark"></result></collection></resultMap><select id="queryByUsername" resultMap="UserMapWithRole">selectu.*,r.id rid,r.name rname,r.remark rremarkfrom test.user u,test.role rwhere u.role_id=r.id<if test="username != null and username != ''">and username = #{username}</if></select></mapper>注意:
UserMapper.xml 和UserMapper.java 文件寫在同一目錄下,需在pom.xml文件添加如下內容
<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>src/main/resources</directory></resource></resources><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins> </build>并且在啟動類中添加注解@mapperscan 全局掃描 mapper 文件
@SpringBootApplication @MapperScan(basePackages = "com.huang.springbootmybatisshiro.mapper") public class SpringbootMybatisShiroApplication {public static void main(String[] args) {SpringApplication.run(SpringbootMybatisShiroApplication.class, args);} }3.3.4、service
新建一個包service,并添加 Userservice.java 文件如下
@Service public class UserService {@AutowiredUserMapper userMapper;public User queryByUsername(String username) {return userMapper.queryByUsername(username);} }3.3.4 測試是否可以正常獲取數據庫信息
@SpringBootTest class SpringbootMybatisShiroApplicationTests {@AutowiredUserService userService;@Testvoid contextLoads() {User rootA = userService.queryByUsername("rootA");System.out.println("=====================================");System.out.println("rootA = " + rootA);}}如果輸入如下數據則成功,就可以進行下一步了
3.4、編寫頁面
在resources目錄下新建文件夾templates(有就不用了),在templates 下添加如下頁面
- add.html 添加頁面
- delete.html 刪除頁面
- index.html 首頁頁面
- login.html 登錄頁面
- query.html 查詢頁面
- test.html 測試頁面
- unauthorized.html 未授權頁面
add.html
<!DOCTYPE html > <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head><meta charset="UTF-8"><title>add</title> </head> <body> <h1>添加頁面</h1> </body> </html>delete.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>update</title> </head> <body> <h1>刪除頁面</h1> </body> </html>index.html
<!DOCTYPE html > <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head><meta charset="UTF-8"><title>首頁</title> </head> <body> <h1>首頁</h1> 進入用戶添加頁面: <a href="add">添加頁面</a><br/> 進入用戶刪除頁面: <a href="delete">刪除頁面</a><br/> 進入用戶查詢頁面: <a href="query">查詢頁面</a><br/> 進入用戶測試頁面: <a href="test">測試頁面</a><br/> </body> </html>login.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head><meta charset="UTF-8"><title>login</title> </head> <body> <h1>登錄頁面</h1> <h3 th:text="${msg}" style="color: red"></h3> <form action="login" method="post">用戶名:<input type="text" name="username"><br>密碼:<input type="text" name="password"><br><input type="submit" value="submit"> </form> </body> </html>query.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>query</title> </head> <body> <h1>查詢頁面</h1> </body> </html>test.html
<!DOCTYPE html > <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head><meta charset="UTF-8"><title>測試頁面</title> </head> <body> <h1>測試頁面</h1> </body> </html>unauthorized.html。當訪問不夠權限的頁面時會跳轉到該頁面
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>unauthorized</title> </head> <body> <h1>你未授權,請聯系管理員</h1> </body> </html>3.5、編寫 shiro 的有關配置
新建一個config 包,添加以下文件
CustomRealm.java
/*** @Author: Zhiyu* @Date: 2021/7/13 10:40* @Description: 主要用于用戶數據和shiro的交互工作*/ public class CustomRealm extends AuthorizingRealm {@AutowiredUserService userService;/*** 授權:給當前用戶授權,以便能訪問** @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// principal 就是下面的方法 doGetAuthenticationInfo 中的return new SimpleAuthenticationInfo(user, user.getPassword(), "") 中的第一個參數 user 賦值的User principal = (User) principals.getPrimaryPrincipal();System.out.println("principal = " + principal);// 給資源進行授權SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 授權該用戶的本身角色的權限info.addRole(principal.getRole().getName());return info;}/*** 認證:能不能登錄等認證** @param authenticationToken* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("-------------進入了認證邏輯---------------");// token 中存儲著 subject.login(token) 中傳過來的數據UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;String username = token.getUsername();// 這里假設,數據庫中的 username 字段是唯一字段,可以作為唯一標識,實際開發中可以適當修改,換湯不換藥// 根據 username 去數據庫查詢用戶信息// 數據庫查詢回來的數據User user = userService.queryByUsername(username);if (user == null) {// 用戶名不存在// return null; shiro 底層會拋出UnknowAccountExceptionthrow new UnknownAccountException();}// 第一個參數 user: 代表傳值,及保存用戶的信息,后面會用到// 第二個參數 填真正的密碼,shiro 會幫我們做密碼驗證,無需我們自己做密碼驗證邏輯return new SimpleAuthenticationInfo(user, user.getPassword(), "");} }ShiroConfig.java
/*** @Author: Zhiyu* @Date: 2021/7/13 10:40* @Description: shiro 的配置* 完成下面三件事* 1.創建 ShiroFilterFactoryBean* 2.DefaultWebSecurityManager* 3.創建Realm并關聯*/ @Configuration public class ShiroConfig {@Bean(name = "customRealm")public CustomRealm customRealm() {return new CustomRealm();}@Bean(name = "securityManager")public SecurityManager securityManager(CustomRealm customRealm) {DefaultWebSecurityManager manager = new DefaultWebSecurityManager();manager.setRealm(customRealm);return manager;}/*** 過濾器配置** @param securityManager* @return*/@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);/*** Shiro內置過濾器,可以實現權限相關的攔截器* 常用的過濾器:* anon: 無需認證(登錄)可以訪問* authc: 必須認證才可以訪問* user: 如果使用rememberMe的功能可以直接訪問* perms: 該資源必須得到資源權限才可以訪問* roles: 該資源必須得到角色權限才可以訪問*/// 1. 權限相關的攔截器(什么路徑需要什么權限)LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();filterMap.put("/delete", "roles[root]"); //roles[root] 意思是訪問/delete 需要角色 root// roles[admin,root]意思是訪問/add 需要角色 admin或者root。// 如果不配置 RoleFilter,解決多角色and關系,則roles[admin,root]意思就是訪問/add 需要 admin和root兩個角色同時。filterMap.put("/add", "roles[admin,root]");// anon,意識是/test、/login / 無需認證(登錄)可以訪問filterMap.put("/test", "anon");filterMap.put("/login", "anon");filterMap.put("/", "anon");// authc 其余的訪問都必須認證才可以訪問,filterMap.put("/*", "authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);// 添加shiro 權限過濾器// 2. 配置自定義 or 角色 認證,把自定義過濾器配置進去即可LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();filters.put("roles", new RoleFilter());shiroFilterFactoryBean.setFilters(filters);// 3. 修改默認的登錄頁面和未授權頁面// 即訪問需要登錄有頁面時會跳轉到 /toLogin 請求// 即訪問需要不夠權限的時候頁面時會跳轉到 /toLogin 請求shiroFilterFactoryBean.setLoginUrl("/unauthorized");// 修改調整的登錄頁面shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");// 設置未授權提示頁面return shiroFilterFactoryBean;} }RoleFilter.java
/*** @Author: Zhiyu* @Date: 2021/7/13 11:08* @Description: 重寫Shiro自帶角色權限過濾器* shiro自帶的方法同一權限只能分配一個角色,默認所個角色的時候是 and 關系,不是 or 關系* 所以重寫 重寫Shiro自帶角色權限過濾器 解決多角色 的時候是 or 關系*/ public class RoleFilter extends RolesAuthorizationFilter {@Overridepublic boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)throws IOException {final Subject subject = getSubject(request, response);final String[] rolesArray = (String[]) mappedValue;if (rolesArray == null || rolesArray.length == 0) {// 無指定角色時,無需檢查,允許訪問return true;}for (String roleName : rolesArray) {if (subject.hasRole(roleName)) {return true;}}return false;} }3.6、編寫Controller層代碼
新建一個controller包,添加 UserController.java 文件,內容如下
@Controller public class UserController {/*** 使用 shiro 編寫認證(登錄)邏輯* 1. 獲取 Subject* 2. 封裝用戶數據* 3. 執行登錄方法*/@PostMapping("/login")public String login(String username, String password, Model model) {System.out.println("username = " + username);System.out.println("password = " + password);// 1.獲取 SubjectSubject subject = SecurityUtils.getSubject();// 2. 封裝用戶數據UsernamePasswordToken token = new UsernamePasswordToken(username, password);String msg = "登錄成功";try {// 3. 執行登錄方法, 到 CustomRealm 類的doGetAuthenticationInfo中去執行認證邏輯subject.login(token);} catch (UnknownAccountException uae) {msg = "未知賬戶";} catch (IncorrectCredentialsException ice) {msg = "密碼不正確";} catch (LockedAccountException lae) {msg = "賬戶已鎖定";} catch (ExcessiveAttemptsException eae) {msg = "用戶名或密碼錯誤次數過多";} catch (AuthenticationException ae) {msg = "用戶名或密碼不正確!";}model.addAttribute("msg", msg);if (subject.isAuthenticated()) {// 登錄成功,跳轉到 index.htmlreturn "redirect:/index";} else {token.clear();return "login";}} }7、編寫 WebMvcConfig, 配置無邏輯的訪問頁面跳轉
在config 包下新建 WebMvcConfig.java ,代碼如下
@Configuration public class WebMvcConfig implements WebMvcConfigurer {// 這里配置一些無邏輯處理的頁面請求@Overridepublic void addViewControllers(ViewControllerRegistry registry) {/*** registry.addViewController("/add").setViewName("add");* 的意識等價于 在controller 層的* @RequestMapping("/add")* public String add() {* return "add"; // 跳轉到 add.html 頁面* }*** 所以registry.addViewController("/add").setViewName("add");* 意思是:訪問 /add 就會跳轉到 add.html 頁面* 下面的以此類推**/registry.addViewController("/add").setViewName("add");registry.addViewController("/delete").setViewName("delete");registry.addViewController("/query").setViewName("query");registry.addViewController("/toLogin").setViewName("login");registry.addViewController("/").setViewName("login");registry.addViewController("/unauthorized").setViewName("unauthorized");registry.addViewController("/index").setViewName("index");registry.addViewController("/test").setViewName("test");} }啟動項目進行測試
4、測試
User 表數據如下
4.0、游客訪問
游客直接在瀏覽器輸入下面的地址
-
http://localhost:8080/add
-
http://localhost:8080/delete
-
http://localhost:8080/query
都是下面會跳轉到登錄頁面
除了訪問http://localhost:8080/test 可以正確跳轉
4.1、測試無用戶登錄
訪問 http://localhost:8080/, 輸入如下信息
點擊提交,然后顯示的頁面如下
4.2、測試密碼不正確登錄
訪問 http://localhost:8080/, 輸入如下信息
點擊提交,然后顯示的頁面如下
4.3、測試 rootA用戶正確登錄
訪問 http://localhost:8080/, 輸入如下信息
點擊提交,然后顯示的頁面如下
分別點擊添加、刪除、查詢、測試頁面,分別顯示的頁面如下
結果:可以看到全部頁面都可以正常訪問。
4.4、測試adminA用戶正確登錄
訪問 http://localhost:8080/, 輸入如下信息
點擊提交,然后顯示的頁面如下
分別點擊添加、刪除、查詢、測試頁面,分別顯示的頁面如下
結果:擁有admin 角色的用戶可以訪問添加頁面,查詢頁面、測試頁面,除了刪除頁面不能訪問
4.5、測試userA用戶正確登錄
訪問 http://localhost:8080/, 輸入如下信息
點擊提交,然后顯示的頁面如下
分別點擊添加、刪除、查詢、測試頁面,分別顯示的頁面如下
結果:擁有 user 角色的用戶可以訪問 查詢頁面、測試頁面,除了添加頁面、刪除頁面不能訪問
5、結語
- 該項目不會將過多基礎,直接實戰,比較使用有一點基礎的。
- 該項目中沒有使用密碼加密,如果多人瀏覽并反饋需要,我可以再寫篇密碼加密認證的
- 都是原創,希望看到這的能夠點個贊支持一些。
- 代碼和數據庫sql都放在github,鏈接如下:https://github.com/JiuRiYunYue/springboot-mybatis-shiro.git
總結
以上是生活随笔為你收集整理的一篇搞定 SpringBoot+Mybatis+Shiro 实现多角色权限管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于0x3f3f3f3f(0x四个3f)
- 下一篇: Spring Security | 轻松