前言:
Shiro中本身就提供了sessionManager和sessionDAO,我們可以把shiro和redis集成起來,把session持久化到Redis中,需要使用的時候從Redis中可以獲取對應的session。
本章介紹如下幾個功能:
1.當用戶沒有登陸時只能訪問登陸界面
2.當用戶登陸成功后,只能訪問該用戶下僅有的權限
3.記住登錄用戶(rememberMe)
4.一個賬號可以多人同時登錄
說明:本章案例做了簡化,僅作為springboot+shiro+redis項目整合為參考,適合入門使用,親測有效。
一、數據庫設計
表設計思路:用戶對應角色,角色包含擁有的菜單和其他權限,菜單也對應着某個權限,說明有這個菜單就有對應的權限(權限表包含菜單ID),權限表里不設置菜單ID就是其他權限。
1.SQL

/* Navicat Premium Data Transfer Source Server : localhost Source Server Type : MySQL Source Server Version : 50712 Source Host : localhost:3306 Source Schema : boot_shiro_redis Target Server Type : MySQL Target Server Version : 50712 File Encoding : 65001 Date: 10/03/2020 17:09:41 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for manage_menu -- ---------------------------- DROP TABLE IF EXISTS `manage_menu`; CREATE TABLE `manage_menu` ( `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID', `NAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '路徑名稱', `ICON` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '圖標class(el)', `URL` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '路徑地址', `PARENT_ID` int(11) NULL DEFAULT NULL COMMENT '父節點ID,父節點同樣在本目錄下', PRIMARY KEY (`ID`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜單管理表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of manage_menu -- ---------------------------- INSERT INTO `manage_menu` VALUES (1, '首頁', 'el-icon-s-home', '/index', NULL); INSERT INTO `manage_menu` VALUES (2, '權限管理', 'fa fa-book', '/managePermission/getPermissionAll', NULL); INSERT INTO `manage_menu` VALUES (3, '人員管理', 'fa fa-book', '/manageUser/getUserAll', 2); -- ---------------------------- -- Table structure for manage_permission -- ---------------------------- DROP TABLE IF EXISTS `manage_permission`; CREATE TABLE `manage_permission` ( `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `NAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '權限名稱', `RESOURCE` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '資源地址', `SN` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '描述', `MENU_ID` int(11) NULL DEFAULT NULL COMMENT '菜單表中的ID', PRIMARY KEY (`ID`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '路徑權限' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of manage_permission -- ---------------------------- INSERT INTO `manage_permission` VALUES (1, '查看所有權限', '/managePermission/getPermissionAll', 'managePermission:list', 3); INSERT INTO `manage_permission` VALUES (2, '查看所有人員', '/manageUser/getUserAll', 'manageUser:list', NULL); -- ---------------------------- -- Table structure for manage_roles -- ---------------------------- DROP TABLE IF EXISTS `manage_roles`; CREATE TABLE `manage_roles` ( `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `NAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名', `MENUS_ID` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜單的ID(多個菜單由逗號分隔)', `PERMISSIONS_ID` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '其余權限的ID(多個權限由逗號分隔)', PRIMARY KEY (`ID`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色權限' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of manage_roles -- ---------------------------- INSERT INTO `manage_roles` VALUES (1, '管理員', '1,2,3', '3'); INSERT INTO `manage_roles` VALUES (2, '普通用戶', '1', NULL); -- ---------------------------- -- Table structure for manage_user -- ---------------------------- DROP TABLE IF EXISTS `manage_user`; CREATE TABLE `manage_user` ( `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '管理員ID', `USERNAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登陸名', `PASSWORD` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密碼', `ROLE_ID` int(11) NOT NULL DEFAULT 0 COMMENT '對應的角色Id', PRIMARY KEY (`ID`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '登錄用戶' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of manage_user -- ---------------------------- INSERT INTO `manage_user` VALUES (1, 'admin', '4ec847db9bc2bad60e4279cce1fad5db', 1); INSERT INTO `manage_user` VALUES (4, 'user', '4e0374eaa5fd58d90a549cac95a657ab', 2); SET FOREIGN_KEY_CHECKS = 1;
二、pom.xml添加依賴

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--mysql數據庫連接驅動--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- mybatis-plus-boot-starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!-- 模板引擎 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.0</version> </dependency> <!--lombok代碼簡化工具--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.6</version> </dependency> <!-- shiro spring. --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency> <!--使用的是shiro-redis開源插件--> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>2.4.2.1-RELEASE</version> <exclusions> <exclusion> <artifactId>shiro-core</artifactId> <groupId>org.apache.shiro</groupId> </exclusion> <exclusion> <artifactId>shiro-core</artifactId> <groupId>org.apache.shiro</groupId> </exclusion> <exclusion> <artifactId>jedis</artifactId> <groupId>redis.clients</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--thymeleaf 頁面模板依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
三、application.yml配置redis

spring
redis:
database: 1
host: 127.0.0.1
port: 6379
password: # 密碼(默認為空)
timeout: 6000 # 連接超時時長(毫秒)
jedis:
pool:
max-active: 1000 # 連接池最大連接數(使用負值表示沒有限制)
max-wait: -1 # 連接池最大阻塞等待時間(使用負值表示沒有限制)
max-idle: 10 # 連接池中的最大空閑連接
min-idle: 5 # 連接池中的最小空閑連接
四、添加配置類
1.shiro配置類

package com.example.demo.config; import com.example.demo.shiro.MySessionManager; import com.example.demo.shiro.realms.UserRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * @author: * @since: * @description: */ @Configuration public class ShiroConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private Integer port; @Value("${spring.redis.timeout}") private Integer timeout; @Value("${spring.redis.password}") private String password; @Bean ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager());// 必須設置 SecurityManager安全管理器 bean.setSuccessUrl("/index"); bean.setUnauthorizedUrl("/unauthorizedurl"); Map<String, String> map = new LinkedHashMap<>(); //匿名使用 map.put("/login", "anon"); map.put("/loginPage","anon"); map.put("/logout", "logout");// 配置退出過濾器,其中的具體的退出代碼Shiro已經替我們實現了 map.put("/**","authc"); //配置記住我或認證通過可以訪問的地址 map.put("/**", "user"); bean.setLoginUrl("/loginPage"); bean.setFilterChainDefinitionMap(map); return bean; } /** * 開啟shiro aop注解支持. 使用代理方式; * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * DefaultAdvisorAutoProxyCreator實現了BeanProcessor接口, * 當ApplicationContext讀如所有的Bean配置信息后,這個類將掃描上下文, * 找出所有的Advistor(一個切入點和一個通知的組成),將這些Advisor應用到所有符合切入點的Bean中 * @return */ @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm()); // 自定義session管理 使用redis securityManager.setSessionManager(sessionManager()); // 自定義緩存實現 使用redis securityManager.setCacheManager(cacheManager()); //自定義rememberMe //把cookie管理器交給SecurityManager securityManager.setRememberMeManager(rememberMeManager()); return securityManager; } @Bean public UserRealm myRealm() { UserRealm userRealm = new UserRealm(); // 配置 加密 (在加密后,不配置的話會導致登陸密碼失敗) userRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return userRealm; } /** * 密碼校驗規則HashedCredentialsMatcher * 這個類是為了對密碼進行編碼的 , * 防止密碼在數據庫里明碼保存 , 當然在登陸認證的時候 , * 這個類也負責對form里輸入的密碼進行編碼 * 處理認證匹配處理器:如果自定義需要實現繼承HashedCredentialsMatcher */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //指定加密方式為MD5 credentialsMatcher.setHashAlgorithmName("MD5"); //加密次數 credentialsMatcher.setHashIterations(1024); //此時用的是密碼加密用的是 Hex 編碼; false 時用 Base64 編碼 credentialsMatcher.setStoredCredentialsHexEncoded(true); return credentialsMatcher; } //自定義sessionManager @Bean public SessionManager sessionManager() { MySessionManager mySessionManager = new MySessionManager(); mySessionManager.setSessionIdUrlRewritingEnabled(false);//url 不顯示sessionID mySessionManager.setSessionDAO(redisSessionDAO()); return mySessionManager; } /** * RedisSessionDAO shiro sessionDao層的實現 通過redis * 使用的是shiro-redis開源插件 */ @Bean public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } //@Bean public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(host); redisManager.setExpire(1800);// 配置緩存過期時間 redisManager.setTimeout(timeout); redisManager.setPort(port); redisManager.setPassword(password); return redisManager; } /** * cookie管理對象; * rememberMeManager()方法是生成rememberMe管理器,而且要將這個rememberMe管理器設置到securityManager中 * @return */ public CookieRememberMeManager rememberMeManager(){ CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); return cookieRememberMeManager; } public SimpleCookie rememberMeCookie(){ SimpleCookie simpleCookie = new SimpleCookie(); simpleCookie.setName("boot-shiro-rememberMe"); simpleCookie.setMaxAge(200000);//設置cookie的生效時間 simpleCookie.setHttpOnly(true); return simpleCookie; } }
1.1自定義MySessionManager 獲取sessionId

