当前位置: 首页 > news >正文

若依RBAC权限控制SpringSecurity(自用)

SpringSecurity介绍

(这里是结合若依仅做了SpringSecurity核心配置的简单介绍,如果想要系统性掌握SpringSecurity请移步更专业的文章或官方文档)

Spring Security是一个功能强大的Java安全框架,它提供了全面的安全认证和授权的支持。

与RBAC模型结合使用时,Spring Security能够实现灵活的权限控制。

我们来看下它的二大核心概念,认证和授权。让我们用一个简单的例子来领会它们的意义。

1)认证(Authentication)想象一下,小智同学去图书馆借书。门口的图书管理员会要求小智出示借书证。这个借书证上有小智的照片和姓名,管理员通过它来确认小智的身份。这个过程,就是一个认证的实例。在Spring Security的世界里,认证就像用户登录时提交的用户名和密码,系统通过这些信息来验证“你是谁”。

Spring Security不仅支持传统的用户名和密码认证,还支持OAuth2、JWT等现代认证方式。

2)授权(Authorization) 现在,假设小智同学已经踏入了图书馆的大门,但当小智想进入只有持特定权限才能进入的参考书籍区时,管理员会再次检查小智的借书证,看是否有相应的权限标记。如果有,那么小智就能进入这个区域;如果没有,那么小智只能遗憾地止步。这就是授权的精髓。在Spring Security中,授权是确认用户在通过认证之后,是否有权限执行某些操作或访问特定资源。

SpringSecurity配置

Spring Security的配置类是实现安全控制的核心部分

开启Spring Security各种功能,以确保Web应用程序的安全性,包括认证、授权、会话管理、过滤器添加等。

package com.dkd.framework.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;
import com.dkd.framework.config.properties.PermitAllUrlProperties;
import com.dkd.framework.security.filter.JwtAuthenticationTokenFilter;
import com.dkd.framework.security.handle.AuthenticationEntryPointImpl;
import com.dkd.framework.security.handle.LogoutSuccessHandlerImpl;/*** spring security配置** @author ruoyi*/
// 开启方法级别的权限控制 ==> @PreAuthorize
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{/*** 自定义用户认证逻辑*/@Autowiredprivate UserDetailsService userDetailsService;/*** 认证失败处理类*/@Autowiredprivate AuthenticationEntryPointImpl unauthorizedHandler;/*** 退出处理类*/@Autowiredprivate LogoutSuccessHandlerImpl logoutSuccessHandler;/*** token认证过滤器*/@Autowiredprivate JwtAuthenticationTokenFilter authenticationTokenFilter;/*** 跨域过滤器*/@Autowiredprivate CorsFilter corsFilter;/*** 允许匿名访问的地址*/@Autowiredprivate PermitAllUrlProperties permitAllUrl;/*** 解决 无法直接注入 AuthenticationManager** @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception{return super.authenticationManagerBean();}/*** anyRequest          |   匹配所有请求路径* access              |   SpringEl表达式结果为true时可以访问* anonymous           |   匿名可以访问* denyAll             |   用户不能访问* fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)* hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问* hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问* hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问* hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问* hasRole             |   如果有参数,参数表示角色,则其角色可以访问* permitAll           |   用户可以任意访问* rememberMe          |   允许通过remember-me登录的用户访问* authenticated       |   用户登录后可访问*/@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception{// 配置URL访问授权规则ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();// 遍历无需认证即可访问的URL列表,设置这些URL对所有用户可访问permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());// 配置Web应用程序规则httpSecurity// CSRF(跨站请求伪造)禁用,因为不使用session.csrf().disable()// 禁用HTTP响应标头.headers().cacheControl().disable().and()// 认证失败处理类.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 对于登录login 注册register 验证码captchaImage 允许匿名访问.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().and().headers().frameOptions().disable();// 添加Logout filterhttpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);// 添加JWT filterhttpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 添加CORS filterhttpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);}/*** 强散列哈希加密实现*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}/*** 身份认证接口*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());}
}

用户登录流程

管理员在登录页面,输入用户名和密码以及验证码后,点击登录按钮,向后端发送请求,后端通过springSecurity认证管理器进行登录校验

此功能涉及前端相关的代码位于views/login.vue这个视图组件中,负责实现用户登录界面和交互逻辑。

后端处理逻辑则在dkd-admin模块的SysLoginController中,负责接收前端的请求,处理登录逻辑,并返回token令牌

前端

  1. 点击login.vue中的登录按钮

  2. 调用login.vue中的handleLogin方法

  3. 调用store/mondles/user.js中的login方法,将返回结果存入useUserStore对象中(用于管理用户相关的状态和操作)

  4. 调用api/login.js中的login方法

  5. 调用utils/request.js中的service实例基于axios发送ajax请求(.env.development文件设置了统一请求路径前缀)

后端

SysLoginController

ruoyi-admin模块中com.ruoyi.web.controller.system.SysLoginController类的login方法接收前端登录请求

SysLoginService

ruoyi-framework模块中com.ruoyi.framework.web.service.SysLoginService类的login方法处理登录逻辑

  1. 验证码校验

  2. 登录前置校验

  3. SS认证管理器用户校验,调用执行UserDetailsServiceImpl.loadUserByUsername

    1. 认证通过后,创建登录用户对象LoginUser包括用户ID、部门ID、用户信息和用户权限信息

  4. 登录成功,记录日志

  5. 修改用户表更新登录信息

  6. 生成token

具体的执行的流程如下图:

获取用户角色和权限

超级管理员登录帝可得系统后,可以查看区域管理所有的权限按钮,实现增删改查操作

而财务人员登录帝可得系统后,仅可以查看区域列表,其他权限没有

1)首先创建一个财务人员的角色,权限字符串 Accountant,仅设置区域管理查询权限

