A brief analysis of springboot RBAC, jpa and caching mechanism

RBAC model in practice

RBAC model overview

The RBAC model (Role-Based Access Control) model is a new model developed in the 1990s, but in fact, this idea had already been proposed during the multi-user computing period in the 1970s. , It was not until the mid-to-late 1990s that RBAC received some attention in the research community, and many types of RBAC models were proposed. Among them, the RBAC96 model proposed by the Laboratory of Information Security Technology (LIST) of George Mason University in the United States is the most representative and has been generally recognized.

RBAC believes that the process of permission authorization can be abstractly summarized as: whether Who can perform How access operations on What, and the solution process of judging whether this logical expression is True, that is, converting the permission problem into What and How Questions, Who, What, and How constitute the access permission triplet.

Composition of RBAC

In the RBAC model, there are three basic components: users, roles and permissions. RBAC controls the user’s permissions by defining the permissions of the role and granting a certain role to the user, achieving the logical separation of users and permissions (different from the ACL model), which greatly facilitates the management of permissions:

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
The relationship between them is shown in the figure below:

img

Security principles supported by RBAC

RBAC supports three well-known security principles: the principle of least privilege, the principle of separation of responsibilities, and the principle of data abstraction

Principle of Least Privilege: RBAC can configure roles to have the minimum set of privileges required to complete their tasks.
Principle of separation of responsibilities: Sensitive tasks can be completed jointly by calling independent and mutually exclusive roles, such as requiring an accountant and financial administrator to jointly participate in unified posting operations.
Data abstraction principle: It can be reflected through the abstraction of permissions. For example, financial operations use abstract permissions such as borrowing and depositing, instead of using typical read, write, and execute permissions.

Advantages and Disadvantages of RBAC

(1) Advantages:

Simplified the relationship between users and permissions
Easy to expand and maintain
(2) Disadvantages:

The RBAC model does not provide a control mechanism for the sequence of operations. This flaw makes it difficult for the RBAC model to adapt to systems that have strict requirements for the sequence of operations.

What is JPA?

JPA (Java Persistence API) is similar to JDBC. It is also a set of officially defined interfaces. However, compared with traditional JDBC, it was born to implement ORM, that is, Object-Relationl Mapping. Its role is in relational databases and A mapping is formed between objects. In this way, when we specifically operate the database, we no longer need to deal with complex SQL statements. We only need to operate it as we usually operate objects.
In the past, we used JDBC or Mybatis to operate data and directly write corresponding SQL statements to achieve data access. However, we found that in fact, most of the situations when we operate the database in Java are to read the data and encapsulate it into an entity. class, so why not just map the entity class directly to a database table? In other words, what attributes are there in a table, then what attributes are there in our object? All attributes correspond to the fields in the database one-to-one. When reading data, you only need to read one row of data and encapsulate it for us to define A good entity class is fine, and the execution of specific SQL statements can be completely left to the framework to generate based on the mapping relationship we define, and it is no longer up to us to write, because these SQLs are actually the same.
The most commonly used framework to implement JPA specifications is Hibernate, which is a heavyweight framework and is more difficult to learn than Mybatis. SpringDataJPA also uses the Hibernate framework as the underlying implementation and encapsulates it.

Use JPA

Introduce dependencies (domain module)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Configure yml file (domain module)

spring:
  datasource:
    druid:
      url: jdbc:mysql://192.168.118.131:3306/user_db?useSSL=false &serverTimezone=Asia/Shanghai &allowPublicKeyRetrieval=true
      username: root
      password: 123
      driver-class-name: com.mysql.cj.jdbc.Driver

  #JPAConfiguration
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    database: mysql

The ddl-auto attribute is used to set automatic table definition, which can automatically create a table for us in the database. The structure of the table will be determined based on the entity class we define. There are 4 types:

create deletes the table in the database when it starts, then creates it, and does not delete the data table when it exits.
create-drop deletes the table in the database when starting, then creates it, and deletes the data table when exiting. If the table does not exist, an error will be reported.
update If the table format is inconsistent at startup, the table will be updated and the original data will be retained.
The validate project starts the table structure for verification and reports an error if it is inconsistent.

Writing entity classes

@Data
@Table(name = "user_tab")
@Entity
public class User {<!-- -->
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long userId;
    @Column(name = "user_name")
    private String userName;
    @Column(name = "password")
    private String password;
    @Column(name = "user_create_by")
    private String createBy;
    @Column(name = "user_create_time")
    private Date createTime;
    @Column(name = "user_update_time")
    private Date updateTime;
    @Column(name = "role_id")
    private Long roleId;
}

Entity relationship mapping

basic mapping

Object side Database side annotion Optional annotion
Class Table @Entity @Table(name=”tablename”)
property column @Column(name = “columnname”)
property primary key @Id @GeneratedValue For details, see ID generation strategy (generally use GenerationType.IDENTITY)
property NONE Transient

Dao

Note that JpaRepository has two generic types. The former is the object entity of the specific operation, that is, the corresponding table, and the latter is the type of ID.

JPA itself provides many encapsulated SQL methods. We can also write our own methods. For example, JPA will automatically splice findUserByUserNameAndPassword (which will give you a prompt when writing the method name) into a SQL statement “select * from user_tab where name=? and password=?”

@Repository
public interface IUserDao extends JpaRepository<User,Long> {<!-- -->
    User findUserByUserNameAndPassword(String name,String password);
}

You can even use the @Query annotation to write a complete SQL statement. nativeQuery = true means turning on native SQL. The example is as follows:

@Repository
public interface IRolePrivsDao extends JpaRepository<RolePrivs,Long> {<!-- -->
    @Query(value = "select * from rp_tab where role_id=?",nativeQuery = true)
    List<RolePrivs> findRpsByRoleId(Long roleId);
}

