RBAC权限模型

概述

在企业系统中,通过配置用户的功能权限可以实现让不同的人分管不同业务的需求。RBAC(Role Based Access Control)模型,中文是基于角色的访问控制,主要是将一组功能组合成一个角色,再将角色分配给用户,也就是说角色是功能的合集

比如:

企业A总共有12个功能,包括财务管理、人事管理、销售管理等等,如果不引入 RBAC 模型,咱们就需要每创建一个用户就要分配一次功能,假设企业A有100个用户,这将至少需要进行100次操作。如果用户数量增加到1000甚至10000,并且一个用户可能会拥有多个功能,操作将会变得非常繁琐。如图:

后台认证授权1

经过多次操作后发现,有些人被分配了相同的功能。例如,A、B等10个用户都被分配了客户管理、订单管理和供应商管理这几个模块。**将这几个功能模块组合成一个包,然后将整个包分配给需要的用户,这个包被称为角色**。由于角色和功能之间的对应关系相对稳定,在给用户分配权限时只需分配角色即可,如下图所示:

后台认证授权2

基于RBAC授权后,咱们可以达成以下2个目标:

  • 解耦用户和功能,降低操作错误率
  • 降低功能权限分配的繁琐程度

后台认证授权3

ER图与关系梳理

在一个核心业务系统中,通常通过业务分析,从而抽离出数据库表,表确定之后会进一步分析表中应该有的字段,下面先看一份业务ER图:

1280X1280

上图中清楚的描述用户、角色、资源、职位、部门之间的关系,同时咱们进一步推导出以下结果:

  • 用户与职位是N:N关系
  • 用户与部门是N:1关系
  • 用户与角色是N:N关系
  • 角色与资源是N:N关系
  • 部门与角色是N:N关系

表关系(简图)

后台认证授权4

Spring Security

概述

Spring Security是一个基于Spring框架安全解决方案,它为应用程序提供了完整的安全管理功能,包括认证、授权、攻击防范和会话管理等

官网地址:Spring Security

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

在若依系统中,已经集成了Spring Security安全框架

核心作用

用一张图来说明一下spring Security的核心作用

后台认证授权5

  • 登录请求
    • 校验验证码是否正确
    • 通过spring security框架验证用户是否存在,密码是否正确
    • 查询用户所拥有的权限列表
    • 最后把用户信息封装到jwt生成的token中返回
  • 其他请求(可以是非登录的任意请求),这些校验工作可以全部交给Spring Security来完成
    • url是否需要检验(访问接口是否需要放行,比如登录、注册、验证码这些接口;还有一些静态资源也需要放行,比如html、js、css等)
    • token校验,是否为空,是否过期,是否正常解析
    • 校验当前访问的url与当前用户的权限是否匹配,匹配则放行,不匹配则是权限不足

权限控制

认证

认证(Authentication)

认证是用户登录系统时,通过比较用户提供的凭证(如用户名和密码)与系统中存储的信息是否一致,以验证用户身份是否有效的过程

当前系统的认证流程:

后台认证授权5

代码入口:zzyl-admin模块下找到:com.zzyl.web.controller.system.SysLoginController.login()

授权

授权(Authorization):

  • 授权就是用户登录后(认证后),控制用户是否有权限访问某些资源。
  • 下图是给用户赋予角色,并且可以可以赋予多个角色

6262a4ac-5266-4a53-88ff-02cbee2df778

下图中,可以给角色赋予多个功能权限

f7bc2eed-722c-4ba1-9f19-e1dfa3f84fe3

下图中,可以对菜单和按钮进行权限标识的配置

5b98c5f1-990d-4a71-a9bb-20e08640e02a

权限验证

一个请求过来之后,需要有诸多的校验来能判断是否能有效访问

whiteboard_exported_image (25)
  • 请求来了以后,如果是doc.htmllogin,是一个静态资源,可以直接放行,在SecurityConfig可以配置放行接口
  • 请求来了以后,如果是/nursing/elder/save,在SecurityConfig没有配置放行,需要校验当前请求携带的token是否有效,如果token无效,则是认证失败,需要重新认证(跳转到登录页)
  • 请求来了以后,如果是/nursing/elder/save,在SecurityConfig没有配置放行,token也有效,则判断当前登录人是否拥有该权限(认证成功后,可以获取用户的权限列表)
    • 如果有权限,则放行
    • 如果没权限,则提醒权限不足

核心代码位置:在zzyl-framework模块中找到类:com.zzyl.framework.config.SecurityConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* spring security配置
*
* @author ruoyi
*/
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig
{
/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;

/**
* 认证失败处理类
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;

/**
* 退出处理类
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;

/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;

/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;

/**
* 允许匿名访问的地址
*/
@Autowired
private PermitAllUrlProperties permitAllUrl;

/**
* 身份验证实现
*/
@Bean
public AuthenticationManager authenticationManager()
{
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return new ProviderManager(daoAuthenticationProvider);
}

/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
{
return httpSecurity
// CSRF禁用,因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token,所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
.build();
}

/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
}

具体的 url 校验是通过 Spring Security 的提供的权限注解@PreAuthorize实现的,可以判断具体的url是否有权限访问该接口

  • @PreAuthorize 是 Spring Security 框架中提供的一个安全注解,用于实现基于注解的访问控制。它允许开发者在方法级别上声明特定的安全约束,以确保只有满足指定条件的用户才能调用该方法
  • 当 @PreAuthorize 注解被应用于某个方法时,Spring Security 在该方法执行前会先对当前认证的用户进行权限检查。如果检查通过,方法调用得以继续;否则,框架会抛出相应的权限异常(如 AccessDeniedException),阻止方法执行
  • 若依框架生成的Controller中都可以见到权限控制的代码,如下:
1
2
3
4
5
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user) {

}
  • @PreAuthorize Spring Security 框架的权限注解,在执行方法前执行
    • 已经开启注解生效,在SecurityConfig类中添加了如下注解:
    • @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
  • @ss.hasPermi(‘system:user:list’)
    • 其中的ss是一个被spring管理的bean
      • 位置:zzyl-framework模块中的com.zzyl.framework.web.service.PermissionService
    • hasPermi 是PermissionService类中的一个方法,判断是否拥有该权限
    • system:user:list 为方法的参数
  • 权限控制的流程

后台认证授权7