[SpringCloud microservice project practice-mall4cloud project (4)]–mall4cloud-rbac

mall4cloud-rbac role permission access control module

  • System architecture and module introduction
    • system structure
    • rbac model introduction
  • Related code
    • Permission verification
    • Interface code
  • Replenish

Code address
github address
Fork from github original project
gitee address
Fork from gitee original project

Introduction to system architecture and modules

System architecture


As can be seen from the figure, in the microservice cluster, the rbac module is used as a support module and is associated with the authentication and authorization account service module, but it is separated as a separate service in the code. Intermediate service calls are performed through fegin. For permission management systems, flexible and organized permission services are essential.

rbac model introduction

RBAC (Role-Based Access Control) is an access control model used to manage and control user access rights to resources in a system or application. RBAC is based on the concept of roles, assigning users to different roles, and each role has specific permissions that determine the operations the user can perform and the resources they can access. The main components of RBAC include:
User: Each user is identified by a unique UID and is granted different roles.
Role: Different roles have different permissions
Permission: access rights
User-role mapping: the mapping relationship between users and roles
Role-permission mapping: mapping between roles and permissions
It can be reflected by the following relationship

  1. Multi-user to multi-role relationship: Multiple users can be assigned to one or more roles. This allows different users to hold different positions or roles in an organization, and each role has different permissions.
  2. Multi-role to multi-permission relationship: Multiple roles can contain one or more permissions. This means that different roles can have different permissions, and the same permission can be assigned to multiple different roles.

    If the above relationship is reflected in database table design, at least five tables will be needed to design:

User Table: Used to store user information in the system. Each user has a unique identifier (user ID).
Role Table: Used to store information about different roles. Each role also has a unique identifier (role ID).
Permission Table: Used to store information about various permissions in the system. Each permission also has a unique identifier (permission ID).
User-Role Relationship Table: used to establish a many-to-many relationship between users and roles to determine which users belong to which roles.
Role-Permission Relationship Table: Used to establish a many-to-many relationship between roles and permissions to determine which roles have the authority to perform which operations.

If you want to add a menu table to the RBAC model to manage system menus and their associations with roles, you can add the menu table and role-menu association table to the database model.
In the system data table, they correspond to user (user table), role (role table), menu (menu table), menu_permission (menu permission table, here the menu and permission are combined), user_role (User role association table), role_menu (role menu table)

Related code

In the module, the verification of permissions is mainly related to the code in the filter of the auth module, and the other is the interface code provided to the front end.

Permission verification

The code in the filter under the auth module is as follows:


①Parameters: User information, uri, request method are passed in
②③: Verification is mainly performed on ordinary users. Others are not verified. This is equivalent to only giving an example. You have to add it yourself if necessary. The next step is to call the remote interface of the rbac module.


①: Query the set of permission identifiers owned by the user. Here is the related query of three tables.

②: Obtain permission objects based on user type
③④: The judgment logic is that if the requested uri + method belongs to the permission object under the current user type, determine whether the permission id set under this user contains the id of the permission object. If included, the verification is complete.

There is a question here. Interacting with the database for every request will affect performance. If you want to avoid this impact, you can use cache to store verified permission results, which can reduce the performance overhead of frequent verification

There is this step in the code:

Method-level caching is done using @Cacheable, and cache is cleaned using @CacheEvict (after each login)

@Cacheable is an annotation provided by the Spring framework to enable method-level caching. It can be used to cache the calculation results of a method so that the next time the same method is called, the cached results can be returned directly without having to recalculate
When using it, you need to configure the cache manager. Spring supports multiple cache managers, such as EhCache, Caffeine, Redis, etc.

The code location of the cache manager is as follows

The specific code is as follows:

package com.mall4j.cloud.common.cache.config;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.mall4j.cloud.common.cache.adapter.CacheTtlAdapter;
import com.mall4j.cloud.common.cache.bo.CacheNameWithTtlBO;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * @author FrozenWatermelon
 * @date 2020/7/4
 */
@EnableCaching
@Configuration
public class RedisCacheConfig {<!-- -->

@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, CacheTtlAdapter cacheTtlAdapter) {<!-- -->

//Create RedisCacheManager instance
RedisCacheManager redisCacheManager = new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
//Default strategy, unconfigured keys will use this
this.getRedisCacheConfigurationWithTtl(3600),
//Specify key strategy
this.getRedisCacheConfigurationMap(cacheTtlAdapter));

redisCacheManager.setTransactionAware(true);
return redisCacheManager;
}

// Get the mapping of the cache strategy
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap(CacheTtlAdapter cacheTtlAdapter) {<!-- -->
if (cacheTtlAdapter == null) {<!-- -->
return Collections.emptyMap();
}
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(16);
// Configure the timeout of each cache according to CacheTtlAdapter
for (CacheNameWithTtlBO cacheNameWithTtlBO : cacheTtlAdapter.listCacheNameWithTtl()) {<!-- -->
redisCacheConfigurationMap.put(cacheNameWithTtlBO.getCacheName(),
getRedisCacheConfigurationWithTtl(cacheNameWithTtlBO.getTtl()));
}
return redisCacheConfigurationMap;
}