Service

How to query user permissions:

public List<Privs> findUserPrivs(String username, String password) {<!-- -->
    List<Privs> privss = new ArrayList<>();
    User user = userDao.findByUsernameAndPassword(username, password);
    Long roleId = user.getRoleId();
    List<RolePrivs> list = rpDao.findRpsbyRoleId(roleId);
    list.forEach(rp->{<!-- -->
        Long privsId = rp.getPrivsId();
        privss.add(privsDao.findById(privsId).get());
    });
    return privss;
}
Bugs encountered

Lazy loading: Object not initialized complete error occurs when using privsDao.getById(privsId)

org.hibernate.LazyInitializationException: could not initialize proxy [com.wnhz.bms.domain.entity.jpa.Privs#1]
The object generated by the proxy is not fully initialized. Only the id value is filled in, and other attributes are not completed.
System.out.println(privs)
findById(privsId) ---immediate loading (corresponding to lazy loading)

Notes on multi-module development

Use the @EntityScan annotation in the startup class to introduce the package of the entity class

@SpringBootApplication
@EntityScan(basePackages = "com.wnhz.bms.entity")
@EnableCaching
public class UserApp {<!-- -->
    public static void main(String[] args) {<!-- -->
        SpringApplication.run(UserApp.class);
    }
}

Springboot cache

Caching introduction

Spring has introduced support for Cache since 3.1. The org.springframework.cache.Cache and org.springframework.cache.CacheManager interfaces are defined to unify different caching technologies. And supports the use of JCache (JSR-107) annotations to simplify our development.

Its usage and principles are similar to Spring’s support for transaction management. Spring Cache works on methods. Its core idea is that when we call a cache method, the method parameters and return results will be stored in the cache as a key-value pair.

Each time a method that requires caching is called, Spring will check whether the specified target method of the specified parameter has been called. If so, it will directly obtain the result of the method call from the cache. If not, it will call the method and cache the result before returning it to user. The next call will be fetched directly from the cache.

Cache usage steps

First step

To enable annotation-based caching, use the @EnableCaching annotation on the springboot main startup class.

@SpringBootApplication
@EntityScan(basePackages = "com.wnhz.bms.entity")
@EnableCaching
public class UserApp {<!-- -->
    public static void main(String[] args) {<!-- -->
        SpringApplication.run(UserApp.class);
    }
}

Second step

Label cache annotations

@Cacheable

The @Cacheable annotation can be used here to cache the running results. If you query the same data in the future, you can get it directly from the cache without calling methods.

Here are some commonly used attributes of the @Cacheable annotation:

cacheNames/value: used to specify the name of the cache component

key: The key used when caching data, you can use it to specify. The default is to use the value of the method parameter. (You can write this key using spEL expressions)

keyGenerator: generator of key. Use key or keyGenerator alternatively

cacheManager: can be used to specify the cache manager. From which cache manager to obtain the cache.

condition: can be used to specify that the cache will only be cached if the conditions are met.

unless : Negates caching. When the condition specified by unless is true, the return value of the method will not be cached. Of course, you can also get the results for judgment. (Get the method result through #result)

sync: Whether to use asynchronous mode.

However, using the @Cacheable annotation also has a big flaw, that is, when the database is changed, the data in the cache cannot be updated in time, which makes the security and consistency of the database not guaranteed.

@Cacheable(cacheNames = "allusers")
    @Override
    public List<User> findAll() {<!-- -->
        return userDao.findAll();
    }
@CachePut

@CachePut can also declare a method to support caching functionality. The difference from @Cacheable is that the method annotated with @CachePut will not check whether there are previously executed results in the cache before execution. Instead, the method will be executed every time and the execution results will be stored in the form of key-value pairs. in the specified cache.

Generally used in save and update methods.
@CachePut can also be annotated on classes and methods. The attributes we can specify when using @CachePut are the same as @Cacheable.
@CachePut("users")//The method will be executed every time and the results will be stored in the specified cache
@CachePut(cacheNames = "allusers")
    @Override
    public List<User> update() {<!-- -->
        userDao.deleteById(2L);
        return userDao.findAll();
    }
@CacheEvict

@CacheEvict is used to annotate methods or classes that need to clear cache elements. When marked on a class, it means that the execution of all methods in it will trigger the cache clearing operation. The attributes that can be specified by @CacheEvict include value, key, condition, allEntries and beforeInvocation. The semantics of value, key and condition are similar to the corresponding attributes of @Cacheable. That is, value indicates which cache the clearing operation occurs on (corresponding to the name of the cache); key indicates which key needs to be cleared. If not specified, the key generated by the default policy will be used; condition indicates the conditions under which the clearing operation occurs. Let’s introduce the two new attributes allEntries and beforeInvocation.

allEntries attribute:
allEntries is a boolean type, indicating whether all elements in the cache need to be cleared. The default is false, which means it is not needed. When allEntries is specified as true, Spring Cache will ignore the specified key. Sometimes we need to clear all elements from Cache at once, which is more efficient than clearing elements one by one.
beforeInvocation attribute:
By default, the clearing operation is triggered after the corresponding method is successfully executed. That is, if the method fails to return successfully because it throws an exception, the clearing operation will not be triggered. You can use beforeInvocation to change the time when the clearing operation is triggered. When we specify the value of this property to be true, Spring will clear the specified element in the cache before calling this method.
@CacheEvict(cacheNames = "allusers", allEntries = true)
    @Override
    public void update(long id) {<!-- -->
        userDao.deleteById(id);
    }

Reference link: http://t.csdnimg.cn/CbTAf

http://t.csdnimg.cn/9QiIn

http://t.csdnimg.cn/dWOmq

http://t.csdnimg.cn/zZ5Kl