SpringBoot+Shiro+Redis整合以及实现记住我(RememberMe)功能
前言:
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】