Ruoyi-vue-plus-5.x第一篇Sa-Token权限认证体系深度解析:1.3 权限控制与注解使用
👋 大家好,我是 阿问学长
!专注于分享优质开源项目
解析、毕业设计项目指导
支持、幼小初高
的教辅资料
推荐等,欢迎关注交流!🚀
权限控制与注解使用
前言
在前面的文章中,我们了解了Sa-Token的基础概念和登录认证机制。本文将深入探讨Sa-Token的权限控制体系,重点介绍各种权限注解的使用方法、复合权限表达式的应用,以及在RuoYi-Vue-Plus中的实际应用场景。
Sa-Token权限注解体系
1. @SaCheckLogin - 登录校验注解
@SaCheckLogin
是最基础的权限注解,用于校验用户是否已登录。
@RestController
@RequestMapping("/user")
public class UserController {/*** 获取用户信息 - 需要登录*/@SaCheckLogin@GetMapping("/info")public R<UserInfoVo> getUserInfo() {Object loginId = StpUtil.getLoginId();SysUser user = userService.selectUserById(Long.valueOf(loginId.toString()));return R.ok(BeanUtil.toBean(user, UserInfoVo.class));}/*** 修改个人信息 - 需要登录*/@SaCheckLogin@PutMapping("/profile")public R<Void> updateProfile(@RequestBody UserProfileDto profileDto) {Object loginId = StpUtil.getLoginId();return toAjax(userService.updateUserProfile(Long.valueOf(loginId.toString()), profileDto));}
}
2. @SaCheckRole - 角色校验注解
@SaCheckRole
用于校验用户是否拥有指定角色。
@RestController
@RequestMapping("/admin")
public class AdminController {/*** 管理员专用接口 - 需要admin角色*/@SaCheckRole("admin")@GetMapping("/dashboard")public R<DashboardVo> getDashboard() {return R.ok(adminService.getDashboardData());}/*** 系统配置 - 需要admin或system角色*/@SaCheckRole(value = {"admin", "system"}, mode = SaMode.OR)@GetMapping("/config")public R<SystemConfigVo> getSystemConfig() {return R.ok(configService.getSystemConfig());}/*** 高级设置 - 需要同时拥有admin和security角色*/@SaCheckRole(value = {"admin", "security"}, mode = SaMode.AND)@PostMapping("/security/settings")public R<Void> updateSecuritySettings(@RequestBody SecuritySettingsDto settings) {return toAjax(securityService.updateSettings(settings));}
}
3. @SaCheckPermission - 权限校验注解
@SaCheckPermission
是最常用的权限注解,用于校验用户是否拥有指定权限。
@RestController
@RequestMapping("/system/user")
public class SysUserController {/*** 查询用户列表*/@SaCheckPermission("system:user:list")@GetMapping("/list")public TableDataInfo<SysUserVo> list(SysUserBo user, PageQuery pageQuery) {return userService.selectPageUserList(user, pageQuery);}/*** 新增用户*/@SaCheckPermission("system:user:add")@PostMappingpublic R<Void> add(@Validated @RequestBody SysUserBo user) {// 检查用户名是否重复if (!userService.checkUserNameUnique(user)) {return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");}return toAjax(userService.insertUser(user));}/*** 修改用户*/@SaCheckPermission("system:user:edit")@PutMappingpublic R<Void> edit(@Validated @RequestBody SysUserBo user) {userService.checkUserAllowed(user.getUserId());if (!userService.checkUserNameUnique(user)) {return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");}return toAjax(userService.updateUser(user));}/*** 删除用户*/@SaCheckPermission("system:user:remove")@DeleteMapping("/{userIds}")public R<Void> remove(@PathVariable Long[] userIds) {if (ArrayUtil.contains(userIds, getUserId())) {return R.fail("当前用户不能删除");}return toAjax(userService.deleteUserByIds(userIds));}/*** 重置密码*/@SaCheckPermission("system:user:resetPwd")@PutMapping("/resetPwd")public R<Void> resetPwd(@RequestBody SysUserBo user) {userService.checkUserAllowed(user.getUserId());user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));return toAjax(userService.resetUserPwd(user.getUserId(), user.getPassword()));}/*** 状态修改*/@SaCheckPermission("system:user:edit")@PutMapping("/changeStatus")public R<Void> changeStatus(@RequestBody SysUserBo user) {userService.checkUserAllowed(user.getUserId());return toAjax(userService.updateUserStatus(user.getUserId(), user.getStatus()));}
}
4. @SaCheckSafe - 二级认证注解
@SaCheckSafe
用于需要二级认证的敏感操作。
@RestController
@RequestMapping("/system/security")
public class SecurityController {/*** 修改系统关键配置 - 需要二级认证*/@SaCheckSafe@PostMapping("/critical/config")public R<Void> updateCriticalConfig(@RequestBody CriticalConfigDto config) {return toAjax(securityService.updateCriticalConfig(config));}/*** 删除重要数据 - 需要二级认证*/@SaCheckSafe(value = "delete-data", timeout = 300)@DeleteMapping("/important/data/{id}")public R<Void> deleteImportantData(@PathVariable Long id) {return toAjax(dataService.deleteImportantData(id));}/*** 进行二级认证*/@PostMapping("/safe/check")public R<Void> safeCheck(@RequestBody SafeCheckDto safeCheckDto) {// 验证密码或其他认证方式if (validateSafeCheck(safeCheckDto)) {// 通过二级认证StpUtil.openSafe(safeCheckDto.getService(), safeCheckDto.getTimeout());return R.ok();}return R.fail("二级认证失败");}
}
5. @SaCheckBasic - HTTP Basic认证
@SaCheckBasic
用于HTTP Basic认证场景。
@RestController
@RequestMapping("/api/external")
public class ExternalApiController {/*** 外部API接口 - 使用HTTP Basic认证*/@SaCheckBasic(realm = "External API", account = "api:123456")@PostMapping("/data/sync")public R<Void> syncData(@RequestBody DataSyncDto data) {return toAjax(externalService.syncData(data));}/*** 动态Basic认证*/@SaCheckBasic(realm = "Dynamic API")@PostMapping("/dynamic/api")public R<Void> dynamicApi(@RequestBody ApiRequestDto request) {// 在StpInterface中实现动态账号密码验证return toAjax(apiService.processRequest(request));}
}
6. @SaIgnore - 忽略校验注解
@SaIgnore
用于忽略权限校验,通常用于公开接口。
@RestController
@RequestMapping("/public")
public class PublicController {/*** 公开接口 - 忽略所有权限校验*/@SaIgnore@GetMapping("/info")public R<SystemInfoVo> getSystemInfo() {return R.ok(systemService.getPublicInfo());}/*** 验证码接口 - 忽略权限校验*/@SaIgnore@GetMapping("/captcha")public R<CaptchaVo> getCaptcha() {return R.ok(captchaService.generateCaptcha());}
}
复合权限表达式
Sa-Token支持复杂的权限表达式,可以实现AND、OR等逻辑组合。
AND逻辑 - 同时拥有多个权限
@RestController
@RequestMapping("/finance")
public class FinanceController {/*** 财务报表 - 需要同时拥有查看和导出权限*/@SaCheckPermission(value = {"finance:report:view", "finance:report:export"}, mode = SaMode.AND)@GetMapping("/report/export")public R<Void> exportReport(@RequestParam String reportType) {return toAjax(financeService.exportReport(reportType));}/*** 审批操作 - 需要同时拥有审批权限和对应级别权限*/@SaCheckPermission(value = {"finance:approve:basic", "finance:approve:level2"}, mode = SaMode.AND)@PostMapping("/approve/level2")public R<Void> approveLevel2(@RequestBody ApprovalDto approval) {return toAjax(approvalService.approveLevel2(approval));}
}
OR逻辑 - 拥有任一权限即可
@RestController
@RequestMapping("/content")
public class ContentController {/*** 内容管理 - 拥有编辑或审核权限即可*/@SaCheckPermission(value = {"content:edit", "content:audit"}, mode = SaMode.OR)@PostMapping("/manage")public R<Void> manageContent(@RequestBody ContentDto content) {return toAjax(contentService.manageContent(content));}/*** 数据查看 - 管理员或数据分析师都可以查看*/@SaCheckRole(value = {"admin", "analyst"}, mode = SaMode.OR)@GetMapping("/data/view")public R<DataVo> viewData(@RequestParam String dataType) {return R.ok(dataService.getData(dataType));}
}
权限与角色混合校验
@RestController
@RequestMapping("/system/advanced")
public class AdvancedController {/*** 高级功能 - 需要管理员角色或特定权限*/@SaCheckPermission(value = "system:advanced:access")@SaCheckRole(value = "admin", mode = SaMode.OR)@GetMapping("/features")public R<List<FeatureVo>> getAdvancedFeatures() {return R.ok(featureService.getAdvancedFeatures());}
}
自定义权限验证逻辑
实现StpInterface接口
@Component
public class StpInterfaceImpl implements StpInterface {@Autowiredprivate ISysUserService userService;@Autowiredprivate ISysRoleService roleService;@Autowiredprivate ISysMenuService menuService;/*** 返回一个账号所拥有的权限码集合*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {Long userId = Long.valueOf(loginId.toString());SysUser user = userService.selectUserById(userId);if (ObjectUtil.isNull(user)) {return Collections.emptyList();}// 管理员拥有所有权限if (user.isAdmin()) {return Arrays.asList("*:*:*");}// 获取用户权限列表Set<String> perms = menuService.selectMenuPermsByUserId(userId);// 添加动态权限perms.addAll(getDynamicPermissions(userId));return new ArrayList<>(perms);}/*** 返回一个账号所拥有的角色标识集合*/@Overridepublic List<String> getRoleList(Object loginId, String loginType) {Long userId = Long.valueOf(loginId.toString());SysUser user = userService.selectUserById(userId);if (ObjectUtil.isNull(user)) {return Collections.emptyList();}// 获取用户角色列表Set<String> roles = roleService.selectRolePermissionByUserId(userId);// 添加动态角色roles.addAll(getDynamicRoles(userId));return new ArrayList<>(roles);}/*** 获取动态权限*/private Set<String> getDynamicPermissions(Long userId) {Set<String> dynamicPerms = new HashSet<>();// 根据用户部门添加权限SysUser user = userService.selectUserById(userId);if (user.getDeptId() != null) {SysDept dept = deptService.selectDeptById(user.getDeptId());if (dept != null && "headquarters".equals(dept.getDeptType())) {dynamicPerms.add("system:headquarters:access");}}// 根据用户岗位添加权限List<SysPost> posts = postService.selectPostsByUserId(userId);for (SysPost post : posts) {if ("manager".equals(post.getPostCode())) {dynamicPerms.add("system:manager:access");}}return dynamicPerms;}/*** 获取动态角色*/private Set<String> getDynamicRoles(Long userId) {Set<String> dynamicRoles = new HashSet<>();// 根据业务逻辑动态分配角色// 例如:VIP用户自动获得vip角色if (isVipUser(userId)) {dynamicRoles.add("vip");}return dynamicRoles;}
}
自定义权限校验器
@Component
public class CustomPermissionChecker {/*** 检查数据权限*/public boolean checkDataPermission(String permission, Object dataId) {// 检查基础权限if (!StpUtil.hasPermission(permission)) {return false;}// 检查数据权限Object loginId = StpUtil.getLoginId();return dataPermissionService.hasDataAccess(Long.valueOf(loginId.toString()), dataId);}/*** 检查时间权限*/public boolean checkTimePermission(String permission, LocalTime startTime, LocalTime endTime) {if (!StpUtil.hasPermission(permission)) {return false;}LocalTime now = LocalTime.now();return now.isAfter(startTime) && now.isBefore(endTime);}/*** 检查IP权限*/public boolean checkIpPermission(String permission, String allowedIp) {if (!StpUtil.hasPermission(permission)) {return false;}String clientIp = ServletUtils.getClientIP();return allowedIp.equals(clientIp);}
}
权限注解的高级用法
条件权限校验
@RestController
@RequestMapping("/conditional")
public class ConditionalController {/*** 条件权限校验 - 根据参数动态校验权限*/@PostMapping("/operation")public R<Void> conditionalOperation(@RequestBody OperationDto operation) {// 根据操作类型动态校验权限String requiredPermission = "system:operation:" + operation.getType();StpUtil.checkPermission(requiredPermission);return toAjax(operationService.execute(operation));}/*** 分级权限校验*/@PostMapping("/level/{level}")public R<Void> levelOperation(@PathVariable Integer level, @RequestBody LevelOperationDto operation) {// 根据级别校验对应权限for (int i = 1; i <= level; i++) {StpUtil.checkPermission("system:level:" + i);}return toAjax(levelService.execute(operation, level));}
}
自定义注解
/*** 自定义数据权限注解*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {/*** 数据权限类型*/String value() default "";/*** 是否检查所有者权限*/boolean checkOwner() default false;/*** 是否检查部门权限*/boolean checkDept() default false;
}/*** 数据权限切面*/
@Aspect
@Component
public class DataPermissionAspect {@Around("@annotation(dataPermission)")public Object around(ProceedingJoinPoint point, DataPermission dataPermission) throws Throwable {// 检查基础登录StpUtil.checkLogin();// 获取当前用户Long userId = Long.valueOf(StpUtil.getLoginId().toString());// 检查数据权限if (dataPermission.checkOwner()) {checkOwnerPermission(point, userId);}if (dataPermission.checkDept()) {checkDeptPermission(point, userId);}return point.proceed();}private void checkOwnerPermission(ProceedingJoinPoint point, Long userId) {// 实现所有者权限检查逻辑}private void checkDeptPermission(ProceedingJoinPoint point, Long userId) {// 实现部门权限检查逻辑}
}
总结
本文详细介绍了Sa-Token的权限控制体系,包括:
- 基础注解:@SaCheckLogin、@SaCheckRole、@SaCheckPermission等
- 高级注解:@SaCheckSafe、@SaCheckBasic、@SaIgnore
- 复合表达式:AND、OR逻辑组合
- 自定义逻辑:StpInterface接口实现和自定义校验器
- 高级用法:条件权限、自定义注解等
Sa-Token的权限注解体系为RuoYi-Vue-Plus提供了灵活而强大的权限控制能力,能够满足各种复杂的业务场景需求。
在下一篇文章中,我们将探讨Sa-Token的高级特性,包括监听器机制、拦截器配置等内容。
参考资料
- Sa-Token权限认证文档
- Sa-Token注解鉴权文档
- RuoYi-Vue-Plus权限管理源码