2)再创建一个用户,昵称为黑马彭于晏,用户名hmpyy,密码admin123 角色为财务人员

前端

  1. 在全局permission.js中的router.beforeEach方法用于在用户导航到不同路由之前进行一些预处理

  2. 调用store/mondles/user.js中的getInfo方法,将返回结果存入useUserStore对象中(用于管理用户相关的状态和操作)

  3. 调用api/login.js中的getInfo方法

后端

SysLoginController

ruoyi-admin模块中com.ruoyi.web.controller.system.SysLoginController类的getInfo方法接收前端获取用户信息请求

SysPermissionService

ruoyi-framework模块中com.ruoyi.framework.web.service.SysPermissionService

  1. getRolePermission查询该用户角色集合

  2. getMenuPermission查询该用户权限(菜单)集合

具体的执行的流程如下图:

页面权限

前端封装了一个指令权限,能简单快速的实现按钮级别的权限判断。

使用权限字符串 v-hasPermi@/directive/permission/hasPermi.js

// 单个
<el-button v-hasPermi="['system:user:add']">存在权限字符串才能看到</el-button>
// 多个
<el-button v-hasPermi="['system:user:add', 'system:user:edit']">包含权限字符串才能看到</el-button>

使用角色字符串 v-hasRole@/directive/permission/hasRole.js

// 单个
<el-button v-hasRole="['admin']">管理员才能看到</el-button>
// 多个
<el-button v-hasRole="['role1', 'role2']">包含角色才能看到</el-button>

获取动态菜单路由

在上个小节我们创建了“黑马彭于晏”这个用户,并为他分配了特定的角色权限。这意味着,当他登录帝可得系统时,看到的侧边栏菜单将根据他的角色权限而有所不同。而超级管理员是可以查看所有菜单的

实现此功能的前端代码位于src/permission.js文件。它在登录成功后,会在跳转到新路由之前,去查询当前用户有权访问的动态菜单的路由列表。

后端处理逻辑则在dkd-admin模块的SysLoginController中,它负责接收前端发来的请求,处理查询,并构建起一个完整的菜单树结构,然后返回给前端。

前端

  1. 在全局permission.js中的router.beforeEach方法用于在用户导航到不同路由之前进行一些预处理

  2. 调用store/mondles/permission.js中的generateRoutes方法,将返回结果存入usePermissionStore对象中

  3. 调用api/menu.js中的getRouters方法

后端

SysLoginController

ruoyi-admin模块中com.ruoyi.web.controller.system.SysLoginController类的getRouters方法接收前端获取路由信息请求

ISysMenuService

ruoyi-system模块中com.ruoyi.web.system.service.ISysMenuService

  1. selectMenuTreeByUserId根据用户ID查询菜单树信息(递归生成父子菜单)

  2. buildMenus构建前端路由所需要的菜单路由格式RouterVo

具体的执行的流程如下图:

路由菜单加载

  1. 用户登录成功后,通过路由router/index.js跳转到首页并加载layout布局组件

  2. layout/index.vue中加载sidbar侧边栏

  3. layout/components/Sidebar/index.vue中遍历动态路由菜单在页面显示

  4. 用户点击菜单后会根据路由的path跳转到对应的视图组件在<app-main />显示

权限注解

源码分析