public class MySessionManager extends DefaultWebSessionManager { private static final String AUTHORIZATION = "Authorization"; private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; public MySessionManager() { super(); } @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION); //如果請求頭中有 Authorization 則其值為sessionId if (!StringUtils.isEmpty(id)) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return id; } else { //否則按默認規則從cookie取sessionId return super.getSessionId(request, response); } } }
2.redis配置類

package com.example.demo.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * redis配置類 * @program: springbootdemo * @Description: */ @Configuration @EnableCaching //開啟注解 public class RedisConfig extends CachingConfigurerSupport { @Autowired private RedisTemplate redisTemplate; @Bean public RedisTemplate<String, Object> stringSerializerRedisTemplate(){ RedisSerializer<String> stringSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringSerializer); redisTemplate.setValueSerializer(stringSerializer); redisTemplate.setHashKeySerializer(stringSerializer); redisTemplate.setHashValueSerializer(stringSerializer); return redisTemplate; } /** * retemplate相關配置 * @param factory * @return */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); // 配置連接工廠 template.setConnectionFactory(factory); //使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值(默認使用JDK的序列化方式) Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); // 指定要序列化的域,field,get和set,以及修飾符范圍,ANY是都有包括private和public om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會跑出異常 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); // 值采用json序列化 template.setValueSerializer(jacksonSeial); //使用StringRedisSerializer來序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); // 設置hash key 和value序列化模式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(jacksonSeial); template.afterPropertiesSet(); return template; } /** * 對hash類型的數據操作 * * @param redisTemplate * @return */ @Bean public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForHash(); } /** * 對redis字符串類型數據操作 * * @param redisTemplate * @return */ @Bean public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForValue(); } /** * 對鏈表類型的數據操作 * * @param redisTemplate * @return */ @Bean public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForList(); } /** * 對無序集合類型的數據操作 * * @param redisTemplate * @return */ @Bean public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForSet(); } /** * 對有序集合類型的數據操作 * * @param redisTemplate * @return */ @Bean public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForZSet(); } }
五、自定義Realm