// Get the Redis cache policy (including timeout)
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {<!-- -->
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer()))
.entryTtl(Duration.ofSeconds(seconds));

return redisCacheConfiguration;
}

/**
* Customize the redis serialization mechanism and redefine an ObjectMapper to prevent conflicts with MVC
* https://juejin.im/post/5e869d426fb9a03c6148c97e
*/
@Bean
public RedisSerializer<Object> redisSerializer() {<!-- -->
ObjectMapper objectMapper = new ObjectMapper();
// No exception will be thrown when unmatched attributes are encountered during deserialization
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//Do not throw an exception when encountering an empty object during serialization
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// If it is an invalid subtype during deserialization, no exception will be thrown.
objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
// Do not use the default dateTime for serialization,
objectMapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
// Use the serialization class provided by JSR310, which contains a large number of JDK8 time serialization classes
objectMapper.registerModule(new JavaTimeModule());
// Enable the type information required for deserialization and add @class to the attribute
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
// Configure the serializer for null values
GenericJackson2JsonRedisSerializer.registerNullValueSerializer(objectMapper, null);
return new GenericJackson2JsonRedisSerializer(objectMapper);
}

@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory,
RedisSerializer<Object> redisSerializer) {<!-- -->
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setDefaultSerializer(redisSerializer);
template.setValueSerializer(redisSerializer);
template.setHashValueSerializer(redisSerializer);
template.setKeySerializer(StringRedisSerializer.UTF_8);
template.setHashKeySerializer(StringRedisSerializer.UTF_8);
template.afterPropertiesSet();
return template;
}

@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {<!-- -->
return new StringRedisTemplate(redisConnectionFactory);
}

@Bean
@ConditionalOnMissingBean
public CacheTtlAdapter cacheTtl() {<!-- -->
return Collections::emptyList;
}

}


Create a configuration class for configuring the Redis cache manager. You can use the @Configuration annotation and @EnableCaching annotation to enable caching and configure RedisCacheManager. RedisCacheManager is a custom caching strategy.

this.getRedisCacheConfigurationMap(cacheTtlAdapter) is used to specify the cache strategy for a specific key. This is a way to use different strategies based on different keys, and may customize the cache timeout based on different business needs. The cacheTtlAdapter parameter is an adapter for generating strategies for specific keys.

The @Bean method cacheManager creates an instance of the Redis cache manager RedisCacheManager. This manager is used to manage the cache, configure cache policies, and connect to the Redis database. In this approach, the following key configurations are used:

RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory): Creates a RedisCacheWriter for interacting with Redis, using non-blocking writing. This is the default and commonly used way of writing

getRedisCacheConfigurationWithTtl(3600): Sets the default cache policy, including a cache timeout of 3600 seconds.

getRedisCacheConfigurationMap(cacheTtlAdapter): Specifies the cache strategy for specific keys. These strategies are dynamically configured according to different keys and timeouts, and are obtained through cacheTtlAdapter.

setTransactionAware(true): Set the cache manager to be transaction-aware to ensure that cache operations within a transaction can be rolled back.

The getRedisCacheConfigurationMap method dynamically generates a cache strategy mapping based on cacheTtlAdapter. This method checks whether cacheTtlAdapter is null, and if not, obtains a specific set of cache keys and corresponding timeouts, and creates the corresponding cache policy. If cacheTtlAdapter is null, an empty map is returned.

The getRedisCacheConfigurationWithTtl method is used to configure the Redis cache strategy, including serialization strategy and cache timeout.

The redisSerializer method creates a custom Redis data serializer, which contains some custom configurations, such as not throwing exceptions when the serializer fails, handling empty objects, etc.

The redisTemplate and stringRedisTemplate methods configure RedisTemplate and StringRedisTemplate respectively for operating the Redis database. They use custom serializers and connection factories.

The final cacheTtl method is used to create a cache timeout adapter CacheTtlAdapter and returns an empty list by default.

Interface code

The interface code can mainly be seen from the controller. They are all related to the business requirements of some data tables. I will not go into details here.

Supplement

Some extensions about rbac

RBAC1 model
The concept of role inheritance is introduced based on RBAC. That is: the child role can inherit all the permissions of the parent role.
Usage scenario: For example, a business department has managers, supervisors, and specialists. The supervisor’s authority cannot be greater than that of the manager, and the specialist’s authority cannot be greater than that of the supervisor. If the RBAC0 model is used as a authority system, errors in allocating authority will most likely occur, and eventually the supervisor will have authority that even the manager does not have.
The RBAC1 model solves this problem very well. After the manager role is created and the permissions are configured, the permissions of the supervisor role inherit the permissions of the manager role, and it supports deleting supervisor permissions from the manager permissions.

About permission management system
RuoYi permission management system is also a project suitable for learning. Code address: https://gitee.com/y_project/RuoYi-Cloud
RuoYi-Cloud