在若依框架中,权限的验证最核心的是使用的Spring Security的提供的权限注解@PreAuthorize

  • @PreAuthorize 是 Spring Security 框架中提供的一个安全注解,用于实现基于注解的访问控制。它允许开发者在方法级别上声明特定的安全约束,以确保只有满足指定条件的用户才能调用该方法

  • 当 @PreAuthorize 注解被应用于某个方法时,Spring Security 在该方法执行前会先对当前认证的用户进行权限检查。如果检查通过,方法调用得以继续;否则,框架会抛出相应的权限异常(如 AccessDeniedException),阻止方法执行。

若依框架中的权限控制代码,如下:

@PreAuthorize("@ss.hasPermi('manage:order:list')")
public TableDataInfo list(Order order)
{startPage();List<Order> list = orderService.selectOrderList(order);return getDataTable(list);
}
  • @PreAuthorize是Spring Security框架的权限注解,在执行方法前执行

  • @ss.hasPermi('manage:order:list')

    • 其中的ss是指的一个spring管理的bean

      • 位置:ruoyi-framework模块中的com.ruoyi.framework.web.service.PermissionService

    • hasPermi 是PermissionService类中的一个方法,判断是否拥有该权限

    • manage:order:list为方法的参数

注意:在SecurityConfig类中添加@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)@PreAuthorize才能生效

权限控制流程:

权限方法

@PreAuthorize注解用于配置接口要求用户拥有某些权限才可访问,它拥有如下方法

方法参数描述
hasPermiString验证用户是否具备某权限
lacksPermiString验证用户是否不具备某权限,与 hasPermi逻辑相反
hasAnyPermiString验证用户是否具有以下任意一个权限
hasRoleString判断用户是否拥有某个角色
lacksRoleString验证用户是否不具备某角色,与 hasRole逻辑相反
hasAnyRolesString验证用户是否具有以下任意一个角色,多个逗号分隔

使用示例

1)数据权限示例。

// 符合system:user:list权限要求
@PreAuthorize("@ss.hasPermi('system:user:list')")// 不符合system:user:list权限要求
@PreAuthorize("@ss.lacksPermi('system:user:list')")// 符合system:user:add或system:user:edit权限要求即可
@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')")

编程式判断是否有资源权限 :

if (SecurityUtils.hasPermi("sys:user:edit"))
{System.out.println("当前用户有编辑用户权限");
}

2)角色权限示例。

// 属于user角色
@PreAuthorize("@ss.hasRole('user')")// 不属于user角色
@PreAuthorize("@ss.lacksRole('user')")// 属于user或者admin之一
@PreAuthorize("@ss.hasAnyRoles('user,admin')")

编程式判断是否有角色权限 :

if (SecurityUtils.hasRole("admin"))
{System.out.println("当前用户有admin角色权限");
}

权限提示:超级管理员拥有所有权限,不受权限约束。

公开接口

如果有些接口是不需要验证权限可以公开访问的,这个时候就需要我们给接口放行。

使用注解方式,只需要在Controller的类或方法上加入@Anonymous该注解即可

// @PreAuthorize("@ss.xxxx('....')") 注释或删除掉原有的权限注解
@Anonymous
@GetMapping("/list")
public List<SysXxxx> list(SysXxxx xxxx)
{return xxxxList;
}

相关文章:

  • WPF GDI 画 晶圆Mapping图
  • CSS 美化页面(三)
  • MegaTTS3: 下一代高效语音合成技术,重塑AI语音的自然与个性化
  • 浏览器运行Pytorch无法启用显卡
  • poll为什么使用poll_list链表结构而不是数组 - 深入内核源码分析
  • Java文件批量复制工具实现解析
  • 【npm install 一直转圈的问题】
  • 力扣HOT100——560.和为k的子数组
  • Kaggle竞赛——商店销售时序预测(Store Sales)
  • ROS---<angles>
  • 分布式锁+秒杀异步优化
  • 从零开始:Python运行环境之VSCode与Anaconda安装配置全攻略 (1)
  • 4.vtk光照vtkLight
  • 使用Python爬取豆瓣电影Top250并保存到Excel完整教程
  • 测试基础笔记第四天(html)
  • 交换机与路由器的默契配合:它们的联系与区别
  • Zookeeper 命令返回数据的含义
  • 【软考-架构】13.4、质量属性-架构评估
  • 【HDFS入门】HDFS核心组件Failover Controller:高可用保障机制解析
  • K8s 生产落地
  • 狄威已任国铁集团副总经理
  • 从孔雀尾巴到蒙娜丽莎,一个鸟类学博士眼中的“美”
  • “光荣之城”2025上海红色文化季启动,红色主题市集亮相
  • 交通运输部:预计今年五一假期全社会跨区域人员流动量将再创新高
  • 国务院安委会对辽宁辽阳一饭店重大火灾事故查处挂牌督办
  • 费高云调研党的建设工作:营造风清气正劲足的政治生态