Springboot 快速集成 Shiro权限管理
Posted 林深时见鹿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Springboot 快速集成 Shiro权限管理相关的知识,希望对你有一定的参考价值。
SpringBoot2.0集成Shiro
数据库表结构:
1 /* 2 Navicat Premium Data Transfer 3 4 Source Server : 本机mysql 5 Source Server Type : MySQL 6 Source Server Version : 50527 7 Source Host : localhost:3306 8 Source Schema : shiro3 9 10 Target Server Type : MySQL 11 Target Server Version : 50527 12 File Encoding : 65001 13 14 Date: 30/06/2020 16:49:48 15 */ 16 17 SET NAMES utf8mb4; 18 SET FOREIGN_KEY_CHECKS = 0; 19 20 -- ---------------------------- 21 -- Table structure for sys_permissions 22 -- ---------------------------- 23 DROP TABLE IF EXISTS `sys_permissions`; 24 CREATE TABLE `sys_permissions` ( 25 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT \'编号\', 26 `permission` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'权限编号\', 27 `description` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'权限描述\', 28 `rid` bigint(20) NULL DEFAULT NULL COMMENT \'此权限关联角色的id\', 29 `available` tinyint(1) NULL DEFAULT 0 COMMENT \'是否锁定\', 30 PRIMARY KEY (`id`) USING BTREE, 31 UNIQUE INDEX `idx_sys_permissions_permission`(`permission`) USING BTREE 32 ) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; 33 34 -- ---------------------------- 35 -- Table structure for sys_roles 36 -- ---------------------------- 37 DROP TABLE IF EXISTS `sys_roles`; 38 CREATE TABLE `sys_roles` ( 39 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT \'角色编号\', 40 `role` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'角色名称\', 41 `description` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'角色描述\', 42 `pid` bigint(20) NULL DEFAULT NULL COMMENT \'父节点\', 43 `available` tinyint(1) NULL DEFAULT 0 COMMENT \'是否锁定\', 44 PRIMARY KEY (`id`) USING BTREE, 45 UNIQUE INDEX `idx_sys_roles_role`(`role`) USING BTREE 46 ) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; 47 48 -- ---------------------------- 49 -- Table structure for sys_roles_permissions 50 -- ---------------------------- 51 DROP TABLE IF EXISTS `sys_roles_permissions`; 52 CREATE TABLE `sys_roles_permissions` ( 53 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT \'编号\', 54 `role_id` bigint(20) NULL DEFAULT NULL COMMENT \'角色编号\', 55 `permission_id` bigint(20) NULL DEFAULT NULL COMMENT \'权限编号\', 56 PRIMARY KEY (`id`) USING BTREE 57 ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; 58 59 -- ---------------------------- 60 -- Table structure for sys_users 61 -- ---------------------------- 62 DROP TABLE IF EXISTS `sys_users`; 63 CREATE TABLE `sys_users` ( 64 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT \'编号\', 65 `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'用户名\', 66 `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'密码\', 67 `salt` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'盐值\', 68 `role_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'角色列表\', 69 `locked` tinyint(1) NULL DEFAULT 0 COMMENT \'是否锁定\', 70 PRIMARY KEY (`id`) USING BTREE, 71 UNIQUE INDEX `idx_sys_users_username`(`username`) USING BTREE 72 ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; 73 74 -- ---------------------------- 75 -- Table structure for sys_users_roles 76 -- ---------------------------- 77 DROP TABLE IF EXISTS `sys_users_roles`; 78 CREATE TABLE `sys_users_roles` ( 79 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT \'编号\', 80 `user_id` bigint(20) NULL DEFAULT NULL COMMENT \'用户编号\', 81 `role_id` bigint(20) NULL DEFAULT NULL COMMENT \'角色编号\', 82 PRIMARY KEY (`id`) USING BTREE 83 ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; 84 85 SET FOREIGN_KEY_CHECKS = 1;
项目版本:
1 springboot2.x 2 shiro:1.3.2
Maven依赖:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
springboot中集成shiro相对简单,只需要两个类:一个是shiroConfig类,一个是CustonRealm类。
ShiroConfig类:
顾名思义就是对shiro的一些配置,相对于之前的xml配置。包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。
CustomRealm类:
自定义的CustomRealm继承AuthorizingRealm。并且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。
项目结构:
shiroConfig配置:
1 package com.SC.demo.utils; 2 3 import java.util.LinkedHashMap; 4 import java.util.Map; 5 6 import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 7 import org.apache.shiro.mgt.SecurityManager; 8 import org.apache.shiro.spring.LifecycleBeanPostProcessor; 9 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 10 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 11 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 12 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 13 import org.springframework.beans.factory.annotation.Autowired; 14 import org.springframework.context.annotation.Bean; 15 import org.springframework.context.annotation.Configuration; 16 import org.springframework.context.annotation.DependsOn; 17 18 import com.SC.demo.realm.CustomRealm; 19 20 /** 21 * 描述: 22 * 23 * @author caojing 24 * @create 2019-01-27-13:38 25 */ 26 @Configuration 27 public class ShiroConfig { 28 29 // @Autowired 30 // CustomRealm customRealm; 31 32 @Bean(name = "shiroFilter") 33 public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { 34 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 35 shiroFilterFactoryBean.setSecurityManager(securityManager); 36 shiroFilterFactoryBean.setLoginUrl("/login"); 37 shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); 38 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); 39 // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> 40 filterChainDefinitionMap.put("/webjars/**", "anon"); 41 filterChainDefinitionMap.put("/login", "anon"); 42 filterChainDefinitionMap.put("/", "anon"); 43 filterChainDefinitionMap.put("/front/**", "anon"); 44 filterChainDefinitionMap.put("/api/**", "anon"); 45 46 filterChainDefinitionMap.put("/admin/**", "authc"); 47 filterChainDefinitionMap.put("/user/**", "authc"); 48 //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证 49 filterChainDefinitionMap.put("/**", "authc"); 50 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 51 return shiroFilterFactoryBean; 52 53 } 54 55 @Bean 56 public SecurityManager securityManager() { 57 DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); 58 defaultSecurityManager.setRealm(customRealm()); 59 return defaultSecurityManager; 60 } 61 62 @Bean 63 public CustomRealm customRealm() { 64 CustomRealm customRealm = new CustomRealm(); 65 // 告诉realm,使用credentialsMatcher加密算法类来验证密文 66 customRealm.setCredentialsMatcher(hashedCredentialsMatcher()); 67 customRealm.setCachingEnabled(false); 68 return customRealm; 69 } 70 71 72 @Bean 73 public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { 74 return new LifecycleBeanPostProcessor(); 75 } 76 77 /** 78 * * 79 * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 80 * * 81 * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 82 * * @return 83 */ 84 @Bean 85 @DependsOn({"lifecycleBeanPostProcessor"}) 86 public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { 87 DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); 88 advisorAutoProxyCreator.setProxyTargetClass(true); 89 return advisorAutoProxyCreator; 90 } 91 92 @Bean 93 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { 94 AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 95 authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); 96 return authorizationAttributeSourceAdvisor; 97 } 98 99 //shiro 加密配置 100 @Bean(name = "credentialsMatcher") 101 public HashedCredentialsMatcher hashedCredentialsMatcher() { 102 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); 103 // 散列算法:这里使用MD5算法; 104 hashedCredentialsMatcher.setHashAlgorithmName("md5"); 105 // 散列的次数,比如散列两次,相当于 md5(md5("")); 106 hashedCredentialsMatcher.setHashIterations(2); 107 // storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码 108 hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); 109 return hashedCredentialsMatcher; 110 } 111 112 113 }
shiro的三个核心概念:
Subject: 代表当前正在执行操作的用户,但Subject代表的可以是人,也可以是任何第三方系统帐号。当然每个subject实例都会被绑定到SercurityManger上。
SecurityManger:SecurityManager是Shiro核心,主要协调Shiro内部的各种安全组件,这个我们不需要太关注,只需要知道可以设置自定的Realm。
Realm:用户数据和Shiro数据交互的桥梁。比如需要用户身份认证、权限认证。都是需要通过Realm来读取数据。
shiroFilter方法:
这个方法看名字就知道了:shiro的过滤器,可以设置登录页面(setLoginUrl)、权限不足跳转页面(setUnauthorizedUrl)、具体某些页面的权限控制或者身份认证。
注意:这里是需要设置SecurityManager(setSecurityManager)。
默认的过滤器还有:anno、authc、authcBasic、logout、noSessionCreation、perms、port、rest、roles、ssl、user过滤器。
具体的大家可以查看package org.apache.shiro.web.filter.mgt.DefaultFilter。这个类,常用的也就authc、anno。
securityManager 方法:
查看源码可以知道 securityManager是一个接口类,我们可以看下它的实现类:
具体怎么实现的,感兴趣的同学可以看下。由于项目是一个web项目,所以我们使用的是DefaultWebSecurityManager ,然后设置自己的Realm。
CustomRealm 方法:
将 customRealm的实例化交给spring去管理,当然这里也可以利用注解的方式去注入。
1 package com.SC.demo.realm; 2 3 import org.apache.shiro.SecurityUtils; 4 import org.apache.shiro.authc.*; 5 import org.apache.shiro.authz.AuthorizationInfo; 6 import org.apache.shiro.authz.SimpleAuthorizationInfo; 7 import org.apache.shiro.realm.AuthorizingRealm; 8 import org.apache.shiro.subject.PrincipalCollection; 9 import org.apache.shiro.subject.Subject; 10 import org.apache.shiro.util.ByteSource; 11 import org.springframework.beans.factory.annotation.Autowired; 12 import org.springframework.context.annotation.Bean; 13 import org.springframework.context.annotation.Configuration; 14 import org.springframework.stereotype.Component; 15 16 import com.SC.demo.pojo.Sys_Roles; 17 import com.SC.demo.pojo.Sys_Users; 18 import com.SC.demo.pojo.Sys_permissions; 19 import com.SC.demo.service.serviceimp.UserServiceImp; 20 21 import java.util.HashSet; 22 import java.util.Set; 23 24 /** 25 * @author 作者 :Runaway programmer 26 * @version 创建时间:2019年12月18日 下午4:48:28 27 * 类说明 28 */ 29 30 public class CustomRealm extends AuthorizingRealm { 31 32 @Autowired 33 UserServiceImp userServiceImp; 34 35 @Override 36 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 37 String username = (String) SecurityUtils.getSubject().getPrincipal(); 38 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 39 //从数据库中获取当前用户拥有的权限 存入集合中 再存入SimpleAuthorizationInfo对象里供注解获取对比权限 40 Sys_permissions permission = userServiceImp.getUserRole(username); 41 Set<String> stringSet = new HashSet<>(); 42 stringSet.add(permission.getPermission()); 43 stringSet.add("user:admin"); 44 info.setStringPermissions(stringSet); 45 return info; 46 } 47 48 /** 49 * 这里可以注入userService,为了方便演示,我就写死了帐号了密码 50 * private UserService userService; 51 * <p> 52 * 获取即将需要认证的信息 53 */ 54 @Override 55 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 56 System.out.println("-------身份认证方法--------"); 57 String userName = (String) authenticationToken.getPrincipal(); 58 System.out.println(userName); 59 String userPwd = new String((char[]) authenticationToken.getCredentials()); 60 61 //根据用户名从数据库获取密码 62 Sys_Users userNamePassword = userServiceImp.getUserNamePassword(userName); 63 String password = userNamePassword.getPassword(); 64 String salt = userNamePassword.getSalt(); 65 // String password = "4b91fc877a3f4df7e812f98ebde4b5e5"; 66 if (userName == null) { 67 throw new AccountException("用户名不正确"); 68 } else if (!userPwd.equals(userPwd)) { 69 throw new AccountException("密码不正确"); 70 } 71 //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配 72 //1.当前用户名userName 2.以及数据库查询password 3.盐 (设置的盐 userName+Salt)4.当前realm对象 73 return new SimpleAuthenticationInfo(userName, password, 74 ByteSource.Util.bytes(userName + salt), getName()); 75 76 } 77 }
说明:
自定义的Realm类继承AuthorizingRealm类,并且重载doGetAuthorizationInfo和doGetAuthenticationInfo两个方法。
doGetAuthorizationInfo: 权限认证,即登录过后,每个身份不一定,对应的所能看的页面也不一样。
doGetAuthenticationInfo:身份认证。即登录通过账号和密码验证登陆人的身份信息。
controller类:
1 package com.SC.demo.controller; 2 3 import org.apache.shiro.SecurityUtils; 4 import org.apache.shiro.authc.AuthenticationException; 5 import org.apache.shiro.authc.ExcessiveAttemptsException; 6 import org.apache.shiro.authc.IncorrectCredentialsException; 7 import org.apache.shiro.authc.LockedAccountException; 8 import org.apache.shiro.authc.UnknownAccountException; 9 import org.apache.shiro.authc.UsernamePasswordToken; 10 import org.apache.shiro.subject.Subject; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 import org.springframework.web.bind.annotation.RequestMethod; 13 import org.springframework.web.bind.annotation.RequestParam; 14 import org.springframework.web.bind.annotation.ResponseBody; 15 import org.springframework.web.bind.annotation.RestController; 16 17 import com.SC.demo.utils.MD5utils; 18 19 /** 20 * @author 作者 :Runaway programmer 21 * @version 创建时间:2019年12月18日 下午4:49:50 22 * 类说明 23 */ 24 @RestController 25 public class HomeIndexController { 26 27 @RequestMapping(value = "/login", method = RequestMethod.GET) 28 @ResponseBody 29 public String defaultLogin() { 30 return "首页"; 31 } 32 33 @RequestMapping(value = "/login", method = RequestMethod.POST) 34 @ResponseBody 35 public String login(@RequestParam("username") String username, @RequestParam("password") String password) { 36 // 从SecurityUtils里边创建一个 subject 37 Subject subject = SecurityUtils.getSubject(); 38 // 在认证提交前准备 token(令牌) 39 UsernamePasswordToken token = new UsernamePasswordToken(username, password); 40 //MD5加盐获取值 41 String md5Pwd = MD5utils.MD5Pwd(username, password); 42 System.out.println(md5Pwd); 43 // 执行认证登陆 44 try { 45 subject.login(token); 46 } catch (UnknownAccountException uae) { 47 return "未知账户"; 48 } catch (IncorrectCredentialsException ice) { 49 return "密码不正确"; 50 } catch (LockedAccountException lae) { 51 return "账户已锁定"; 52 } catch (ExcessiveAttemptsException eae) { 53 return "用户名或密码错误次数过多"; 54 } catch (AuthenticationException ae) { 55 return "用户名或密码不正确!"; 56 } 57 if (subject.isAuthenticated()) { 58 return "登录成功"; 59 } else { 60 token.clear(); 61 return "登录失败"; 62 } 63 } 64 65 }
利用注解配置权限:
其实,我们完全可以不用注解的形式去配置权限,因为在之前已经加过了:DefaultFilter类中有perms(类似于perms[user:add])这种形式的。但是试想一下,这种控制的粒度可能会很细,具体到某一个类中的方法,那么如果是配置文件配,是不是每个方法都要加一个perms?但是注解就不一样了,直接写在方法上面,简单快捷。
很简单,主需要在config类中加入如下代码,就能开启注解:
1 @Bean 2 public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { 3 return new LifecycleBeanPostProcessor(); 4 } 5 6 /** 7 * * 8 * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 9 * * 10 * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 11 * * @return 12 */ 13 @Bean 14 @DependsOn({"lifecycleBeanPostProcessor"}) 15 public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { 16 DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); 17 advisorAutoProxyCreator.setProxyTargetClass(true); 18 return advisorAutoProxyCreator; 19 } 20 21 @Bean 22 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { 23 AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 24 authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); 25 return authorizationAttributeSourceAdvisor; 26 } 27 28 //shiro 加密配置 29 @Bean(name = "credentialsMatcher") 30 public HashedCredentialsMatcher hashedCredentialsMatcher() { 31 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); 32 // 散列算法:这里使用MD5算法; 33 hashedCredentialsMatcher.setHashAlgorithmName("md5"); 34 // 散列的次数,比如散列两次,相当于 md5(md5("")); 35 hashedCredentialsMatcher.setHashIterations(2); 36 // storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码 37 hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); 38 return hashedCredentialsMatcher; 39 }
注解权限Controller测试类
1 package com.SC.demo.controller; 2 3 import org.apache.shiro.authz.annotation.RequiresPermissions; 4 import org.apache.shiro.authz.annotation.RequiresRoles; 5 import org.springframework.stereotype.Controller; 以上是关于Springboot 快速集成 Shiro权限管理的主要内容,如果未能解决你的问题,请参考以下文章SpringBoot 集成 Shiro:使用Shiro的权限管理
Spring boot 入门:集成 Shiro 实现登陆认证和权限管理