您现在的位置是:首页 >技术教程 >java每日精进2.8(SpringSecurity应用鉴权)网站首页技术教程
java每日精进2.8(SpringSecurity应用鉴权)
1.一个接口的执行流程
@GetMapping("/list")
@Operation(summary = "获取菜单列表", description = "用于【菜单管理】界面")
@PreAuthorize("@ss.hasPermission('system:menu:query')")
public CommonResult<List<MenuRespVO>> getMenuList(MenuListReqVO reqVO) {
List<MenuDO> list = menuService.getMenuList(reqVO);
list.sort(Comparator.comparing(MenuDO::getSort));
return success(BeanUtils.toBean(list, MenuRespVO.class));
}
1.请求发送(HTTP 请求)
假设有一个 HTTP 请求访问 /list 路径,方法是 GET,请求的 URL 类似于 http://example.com/list。
2.Spring MVC 接收到请求:
Spring Boot 的 @RestController 或 @Controller 会接收到这个请求,并通过 @GetMapping("/list") 注解来匹配这个请求,找到对应的 getMenuList 方法。
3.方法入口:
当请求到达 getMenuList 方法时,Spring 会将请求中的参数绑定到 MenuListReqVO 对象(例如:reqVO)。如果请求中有参数(如分页信息、过滤条件等),Spring 会根据请求的参数自动映射到 MenuListReqVO 类的字段。
例如,假设请求 URL 是 http://example.com/list?page=1&size=10,那么 reqVO 对象会被填充为:
MenuListReqVO reqVO = new MenuListReqVO(); reqVO.setPage(1); reqVO.setSize(10);
4.权限校验(@PreAuthorize 注解):
在方法执行之前,@PreAuthorize 注解指定了一个表达式 @ss.hasPermission('system:menu:query'),这个表达式会被 Spring Security 执行。
@ss 是 Spring Security 中的一个预定义变量,通常它代表一个自定义的权限检查服务类。可以在 Spring 容器中配置并注入该类。
@PreAuthorize("@ss.hasPermission('system:menu:query')") 会调用 SecurityFrameworkServiceImpl 中的 hasPermission 方法来验证当前用户是否具有 system:menu:query 权限
@Override
@SneakyThrows
public boolean hasAnyPermissions(String... permissions) {
Long userId = getLoginUserId();
if (userId == null) {
return false;
}
return hasAnyPermissionsCache.get(new KeyValue<>(userId, Arrays.asList(permissions)));
}
/**
* 针对 {@link #hasAnyPermissions(String...)} 的缓存
*/
private final LoadingCache<KeyValue<Long, List<String>>, Boolean> hasAnyPermissionsCache = buildCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<KeyValue<Long, List<String>>, Boolean>() {
@Override
public Boolean load(KeyValue<Long, List<String>> key) {
return permissionApi.hasAnyPermissions(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData();
}
});
这段代码定义了一个缓存 hasAnyPermissionsCache,用于存储基于 KeyValue<Long, List<String>> 作为键值对的权限检查结果。它使用了 LoadingCache 来实现缓存,缓存的过期时间设置为 1 分钟。每当缓存中没有对应的值时,会通过 CacheLoader 进行加载并进行权限检查。
1.LoadingCache<KeyValue<Long, List<String>>, Boolean>
LoadingCache 是 Guava 库提供的一个缓存类,它在缓存中没有相应值时,能够自动加载数据,并将加载的结果缓存起来。在这段代码中:
KeyValue<Long, List<String>>:缓存的键是一个 KeyValue 对象,它的 Key 是 Long 类型,表示用户的 ID 或其他相关的键;Value 是一个 List<String>,表示权限的列表(多个权限名称)。
Boolean:缓存的值是一个 Boolean 类型,表示是否拥有这些权限的检查结果。
2.buildCache(...) 方法
buildCache(...) 是一个方法,返回一个已经配置好的 LoadingCache 实例。通过这个方法,我们可以设置缓存的行为,例如过期时间、最大缓存数量等。
Duration.ofMinutes(1L):设置缓存的过期时间为 1 分钟,也就是说,缓存存储的数据会在 1 分钟后失效,过期后需要重新加载数据。
3. CacheLoader<KeyValue<Long, List<String>>, Boolean>
CacheLoader 是 Guava 中的一个接口,提供了缓存未命中时的加载机制。它包含一个 load() 方法,用于定义缓存值的加载逻辑。当缓存中没有需要的数据时,会调用 load() 方法来加载并返回相应的值。
5. 调用远程服务进行权限验证
如果缓存中没有相应的权限信息,hasAnyPermissionsCache.get 会通过 PermissionApi 调用远程服务,验证用户是否具备 system:menu:query 权限。
@Override
public Boolean load(KeyValue<Long, List<String>> key) {
return permissionApi.hasAnyPermissions(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData();
}
permissionApi.hasAnyPermissions 会发起 HTTP 请求到权限服务,通过 FeignClient 调用 PermissionApi 中的 hasAnyPermissions 接口。
PermissionApi 中的 hasAnyPermissions 方法:
@GetMapping(PREFIX + "/has-any-permissions")
@Operation(summary = "判断是否有权限,任意一个即可")
@Parameters({
@Parameter(name = "userId", description = "用户编号", example = "1", required = true),
@Parameter(name = "permissions", description = "权限", example = "read,write", required = true)
})
CommonResult<Boolean> hasAnyPermissions(@RequestParam("userId") Long userId,
@RequestParam("permissions") String... permissions);
这个接口会判断给定的 userId 是否有传入的 permissions 中的任意一个权限。它通过 CommonResult<Boolean> 返回结果,表示是否有权限。
@Override
public boolean hasAnyPermissions(Long userId, String... permissions) {
// 如果为空,说明已经有权限
if (ArrayUtil.isEmpty(permissions)) {
return true;
}
// 获得当前登录的角色。如果为空,说明没有权限
List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
if (CollUtil.isEmpty(roles)) {
return false;
}
// 情况一:遍历判断每个权限,如果有一满足,说明有权限
for (String permission : permissions) {
if (hasAnyPermission(roles, permission)) {
return true;
}
}
// 情况二:如果是超管,也说明有权限
return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
}
/**
* 判断指定角色,是否拥有该 permission 权限
*
* @param roles 指定角色数组
* @param permission 权限标识
* @return 是否拥有
*/
private boolean hasAnyPermission(List<RoleDO> roles, String permission) {
List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission);
// 采用严格模式,如果权限找不到对应的 Menu 的话,也认为没有权限
if (CollUtil.isEmpty(menuIds)) {
return false;
}
// 判断是否有权限
Set<Long> roleIds = convertSet(roles, RoleDO::getId);
for (Long menuId : menuIds) {
// 获得拥有该菜单的角色编号集合
Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
// 如果有交集,说明有权限
if (CollUtil.containsAny(menuRoleIds, roleIds)) {
return true;
}
}
return false;
}
根据权限获取权限可以访问的菜单,再根据菜单获取可以访问菜单的角色,最后判断和用户的角色是否有交集,要有交集则证明用户可以访问此菜单;
对每个权限对进行判断,有一个即可访问;





U8W/U8W-Mini使用与常见问题解决
QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。...
stm32使用HAL库配置串口中断收发数据(保姆级教程)
分享几个国内免费的ChatGPT镜像网址(亲测有效)
Allegro16.6差分等长设置及走线总结