基于角色的权限控制,用户分配角色,角色分配菜单。
1. 权限注解
1.基于【权限标识】的权限控制
权限标识,对应 system_menu 表的 permission 字段,推荐格式为 {系统}:{模块}:{操作},例如说 system:admin:add 标识 system 服务的添加管理员。
- @PreAuthorize (opens new window)是 Spring Security 内置的前置权限注解,添加在接口方法上,声明需要的权限,实现访问权限的控制。
- 当 @PreAuthorize 注解里的 Spring EL 表达式返回 false 时,表示没有权限。
ss是自动配置类中注入的一个名字为ss的bean,传入的是PermissionApi接口的一个实现类,返回的是SecurityFrameworkServiceImpl类型
传入的实现类定义在system模块下
返回的实体类在
这样当访问一个接口时,会进到SecurityFrameworkServiceImpl类的hasPermission方法。传入的就是controller中的system:dept:update
然后继续往下调用
PermissionApi就是PermissionApiImpl类型
然后会调用permissionService的hasAnyPermissions方法
然后进入判断逻辑
@Override
public boolean hasAnyPermissions(Long userId, String... permissions) {
// 如果为空,说明已经有权限,也就是controller传入的权限就是空,也就是可以不加注解
if (ArrayUtil.isEmpty(permissions)) {
return true;
}
// 获得当前登录的角色(从缓存)。如果为空,说明没有权限,传入当前用户id和开启的status 0
//流程就是获取到当前用户的所有角色id 然后判断这些角色是否为关闭状态
Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
if (CollUtil.isEmpty(roleIds)) {
return false;
}
// 判断是否是超管。如果是,当然符合条件
if (roleService.hasAnySuperAdmin(roleIds)) {
return true;
}
// 遍历权限,判断是否有一个满足
return Arrays.stream(permissions).anyMatch(permission -> {
//根据permission获取菜单,获取不到说明没有权限
List<MenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
// 采用严格模式,如果权限找不到对应的 Menu 的话,认为
if (CollUtil.isEmpty(menuList)) {
return false;
}
// 获得是否拥有该权限,任一一个
//获取到菜单,去缓存中根据菜单id看是否能匹配当前角色
//anyMatch用法 遍历menuList,
//CollUtil.containsAny(roleIds, menuRoleCache.get(menu.getId()))有一个返回true的话,就返回true
return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds,
menuRoleCache.get(menu.getId())));
});
}
这样这个判断就完成了。
2.基于角色的权限控制
@Override
public boolean hasAnyRoles(Long userId, String... roles) {
// 如果为空,说明已经有权限
if (ArrayUtil.isEmpty(roles)) {
return true;
}
// 获得当前登录的角色。如果为空,说明没有权限
Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
if (CollUtil.isEmpty(roleIds)) {
return false;
}
// 判断是否是超管。如果是,当然符合条件
if (roleService.hasAnySuperAdmin(roleIds)) {
return true;
}
Set<String> userRoles = convertSet(roleService.getRoleListFromCache(roleIds),
RoleDO::getCode);
return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
}
3.登陆判断
@PreAuthenticated 注解
- @PreAuthenticated (opens new window)是项目自定义的认证注解,添加在接口方法上,声明登录的用户才允许访问。
- 主要使用场景是,针对用户 App 的 /app-app/** 的 RESTful API 接口,默认是无需登录的,通过 @PreAuthenticated 声明它需要进行登录。
针对这个注解的aop
如果加了这个注解而且没有登录,会抛出异常,然后会被GlobalExceptionHandler类的serviceExceptionHandler方法捕获。返回前端异常信息。
2.自定义权限配置
1.自定义 AuthorizeRequestsCustomizer 实现
例如yudao-module-infra 模块的 SecurityConfiguration ()类
@Configuration("infraSecurityConfiguration")
public class SecurityConfiguration {
@Value("${spring.boot.admin.context-path:''}")
private String adminSeverContextPath;
@Bean("infraAuthorizeRequestsCustomizer")
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
return new AuthorizeRequestsCustomizer() {
@Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
// Swagger 接口文档
registry.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous();
// Spring Boot Actuator 的安全配置
registry.antMatchers("/actuator").anonymous()
.antMatchers("/actuator/**").anonymous();
// Druid 监控
registry.antMatchers("/druid/**").anonymous();
// Spring Boot Admin Server 的安全配置
registry.antMatchers(adminSeverContextPath).anonymous()
.antMatchers(adminSeverContextPath + "/**").anonymous();
}
};
}
}
- permitAll() 方法:所有用户可以任意访问,包括带上 Token 访问
- anonymous() 方法:匿名用户可以任意访问,带上 Token 访问会报错
2.@PermitAll 注解
直接在接口方法加这个注解