package com.example.demo.shiro.realms; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.example.demo.entity.ManagePermission; import com.example.demo.entity.ManageRoles; import com.example.demo.entity.ManageUser; import com.example.demo.service.ManagePermissionService; import com.example.demo.service.ManageRolesService; import com.example.demo.service.ManageUserService; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; /** * @author: * @since: * @description: */ public class UserRealm extends AuthorizingRealm { @Autowired private ManageUserService userService; @Autowired private ManageRolesService manageRolesService; @Autowired private ManagePermissionService managePermissionService; //執行授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { // TODO Auto-generated method stub System.out.println("授權"); //獲取當前登錄用戶 Subject subject = SecurityUtils.getSubject(); ManageUser user = (ManageUser) subject.getPrincipal(); //查詢用戶對應的角色 Integer roleId = user.getRoleId(); ManageRoles manageRoles = manageRolesService.selectById(roleId); if(manageRoles!=null){//角色不為空 //獲取角色對應的菜單 String menus = manageRoles.getMenusId(); String[] menuId = menus.split(","); //菜單對應的權限 List<ManagePermission> menuPermission = managePermissionService.selectList(new EntityWrapper<ManagePermission>().in("MENU_ID", Arrays.asList(menuId))); //其他相關權限 List<ManagePermission> managePermissions = new ArrayList<>(); String permissions = manageRoles.getPermissionsId(); if(StringUtils.isNotBlank(permissions)){ String[] permissionsId = permissions.split(","); managePermissions = managePermissionService.selectBatchIds(Arrays.asList(permissionsId)); } //當前登錄用戶的所有權限 managePermissions.addAll(menuPermission); List<String> sn = managePermissions.stream().map(ManagePermission::getSn).collect(Collectors.toList()); System.out.println(String.valueOf(sn)); //給資源授權 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addStringPermissions(sn); simpleAuthorizationInfo.addRole(manageRoles.getName()); return simpleAuthorizationInfo; } return null; } //執行認證邏輯 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // TODO Auto-generated method stub System.out.println("認證"); //shiro判斷邏輯 UsernamePasswordToken user = (UsernamePasswordToken) token; ManageUser realUser = new ManageUser(); realUser.setUsername(user.getUsername()); realUser.setPassword(String.copyValueOf(user.getPassword())); ManageUser newUser = userService.selectOne(new EntityWrapper<ManageUser>().eq("USERNAME",realUser.getUsername())); if(newUser == null){ //用戶名錯誤 //shiro會拋出UnknownAccountException異常 return null; } System.out.println("認證用戶:"+realUser); ByteSource credentialsSalt = ByteSource.Util.bytes(newUser.getUsername()); return new SimpleAuthenticationInfo(newUser,newUser.getPassword(),credentialsSalt,getName()); } /** * 在使用登出功能的時候,會觸發的回調 * @param principals */ @Override public void onLogout(PrincipalCollection principals) { super.onLogout(principals); } public static void main(String[] args) { String hashAlgorithName = "MD5"; String password = "root"; int hashIterations = 1024;//加密次數 ByteSource credentialsSalt = ByteSource.Util.bytes("admin");//鹽值 Object obj = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations); System.out.println(obj); } }
六、VO層
1.菜單樹

@Data @Builder @NoArgsConstructor @EqualsAndHashCode @AllArgsConstructor @Accessors(chain = true) public class ManageMenuVO implements Serializable { private Integer id; /** * 路徑名稱 */ private String name; /** * 圖標class(elementui) */ private String icon; /** * 路徑地址 */ private String url; /** * 父節點ID,父節點同樣在本目錄下 */ private Integer parentId; private List<ManageMenuVO> childrens; }
七、Controller層
1.登錄

package com.example.demo.controller; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; /** * @author: * @since: * @description: */ @Slf4j @Controller public class LoginController { @PostMapping(value = "/login") public String createUser(String username, String password, String rememberMe, Model model) { Map restMap = new HashMap<String,Object>(); Integer code = 200; String msg = "登錄成功"; Object data = null; Session session = SecurityUtils.getSubject().getSession(); log.info("登錄的sessionId "+session.getId()); //獲取Subject Subject subject = SecurityUtils.getSubject(); //封裝用戶數據 UsernamePasswordToken token = new UsernamePasswordToken(username, password); //執行登錄方法 try { if(rememberMe!=null){token.setRememberMe(Boolean.parseBoolean(rememberMe));} subject.login(token); //登錄成功 return "redirect:index"; } catch (UnknownAccountException e) { code = 500; msg = "用戶名錯誤"; data = e; } catch (IncorrectCredentialsException e) { code = 500; msg = "密碼錯誤"; data = e; } restMap.put("code",code); restMap.put("msg",msg); restMap.put("data",data); model.addAllAttributes(restMap); return "err"; } @RequestMapping(value = "/logout") public void logout() { System.err.println("退出"); Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { subject.logout(); // session 會銷毀,在sessionlistener監聽session銷毀,清理權限緩存 } } }
2.頁面跳轉

@Controller public class RouterController { @Autowired private ManageMenuService manageMenuService; @GetMapping("/loginPage") public String loginPage(){ return "/loginPage"; } @GetMapping("/index") public String indexPage(Model model){ //獲取Subject Subject subject = SecurityUtils.getSubject(); Map restMap = new HashMap<String,Object>(); restMap.put("menuList",manageMenuService.selectMenuTree()); restMap.put("data",String.valueOf(subject.getSession().getId())); System.out.println(restMap); model.addAllAttributes(restMap); return "/index"; } }
3.查看所有權限

@RestController @RequestMapping("/managePermission") public class ManagePermissionController { @Autowired private ManagePermissionService managePermissionService; @GetMapping("/getPermissionAll") @RequiresPermissions("managePermission:list") public List<ManagePermission> RequiresPermissions(){return managePermissionService.selectList(null);} }
4.查看所有人員

@RestController @RequestMapping("/manageUser") public class ManageUserController { @Autowired private ManageUserService manageUserService; @GetMapping("/getUserAll") @RequiresPermissions("manageUser:list") public List<ManageUser> getUserAll(){return manageUserService.selectList(null);} }
八、權限不足全局異常

@ResponseBody @ControllerAdvice public class GlobalException { //授權異常 @ExceptionHandler({AuthorizationException.class}) public Object unauthorizedException() { Map<String, Object> map = new HashMap<String, Object>(); map.put("msg", "權限不足!"); System.err.println("權限不足"); return map; } }
九、Html
1.登錄

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>login</title> </head> <body> <form action="/login" method="POST"> <input type="text" name="username" id=""></br> <input type="password" name="password" id=""></br> <input type ="checkbox" name ="rememberMe" value="true" />記住我 <input type="submit" value="登錄"> </form> </body> </html>
2.登錄成功

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>首頁</title> </head> <body> <h1>菜單</h1> <div th:each="menu:${menuList}"> <a th:href="${menu.url}" ><h4 th:text="${menu.name}"></h4></a> <div th:each="child:${menu.childrens}" style="margin-left: 50px"> <a th:href="${child.url}" > <h4 th:text="${child.name}"></h4></a> </div> </div> </br> <a href="/logout">退出登錄</a> </body> </html>
3.錯誤頁面

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>錯誤頁面</title> </head> <body> <h2 th:text="${code}" style="color: red"></h2> <h1 th:text="${msg}" ></h1> <h3 th:text="${data}"></h3> </body> </html>
項目結構
本章沒有過多的贅述 entity、mapper、service層,可參考【SpringBoot整合Mybatis-Plus】