Author: Sans_
juejin.im/post/5d087d605188256de9779e64
1. Description
Shiro is a security framework that is primarily used in projects for authentication, authorization, encryption, and user session management. Although Shiro is not as rich as SpringSecurity, it is lightweight and simple, and Shiro is generally qualified for business needs in projects.
2. Project environment
-
MyBatis-Plus version: 3.1.0
-
SpringBoot version: 2.1.5
-
JDK version: 1.8
-
Shiro version: 1.4
-
Shiro-redis Plug-in Version: 3.1.0
Data table (SQL file in project): The passwords for test numbers in the database are encrypted, all of which are 123456
Maven relies on the following:
<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>
<!-- AOP rely on,Be sure to add,Otherwise, permission interception validation will not take effect -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- lombok Plug-in unit -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- mybatisPlus Core Library -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!-- Introduce Ali Database Connection Pool -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!-- Shiro Core Dependency -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- Shiro-redis Plug-in unit -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
<!-- StringUitlS tool -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
The configuration is as follows:
# Configure Port
server:
port: 8764
spring:
# Configure Data Source
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/my_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
# Redis data source
redis:
host: localhost
port: 6379
timeout: 6000
password: 123456
jedis:
pool:
max-active: 1000 # Maximum number of connections to the connection pool (use a negative value to indicate no limit)
max-wait: -1 # Maximum blocking wait time for connection pool (use a negative value to indicate no limit)
* max-idle: 10 # Maximum idle connection in connection pool
Minimum idle connection in connection pool
# mybatis-plus related configuration
mybatis-plus:
# XML scan, with multiple directories separated by commas or semicolons (telling Mapper the location of the corresponding XML file)
mapper-locations: classpath:mapper/*.xml
The following configurations have default values, which can be set without
global-config:
db-config:
#Primary Key Type AUTO:'Database ID Self-Increasing'INPUT:'User Input ID', ID_WORKER:'Global Unique ID (Number Type Unique ID)', UUID:'Global Unique ID UUID';
id-type: auto
#Field Policy IGNORED:'Ignore Judgment'(NOT_NULL:'Non-NULL Judgment') NOT_EMPTY:'Non-empty Judgment'
field-strategy: NOT_EMPTY
#Database type
db-type: MYSQL
configuration:
# Turn on Automatic Hump naming rule mapping: a similar mapping from database column names to Java attribute hump naming
map-underscore-to-camel-case: true
# If a column with null values is included in the query result, MyBatis will not map this field when mapping
call-setters-on-nulls: true
# This configuration prints the sql executed and can be used during development or testing
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2. Write project base classes
User entities, Dao,Service, etc. are omitted here, see source code
Write an Exception class to handle Shiro privilege interception exceptions
Create SHA256Util Encryption Tool
Create Spring Tool
/**
* @Description Spring Context Tool Class
* @Author Sans
* @CreateTime 2019/6/17 13:40
*/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext context;
/**
* Spring Determines if the bean is a subclass of ApplicationContextAware after initialization
* If the class is the setApplicationContext() method, the ApplicationContext in the container is passed in as a parameter
* @Author Sans
* @CreateTime 2019/6/17 16:58
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
/**
* Returns the specified Bean through Name
* @Author Sans
* @CreateTime 2019/6/17 16:03
*/
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
Create Shiro Tool
/**
* @Description Shiro Tool class
* @Author Sans
* @CreateTime 2019/6/15 16:11
*/
public class ShiroUtils {
/** Private Constructor **/
private ShiroUtils(){}
private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);
/**
* Get the current user Session
* @Author Sans
* @CreateTime 2019/6/17 17:03
* @Return SysUserEntity User Information
*/
public static Session getSession() {
return SecurityUtils.getSubject().getSession();
}
/**
* Logout
* @Author Sans
* @CreateTime 2019/6/17 17:23
*/
public static void logout() {
SecurityUtils.getSubject().logout();
}
/**
* Get current user information
* @Author Sans
* @CreateTime 2019/6/17 17:03
* @Return SysUserEntity User Information
*/
public static SysUserEntity getUserInfo() {
return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
}
/**
* Delete user cache information
* @Author Sans
* @CreateTime 2019/6/17 13:57
* @Param username User Name
* @Param isRemoveSession Whether to delete Session
* @Return void
*/
public static void deleteCache(String username, boolean isRemoveSession){
//Get Session from Cache
Session session = null;
Collection<Session> sessions = redisSessionDAO.getActiveSessions();
SysUserEntity sysUserEntity;
Object attribute = null;
for(Session sessionInfo : sessions){
//Traverse Session to find the Session corresponding to the user name
attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (attribute == null) {
continue;
}
sysUserEntity = (SysUserEntity) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
if (sysUserEntity == null) {
continue;
}
if (Objects.equals(sysUserEntity.getUsername(), username)) {
session=sessionInfo;
}
}
if (session == null||attribute == null) {
return;
}
//Delete session
if (isRemoveSession) {
redisSessionDAO.delete(session);
}
//Delete Cache and reauthorize when accessing restricted interfaces
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
Authenticator authc = securityManager.getAuthenticator();
((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);
}
}
Create Shiro's SesionId Generator
3. Writing Shiro Core Classes
Create Realm for Authorization and Authentication
/**
* @Description Shiro Privilege matching and account password matching
* @Author Sans
* @CreateTime 2019/6/15 11:27
*/
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
/**
* Authorization Permissions
* Shiro looks in the cache when the user authenticates permissions, and if no data is found, executes this method to look up permissions and put them in the cache
* @Author Sans
* @CreateTime 2019/6/12 11:44
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
SysUserEntity sysUserEntity = (SysUserEntity) principalCollection.getPrimaryPrincipal();
//Get User ID
Long userId =sysUserEntity.getUserId();
//Authorization and processing are available here
Set<String> rolesSet = new HashSet<>();
Set<String> permsSet = new HashSet<>();
//Query roles and permissions (self-querying here based on business)
List<SysRoleEntity> sysRoleEntityList = sysRoleService.selectSysRoleByUserId(userId);
for (SysRoleEntity sysRoleEntity:sysRoleEntityList) {
rolesSet.add(sysRoleEntity.getRoleName());
List<SysMenuEntity> sysMenuEntityList = sysMenuService.selectSysMenuByRoleId(sysRoleEntity.getRoleId());
for (SysMenuEntity sysMenuEntity :sysMenuEntityList) {
permsSet.add(sysMenuEntity.getPerms());
}
}
//Pass the discovered permissions and roles into authorizationInfo, respectively
authorizationInfo.setStringPermissions(permsSet);
authorizationInfo.setRoles(rolesSet);
return authorizationInfo;
}
/**
* identity authentication
* @Author Sans
* @CreateTime 2019/6/12 12:36
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//Get the user's input account.
String username = (String) authenticationToken.getPrincipal();
//Find the User object from the database through username, and verify if found
//In a real project, the cache can be cached according to the actual situation. If not, Shiro also has a time interval mechanism and will not repeat this method in 2 minutes.
SysUserEntity user = sysUserService.selectUserByName(username);
//Determine if an account exists
if (user == null) {
throw new AuthenticationException();
}
//Determine if accounts are frozen
if (user.getState()==null||user.getState().equals("PROHIBIT")){
throw new LockedAccountException();
}
//Verify
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //User name
user.getPassword(), //Password
ByteSource.Util.bytes(user.getSalt()), //Set salt value
getName()
);
//Verify successful start kicking (clear cache and ession)
ShiroUtils.deleteCache(username,true);
return authenticationInfo;
}
}
Create SessionManager Class
Create ShiroConfig Configuration Class
/**
* @Description Shiro Configuration Class
* @Author Sans
* @CreateTime 2019/6/10 17:42
*/
@Configuration
public class ShiroConfig {
private final String CACHE_KEY = "shiro:cache:";
private final String SESSION_KEY = "shiro:session:";
private final int EXPIRE = 1800;
//Redis Configuration
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
/**
* Turn on Shiro-aop annotation support
* @Attention Use proxy mode so code support needs to be turned on
* @Author Sans
* @CreateTime 2019/6/12 8:38
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro Basic Configuration
* @Author Sans
* @CreateTime 2019/6/12 8:42
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//Note that the filter configuration order cannot be reversed
//Configure filtering: links that will not be blocked
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/userLogin/**", "anon");
filterChainDefinitionMap.put("/**", "authc");
// Configure shiro default login interface address, login interface jump in front-end and back-end separation should be controlled by front-end routing, background only returns json data
shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* Security Manager
* @Author Sans
* @CreateTime 2019/6/12 10:34
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//Custom Ssession Management
securityManager.setSessionManager(sessionManager());
//Custom Cache implementation
securityManager.setCacheManager(cacheManager());
//Custom Realm Validation
securityManager.setRealm(shiroRealm());
return securityManager;
}
/**
* Authenticator
* @Author Sans
* @CreateTime 2019/6/12 10:37
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
/**
* Voucher Matcher
* Password verification is handed over to Shiro's SimpleAuthenticationInfo for processing, where matching configurations are made
* @Author Sans
* @CreateTime 2019/6/12 10:48
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
//Hash algorithm: SHA256 algorithm is used here;
shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
//Number of hashes, such as two, equal to md5(md5("));
shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
return shaCredentialsMatcher;
}
/**
* Configure Redis Manager
* @Attention Using the shiro-redis open source plug-in
* @Author Sans
* @CreateTime 2019/6/12 11:06
*/
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
/**
* Configure Cache Manager
* Used to store permissions and role identities to Redis
* @Attention Using the shiro-redis open source plug-in
* @Author Sans
* @CreateTime 2019/6/12 12:37
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setKeyPrefix(CACHE_KEY);
//Configuring caches requires that entity classes placed in session s must have an id identity
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
/**
* SessionID generator
* @Author Sans
* @CreateTime 2019/6/12 13:12
*/
@Bean
public ShiroSessionIdGenerator sessionIdGenerator(){
return new ShiroSessionIdGenerator();
}
/**
* Configure RedisSessionDAO
* @Attention Using the shiro-redis open source plug-in
* @Author Sans
* @CreateTime 2019/6/12 13:44
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(expire);
return redisSessionDAO;
}
/**
* Configure Session Manager
* @Author Sans
* @CreateTime 2019/6/12 14:25
*/
@Bean
public SessionManager sessionManager() {
ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
shiroSessionManager.setSessionDAO(redisSessionDAO());
return shiroSessionManager;
}
}
4. Implement Rights Control
Shiro can use code or annotations to control permissions. Usually we use annotation control, which is not only simple and convenient, but also more flexible. There are five Shiro annotations:
In general, we do privilege control in our projects. RequiresPermissions and RequiresRoles are used the most, allowing multiple roles and privileges. The default logic is AND, which means having both of these methods to access them. You can set them as parameters in your comments to OR
Example
Use order: Shiro annotations are sequential, and when multiple annotations are on one method, they are checked one by one until they all pass. The default intercept order is RequiresRoles->RequiresPermissions->RequiresAuthentication->
RequiresUser->RequiresGuest
Example
Create UserRoleController Role Interception Test Class
/**
* @Description Role testing
* @Author Sans
* @CreateTime 2019/6/19 11:38
*/
@RestController
@RequestMapping("/role")
public class UserRoleController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
@Autowired
private SysRoleMenuService sysRoleMenuService;
/**
* Administrator Role Test Interface
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> Return results
*/
@RequestMapping("/getAdminInfo")
@RequiresRoles("ADMIN")
public Map<String,Object> getAdminInfo(){
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","Here is the interface that only the Administrator role can access");
return map;
}
/**
* User Role Test Interface
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> Return results
*/
@RequestMapping("/getUserInfo")
@RequiresRoles("USER")
public Map<String,Object> getUserInfo(){
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","Here is the interface that only user roles can access");
return map;
}
/**
* Role Test Interface
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> Return results
*/
@RequestMapping("/getRoleInfo")
@RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)
@RequiresUser
public Map<String,Object> getRoleInfo(){
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","As long as there are ADMIN perhaps USER Role-accessible interfaces");
return map;
}
/**
* Logout (Test Logout)
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> Return results
*/
@RequestMapping("/getLogout")
@RequiresUser
public Map<String,Object> getLogout(){
ShiroUtils.logout();
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","Logout");
return map;
}
}
Create UserMenuController Privilege Interception Test Class
/**
* @Description Permission Test
* @Author Sans
* @CreateTime 2019/6/19 11:38
*/
@RestController
@RequestMapping("/menu")
public class UserMenuController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
@Autowired
private SysRoleMenuService sysRoleMenuService;
/**
* Get a collection of user information
* @Author Sans
* @CreateTime 2019/6/19 10:36
* @Return Map<String,Object> Return results
*/
@RequestMapping("/getUserInfoList")
@RequiresPermissions("sys:user:info")
public Map<String,Object> getUserInfoList(){
Map<String,Object> map = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
map.put("sysUserEntityList",sysUserEntityList);
return map;
}
/**
* Get Role Information Collection
* @Author Sans
* @CreateTime 2019/6/19 10:37
* @Return Map<String,Object> Return results
*/
@RequestMapping("/getRoleInfoList")
@RequiresPermissions("sys:role:info")
public Map<String,Object> getRoleInfoList(){
Map<String,Object> map = new HashMap<>();
List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
map.put("sysRoleEntityList",sysRoleEntityList);
return map;
}
/**
* Get permission information collection
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> Return results
*/
@RequestMapping("/getMenuInfoList")
@RequiresPermissions("sys:menu:info")
public Map<String,Object> getMenuInfoList(){
Map<String,Object> map = new HashMap<>();
List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
map.put("sysMenuEntityList",sysMenuEntityList);
return map;
}
/**
* Get all the data
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> Return results
*/
@RequestMapping("/getInfoAll")
@RequiresPermissions("sys:info:all")
public Map<String,Object> getInfoAll(){
Map<String,Object> map = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
map.put("sysUserEntityList",sysUserEntityList);
List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
map.put("sysRoleEntityList",sysRoleEntityList);
List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
map.put("sysMenuEntityList",sysMenuEntityList);
return map;
}
/**
* Add Administrator Role Permissions (test dynamic permission updates)
* @Author Sans
* @CreateTime 2019/6/19 10:39
* @Param username User ID
* @Return Map<String,Object> Return results
*/
@RequestMapping("/addMenu")
public Map<String,Object> addMenu(){
//Add Administrator Role Permissions
SysRoleMenuEntity sysRoleMenuEntity = new SysRoleMenuEntity();
sysRoleMenuEntity.setMenuId(4L);
sysRoleMenuEntity.setRoleId(1L);
sysRoleMenuService.save(sysRoleMenuEntity);
//Clear Cache
String username = "admin";
ShiroUtils.deleteCache(username,false);
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","Permissions added successfully");
return map;
}
}
Create UserLoginController Login Class
/**
* @Description User Login
* @Author Sans
* @CreateTime 2019/6/17 15:21
*/
@RestController
@RequestMapping("/userLogin")
public class UserLoginController {
@Autowired
private SysUserService sysUserService;
/**
* Sign in
* @Author Sans
* @CreateTime 2019/6/20 9:21
*/
@RequestMapping("/login")
public Map<String,Object> login(@RequestBody SysUserEntity sysUserEntity){
Map<String,Object> map = new HashMap<>();
//Authenticate
try{
//Authentication and login
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(sysUserEntity.getUsername(), sysUserEntity.getPassword());
//Verify successful login operation
subject.login(token);
}catch (IncorrectCredentialsException e) {
map.put("code",500);
map.put("msg","User does not exist or password error");
return map;
} catch (LockedAccountException e) {
map.put("code",500);
map.put("msg","Logon failed, the user was frozen");
return map;
} catch (AuthenticationException e) {
map.put("code",500);
map.put("msg","The user does not exist");
return map;
} catch (Exception e) {
map.put("code",500);
map.put("msg","Unknown exception");
return map;
}
map.put("code",0);
map.put("msg","Login Successful");
map.put("token",ShiroUtils.getSession().getId().toString());
return map;
}
/**
* Not logged in
* @Author Sans
* @CreateTime 2019/6/20 9:22
*/
@RequestMapping("/unauth")
public Map<String,Object> unauth(){
Map<String,Object> map = new HashMap<>();
map.put("code",500);
map.put("msg","Not logged in");
return map;
}
}
V. POSTMAN testing
After successful login, TOKEN will be returned, because it is a single sign-on, new TOKEN will be returned if you login again, and the TOKEN of Redis will be invalidated before
When we access the interface for the first time, we can see that the cache already has permission data. When we access the interface for the second time, Shiro will go directly to the cache to get permissions, and pay attention to setting the request header when accessing the interface.
ADMIN does not currently have sys:info:all, so it cannot access the getInfoAll interface. To clear the cache after dynamically assigning permissions, Shiro will re-execute the authorization method when accessing the interface, and then put the permissions and role data into the cache again
Access to add permission test interface, because it is a test, I write the ADMIN of the user with added permission to it, after permission is added, call the tool class to clear the cache, we can see that there is no cache in Redis
Access the getInfoAll interface again, because there is no data in the cache, and Shiro reauthorizes the query to block passage through
6. Project Source
https://gitee.com/liselotte/spring-boot-shiro-demo
https://github.com/xuyulong2017/my-java-demo
Recommended reading (click to skip reading)
1. SpringBoot Content Aggregation
2. Interview Question Content Aggregation
3. Design Mode Content Aggregation