springboot RBAC,jpa与 缓存机制浅析

RBAC模型实战

RBAC模型概述

RBAC模型(Role-Based Access Control:基于角色的访问控制)模型是20世纪90年代研究出来的一种新模型,但其实在20世纪70年代的多用户计算时期,这种思想就已经被提出来,直到20世纪90年代中后期,RBAC才在研究团体中得到一些重视,并先后提出了许多类型的RBAC模型。其中以美国George Mason大学信息安全技术实验室(LIST)提出的RBAC96模型最具有代表,并得到了普遍的公认。

RBAC认为权限授权的过程可以抽象地概括为:Who是否可以对What进行How的访问操作,并对这个逻辑表达式进行判断是否为True的求解过程,也即是将权限问题转换为What、How的问题,Who、What、How构成了访问权限三元组。

RBAC的组成

在RBAC模型里面,有3个基础组成部分,分别是:用户、角色和权限。RBAC通过定义角色的权限,并对用户授予某个角色从而来控制用户的权限,实现了用户和权限的逻辑分离(区别于ACL模型),极大地方便了权限的管理:

User(用户):每个用户都有唯一的UID识别,并被授予不同的角色
Role(角色):不同角色具有不同的权限
Permission(权限):访问权限
用户-角色映射:用户和角色之间的映射关系
角色-权限映射:角色和权限之间的映射
它们之间的关系如下图所示:

img

RBAC支持的安全原则

RBAC支持三个著名的安全原则:最小权限原则、责任分离原则和数据抽象原则

最小权限原则:RBAC可以将角色配置成其完成任务所需的最小权限集合
责任分离原则:可以通过调用相互独立互斥的角色来共同完成敏感的任务,例如要求一个计账员和财务管理员共同参与统一过账操作
数据抽象原则:可以通过权限的抽象来体现,例如财务操作用借款、存款等抽象权限,而不是使用典型的读、写、执行权限

RBAC的优缺点

(1)优点:

简化了用户和权限的关系
易扩展、易维护
(2)缺点:

RBAC模型没有提供操作顺序的控制机制,这一缺陷使得RBAC模型很难适应哪些对操作次序有严格要求的系统

JPA是什么?

JPA(Java Persistence API)和JDBC类似,也是官方定义的一组接口,但是它相比传统的JDBC,它是为了实现ORM而生的,即Object-Relationl Mapping,它的作用是在关系型数据库和对象之间形成一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了。
在之前,我们使用JDBC或是Mybatis来操作数据,通过直接编写对应的SQL语句来实现数据访问,但是我们发现实际上我们在Java中大部分操作数据库的情况都是读取数据并封装为一个实体类,因此,为什么不直接将实体类直接对应到一个数据库表呢?也就是说,一张表里面有什么属性,那么我们的对象就有什么属性,所有属性跟数据库里面的字段一一对应,而读取数据时,只需要读取一行的数据并封装为我们定义好的实体类既可以,而具体的SQL语句执行,完全可以交给框架根据我们定义的映射关系去生成,不再由我们去编写,因为这些SQL实际上都是千篇一律的。
而实现JPA规范的框架一般最常用的就是Hibernate,它是一个重量级框架,学习难度相比Mybatis也更高一些,而SpringDataJPA也是采用Hibernate框架作为底层实现,并对其加以封装。

使用JPA

引入依赖(domain模块)

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

配置yml文件(domain模块)

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

  #JPA配置
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    database: mysql

ddl-auto属性用于设置自动表定义,可以实现自动在数据库中为我们创建一个表,表的结构会根据我们定义的实体类决定,它有4种:

create 启动时删数据库中的表,然后创建,退出时不删除数据表
create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错
update 如果启动时表格式不一致则更新表,原有数据保留
validate 项目启动表结构进行校验 如果不一致则报错

编写实体类

@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;
}

实体关系映射

基本映射

对象端数据库端annotion可选annotion
ClassTable@Entity@Table(name=“tablename”)
propertycolumn@Column(name = “columnname”)
propertyprimary key@Id@GeneratedValue 详见ID生成策略(一般用GenerationType.IDENTITY)
propertyNONETransient

Dao

注意JpaRepository有两个泛型,前者是具体操作的对象实体,也就是对应的表,后者是ID的类型。

jpa本身提供了许多已经封装好的SQL方法,我们也可以自己编写方法,例如,jpa会自动将findUserByUserNameAndPassword(在书写方法名时会给予提示)拼接成SQL语句"select * from user_tab where name=? and password=?"

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

甚至可以使用@Query注解写出完整的SQL语句,nativeQuery = true表示开启原生SQL,示例如下:

@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

查询用户权限的方法:

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;
}
遇到的bug

懒加载: 使用privsDao.getById(privsId)出现对象未初始化完整错误

org.hibernate.LazyInitializationException: could not initialize proxy [com.wnhz.bms.domain.entity.jpa.Privs#1]
代理(proxy)产生的对象,没有初始化完全,只填充了id值,其它属性并没有完成。
System.out.println(privs)
findById(privsId) ---即时加载(与懒加载对应)

多模块开发注意事项

在启动类使用@EntityScan注解引入实体类的包

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

Springboot缓存

缓存介绍

Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。

其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方法上的,其核心思想是,当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存在缓存中。

每次调用需要缓存功能的方法时,Spring 会检查指定参数的指定目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

缓存使用步骤

第一步

开启基于注解的缓存,使用 @EnableCaching 标注在 springboot 主启动类上

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

第二步

标注缓存注解

@Cacheable

这里使用 @Cacheable 注解就可以将运行结果缓存,以后查询相同的数据,直接从缓存中取,不需要调用方法。

下面介绍一下 @Cacheable 这个注解常用的几个属性:

cacheNames/value :用来指定缓存组件的名字

key :缓存数据时使用的 key,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写)

keyGenerator :key 的生成器。 key 和 keyGenerator 二选一使用

cacheManager :可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。

condition :可以用来指定符合条件的情况下才缓存

unless :否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)

sync :是否使用异步模式。

但是使用 @Cacheable 注解也有很大的缺陷,那就是当数据库发生改动后,缓存里的数据得不到及时更新,这使得数据库的安全性和一致性得不到保障。

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

@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

一般使用在保存,更新方法中。
@CachePut也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。
@CachePut(“users”)//每次都会执行方法,并将结果存入指定的缓存中
@CachePut(cacheNames = "allusers")
    @Override
    public List<User> update() {
        userDao.deleteById(2L);
        return userDao.findAll();
    }
@CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation。

allEntries属性:
allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。
beforeInvocation属性:
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
@CacheEvict(cacheNames = "allusers", allEntries = true)
    @Override
    public void update(long id) {
        userDao.deleteById(id);
    }

参考链接:http://t.csdnimg.cn/CbTAf

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

http://t.csdnimg.cn/dWOmq

http://t.csdnimg.cn/zZ5Kl

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值