feat:完整实现权限隔离

This commit is contained in:
wangyu 2022-01-03 01:19:44 +08:00
parent 0a0b63e480
commit 38f90de292
16 changed files with 341 additions and 50 deletions

View File

@ -3,19 +3,22 @@ package com.flyfish.framework.builder;
import com.flyfish.framework.context.DateContext;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.domain.base.Qo;
import com.mongodb.BasicDBList;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ClassUtils;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@ -25,12 +28,11 @@ import java.util.stream.Collectors;
*/
public final class CriteriaBuilder<T extends Domain> {
private final Map<String, BiFunction<Criteria, Object, Criteria>> functionMap = new HashMap<>();
private final Map<String, String> keyMapper = new HashMap<>();
private final List<Supplier<Criteria>> criterias = new ArrayList<>();
private Qo<T> qo;
private Map<String, BiFunction<Criteria, Object, Criteria>> functionMap = new HashMap<>();
private Map<String, String> keyMapper = new HashMap<>();
/**
* 构造器接受一个qo
*
@ -44,6 +46,20 @@ public final class CriteriaBuilder<T extends Domain> {
return builder;
}
/**
* 创建criteria列表
*
* @param criteria 多个查询
* @return 结果
*/
public static BasicDBList createCriteriaList(Criteria... criteria) {
BasicDBList bsonList = new BasicDBList();
for (Criteria c : criteria) {
bsonList.add(c.getCriteriaObject());
}
return bsonList;
}
/**
* 添加策略
*
@ -81,6 +97,17 @@ public final class CriteriaBuilder<T extends Domain> {
return this;
}
/**
* 添加自定义条件
*
* @param function 函数
* @return 结果
*/
public CriteriaBuilder<T> with(Supplier<Criteria> function) {
this.criterias.add(function);
return this;
}
/**
* 批量导入规则
*
@ -94,11 +121,11 @@ public final class CriteriaBuilder<T extends Domain> {
}
public Criteria build() {
if (!CollectionUtils.isEmpty(functionMap)) {
if (!MapUtils.isEmpty(functionMap)) {
// 键集合
Set<String> keySet = functionMap.keySet();
// 建立查询器
Criteria[] criteria = Arrays.stream(BeanUtils.getPropertyDescriptors(qo.getClass()))
List<Criteria> criteria = Arrays.stream(BeanUtils.getPropertyDescriptors(qo.getClass()))
.filter(propertyDescriptor -> keySet.contains(propertyDescriptor.getName()))
.map(propertyDescriptor -> {
try {
@ -117,7 +144,13 @@ public final class CriteriaBuilder<T extends Domain> {
return null;
})
.filter(Objects::nonNull)
.toArray(Criteria[]::new);
.collect(Collectors.toList());
// 添加自定义criteria
if (CollectionUtils.isNotEmpty(criterias)) {
for (Supplier<Criteria> builder : criterias) {
criteria.add(builder.get());
}
}
return combine(criteria);
}
return null;
@ -165,12 +198,12 @@ public final class CriteriaBuilder<T extends Domain> {
* @param criteria 结果
* @return 返回
*/
private Criteria combine(Criteria[] criteria) {
if (criteria.length == 0) {
private Criteria combine(List<Criteria> criteria) {
if (criteria.size() == 0) {
return new Criteria();
}
if (criteria.length == 1) {
return criteria[0];
if (criteria.size() == 1) {
return criteria.get(0);
}
return new Criteria().andOperator(criteria);
}

View File

@ -0,0 +1,23 @@
package com.flyfish.framework.context;
import com.flyfish.framework.domain.base.IUser;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import reactor.core.publisher.Mono;
/**
* 异步的用户上下文
*
* @author wangyu
*/
public class ReactiveUserContext {
/**
* 当前用户
*
* @return 结果
*/
public static Mono<IUser> currentUser() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> (IUser) ctx.getAuthentication().getPrincipal());
}
}

View File

@ -15,4 +15,8 @@ public abstract class AuthorizedDomain extends AuditDomain {
// 作用域id一般是部门用户存储时插入
@Property(readonly = true)
private String authorizeId;
// 是否已发布已发布的内容谁都不能修改
@Property(readonly = true)
private Boolean published = false;
}

View File

@ -3,11 +3,12 @@ package com.flyfish.framework.domain.authorized;
import com.flyfish.framework.builder.CriteriaBuilder;
import com.flyfish.framework.domain.base.NameLikeQo;
import com.flyfish.framework.domain.po.Department;
import com.flyfish.framework.enums.UserType;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.mongodb.core.query.Criteria;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
@ -19,12 +20,15 @@ import java.util.Set;
@Setter
public abstract class AuthorizedQo<T extends AuthorizedDomain> extends NameLikeQo<T> {
// 是否已发布
private Boolean published;
/**
* 获取可见的权限ids
*
* @return 结果
*/
public Set<String> getAuthorizedIds() {
private Set<String> authorizeIds() {
if (user instanceof AuthorizedUserDetails) {
return ((AuthorizedUserDetails) user).getAuthorityCodes();
}
@ -33,6 +37,17 @@ public abstract class AuthorizedQo<T extends AuthorizedDomain> extends NameLikeQ
@Override
public CriteriaBuilder<T> criteriaBuilder() {
return super.criteriaBuilder().with("authorizedIds", "authorizeId", CriteriaBuilder.Builders.IN);
if (user.getType() == UserType.SUPER_ADMIN) {
return super.criteriaBuilder().with("published");
}
return super.criteriaBuilder()
.with(() -> Criteria.where("$or").is(
CriteriaBuilder.createCriteriaList(
Criteria.where("authorizeId").in(authorizeIds()),
Criteria.where("creatorId").is(user.getId())
.and("authorizeId").in(((AuthorizedUserDetails) user).getVisibleDeparts())
)
))
.with("published");
}
}

View File

@ -0,0 +1,16 @@
package com.flyfish.framework.domain.authorized;
import com.flyfish.framework.domain.base.Vo;
import lombok.Data;
/**
* 授权的vo需要返回权限标识
*
* @param <T> 泛型
*/
@Data
public abstract class AuthorizedVo<T extends AuthorizedDomain> implements Vo<T> {
// 是否是只读数据
protected Boolean readonly;
}

View File

@ -130,6 +130,6 @@ public class BaseQo<T extends Domain> implements Qo<T> {
@Override
public Sort sorts() {
return Sort.by(Sort.Order.desc("createTime"));
return Sort.by(Sort.Order.desc("modifyTime"));
}
}

View File

@ -3,7 +3,6 @@ package com.flyfish.framework.domain.base;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flyfish.framework.annotations.Generation;
import com.flyfish.framework.annotations.Property;
import com.flyfish.framework.domain.po.User;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
@ -51,7 +50,7 @@ public abstract class Domain implements Po, Named, Serializable {
@Transient
@JsonIgnore
@Property(readonly = true)
private User currentUser;
private IUser currentUser;
@Override
public int hashCode() {

View File

@ -83,7 +83,8 @@ public class DefaultReactiveRepositoryImpl<T extends Domain> extends SimpleReact
return Mono.justOrEmpty(QueryBuildUtils.getQuery(query))
.flatMapMany(querying -> mongoOperations.find(querying,
entityInformation.getJavaType(),
entityInformation.getCollectionName()));
entityInformation.getCollectionName()))
.doOnNext(t -> t.setCurrentUser(query.getUser()));
}
/**
@ -117,6 +118,7 @@ public class DefaultReactiveRepositoryImpl<T extends Domain> extends SimpleReact
return Mono.justOrEmpty(QueryBuildUtils.getQuery(query))
.flatMap(querying -> mongoOperations.find(querying.with(pageable),
entityInformation.getJavaType(), entityInformation.getCollectionName())
.doOnNext(t -> t.setCurrentUser(query.getUser()))
.collectList()
.flatMap(list -> ReactivePageableExecutionUtils.getPage(list, pageable, this.count(query))))
.defaultIfEmpty(Page.empty());

View File

@ -0,0 +1,88 @@
package com.flyfish.framework.utils;
import com.flyfish.framework.domain.authorized.AuthorizedDomain;
import com.flyfish.framework.domain.authorized.AuthorizedVo;
import com.flyfish.framework.domain.base.IUser;
import com.flyfish.framework.domain.po.Department;
import com.flyfish.framework.domain.po.Role;
import com.flyfish.framework.enums.UserType;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.data.util.CastUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 部门工具类
*
* @author wangyu
*/
public final class DepartUtils {
/**
* 合并部门有包含关系的部门将被合并
*
* @param departments 部门
* @return 结果
*/
public static Set<String> mergeDeparts(List<Department> departments) {
if (CollectionUtils.isEmpty(departments)) {
return Collections.emptySet();
}
Set<String> departs = departments.stream().map(Department::getId)
.collect(Collectors.toSet());
List<String> removing = new ArrayList<>();
for (Department department : departments) {
if (CollectionUtils.containsAny(department.getParentIds(), departs)) {
removing.add(department.getId());
}
}
removing.forEach(departs::remove);
return departs;
}
/**
* 包装权限必须调用
*
* @param item 内容
* @return 结果
*/
public static <T> T wrapAuthority(T item, AuthorizedDomain po) {
if (item instanceof AuthorizedVo) {
AuthorizedVo<?> vo = CastUtils.cast(item);
// 已发布的内容谁都不能修改
if (BooleanUtils.isTrue(po.getPublished())) {
vo.setReadonly(true);
} else {
vo.setReadonly(false);
// 获取当前用户
IUser user = po.getCurrentUser();
if (user.getType() != UserType.SUPER_ADMIN) {
// 用户所属部门
Set<String> userDeparts = DepartUtils.mergeDeparts(user.getDepartments());
// 实体归属部门
String currentDepart = po.getAuthorizeId();
// 用户权限合集
Set<Role.Authority> authorities = user.getRoles().stream()
.flatMap(role -> null == role.getAuthorities() ? Stream.empty() : role.getAuthorities().stream())
.collect(Collectors.toSet());
// 取出权限便于判定
boolean admin = authorities.contains(Role.Authority.ADMIN);
boolean edit = authorities.contains(Role.Authority.EDIT);
boolean editChildren = authorities.contains(Role.Authority.EDIT_CHILDREN);
// 开始判定只读情况管理员权限或者创建者均具有读写权限
if (!admin && !po.getCreatorId().equals(user.getId())) {
vo.setReadonly(!edit && userDeparts.contains(currentDepart) ||
!editChildren && !userDeparts.contains(currentDepart));
}
}
}
}
return item;
}
}

View File

@ -0,0 +1,33 @@
package com.flyfish.framework.config.audit;
import com.flyfish.framework.auditor.ReactiveBeanPoster;
import com.flyfish.framework.domain.po.Role;
import com.flyfish.framework.service.UserDetailsConverter;
import com.flyfish.framework.utils.ReactiveRedisOperations;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* 角色bean后置处理
*
* @author wangyu
*/
@Component
@RequiredArgsConstructor
public class RoleBeanPoster implements ReactiveBeanPoster<Role> {
private final ReactiveRedisOperations reactiveRedisOperations;
private final UserDetailsConverter userDetailsConverter;
/**
* 对入库的实体进行审查并执行额外功能
*
* @param data 原数据
*/
@Override
public Mono<Role> post(Role data) {
// 更新缓存
return reactiveRedisOperations.del(reactiveRedisOperations.getKeys("user-*")).thenReturn(data);
}
}

View File

@ -5,11 +5,14 @@ import com.flyfish.framework.controller.reactive.ReactiveTreeController;
import com.flyfish.framework.domain.AdminUserDetails;
import com.flyfish.framework.domain.DepartmentQo;
import com.flyfish.framework.domain.po.Department;
import com.flyfish.framework.enums.UserType;
import com.flyfish.framework.utils.UserUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
@ -31,10 +34,17 @@ public class DepartmentController extends ReactiveTreeController<Department, Dep
public Mono<Result<List<Department>>> getAuthorizedTree() {
return ReactiveSecurityContextHolder.getContext()
.map(UserUtils::extractUserDetails)
.flatMap(detail -> {
.flatMapMany(detail -> {
AdminUserDetails details = (AdminUserDetails) detail;
return reactiveService.getByIds(new ArrayList<>(details.getVisibleDeparts())).collectList();
if (details.getType() == UserType.SUPER_ADMIN) {
return reactiveService.getAll();
}
if (CollectionUtils.isNotEmpty(details.getVisibleDeparts())) {
return reactiveService.getByIds(new ArrayList<>(details.getVisibleDeparts()));
}
return Flux.empty();
})
.collectList()
.map(this::makeTree)
.map(Result::accept);
}

View File

@ -1,11 +1,19 @@
package com.flyfish.framework.service;
import com.flyfish.framework.domain.AdminUserDetails;
import com.flyfish.framework.domain.DepartmentQo;
import com.flyfish.framework.domain.authorized.AuthorizedUserDetails;
import com.flyfish.framework.domain.base.Qo;
import com.flyfish.framework.domain.po.Department;
import com.flyfish.framework.enums.UserType;
import com.flyfish.framework.service.impl.BaseReactiveServiceImpl;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
@ -34,4 +42,39 @@ public class DepartmentService extends BaseReactiveServiceImpl<Department> {
query.fields().include("_id");
return reactiveMongoOperations.find(query, Department.class).map(Department::getId).collect(Collectors.toSet());
}
/**
* 查询列表
*
* @param query 查询
* @return 结果
*/
@Override
public Flux<Department> getList(Qo<Department> query) {
if (query instanceof DepartmentQo) {
DepartmentQo qo = (DepartmentQo) query;
// 如果是非管理员
if (qo.getUser().getType() != UserType.SUPER_ADMIN) {
// 使用列表内的部门作为条件
AdminUserDetails userDetails = (AdminUserDetails) query.getUser();
// 查询根节点下的节点
if (Department.ROOT.equals(qo.getParentId())) {
qo.setParentId(null);
qo.setIds(userDetails.getVisibleDeparts());
// 不递归指定深度保证单层
if (BooleanUtils.isNotTrue(qo.getRecursive())) {
// 指定深度
int maxDepth = userDetails.getDepartments().stream().max((a, b) -> a.getDepth() - b.getDepth())
.map(Department::getDepth)
.orElse(0);
qo.setDepth(maxDepth);
}
}
if (null == qo.getParentId() && CollectionUtils.isEmpty(qo.getParentIds())) {
qo.setIds(userDetails.getVisibleDeparts());
}
}
}
return super.getList(query);
}
}

View File

@ -6,18 +6,16 @@ import com.flyfish.framework.domain.po.Department;
import com.flyfish.framework.domain.po.Role;
import com.flyfish.framework.enums.UserType;
import com.flyfish.framework.utils.CopyUtils;
import com.flyfish.framework.utils.DepartUtils;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
@RequiredArgsConstructor
@ -51,16 +49,11 @@ public class UserDetailsConverter {
*/
private Mono<UserDetails> judgeDeparts(IUser user, AdminUserDetails userDetails) {
// 整合数据权限
Set<Role.Authority> authorities = user.getRoles().stream().reduce(new HashSet<>(), (result, item) -> {
// 管理员拥有完全控制权限
if (BooleanUtils.isTrue(item.getAdmin())) {
result.add(Role.Authority.ADMIN);
}
result.addAll(item.getAuthorities());
return result;
}, (a, b) -> a);
Set<Role.Authority> authorities = user.getRoles().stream()
.flatMap(role -> null == role.getAuthorities() ? Stream.empty() : role.getAuthorities().stream())
.collect(Collectors.toSet());
// 根据权限组装查询条件
Set<String> departs = mergeDeparts(user.getDepartments());
Set<String> departs = DepartUtils.mergeDeparts(user.getDepartments());
// 查询所有子部门id
return departmentService.getSubCodes(departs)
.map(codes -> {
@ -80,30 +73,25 @@ public class UserDetailsConverter {
}
// 最终设置权限
if (CollectionUtils.isNotEmpty(all)) {
userDetails.setAuthorityCodes(SetUtils.union(all, userDetails.getAuthorityCodes()));
userDetails.setAuthorityCodes(union(all, userDetails.getAuthorityCodes()));
}
userDetails.setVisibleDeparts(SetUtils.union(codes, departs));
userDetails.setVisibleDeparts(union(codes, departs));
return userDetails;
});
}
/**
* 合并部门有包含关系的部门将被合并
* 链接两个set
*
* @param departments 部门
* @param a 第一个
* @param b 第二个
* @return 结果
*/
private Set<String> mergeDeparts(List<Department> departments) {
Set<String> departs = departments.stream().map(Department::getId)
.collect(Collectors.toSet());
List<String> removing = new ArrayList<>();
for (Department department : departments) {
if (CollectionUtils.containsAny(department.getParentIds(), departs)) {
removing.add(department.getId());
}
}
removing.forEach(departs::remove);
return departs;
private Set<String> union(Set<String> a, Set<String> b) {
Set<String> result = new HashSet<>();
result.addAll(a);
result.addAll(b);
return result;
}
}

View File

@ -1,11 +1,19 @@
package com.flyfish.framework.service;
import com.flyfish.framework.domain.UserQo;
import com.flyfish.framework.domain.authorized.AuthorizedUserDetails;
import com.flyfish.framework.domain.base.Qo;
import com.flyfish.framework.domain.po.User;
import com.flyfish.framework.enums.UserType;
import com.flyfish.framework.repository.UserRepository;
import com.flyfish.framework.service.impl.BaseReactiveServiceImpl;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
/**
* 异步用户service
*
@ -25,4 +33,22 @@ public class UserService extends BaseReactiveServiceImpl<User> implements UserFi
return ((UserRepository) repository).findByUsername(username);
}
/**
* 分页查询这里使用作用域查询
*
* @param query 查询实体
* @return 结果
*/
@Override
public Mono<Page<User>> getPageList(Qo<User> query) {
// 非超级管理员永远返回作用域的部门人员
if (query.getUser().getType() != UserType.SUPER_ADMIN) {
AuthorizedUserDetails userDetails = (AuthorizedUserDetails) query.getUser();
UserQo qo = (UserQo) query;
if (CollectionUtils.isEmpty(qo.getDepartments())) {
qo.setDepartments(new ArrayList<>(userDetails.getVisibleDeparts()));
}
}
return super.getPageList(query);
}
}

View File

@ -1,6 +1,7 @@
package com.flyfish.framework.controller.reactive;
import com.flyfish.framework.bean.Result;
import com.flyfish.framework.configuration.annotations.PagedQuery;
import com.flyfish.framework.domain.tree.RootTreeNode;
import com.flyfish.framework.domain.tree.TreeDomain;
import com.flyfish.framework.domain.tree.TreeQo;
@ -45,7 +46,7 @@ public abstract class ReactiveTreeController<T extends TreeDomain<T>, Q extends
* @return 结果
*/
@GetMapping("tree")
public Mono<Result<List<T>>> getTree(Q qo) {
public Mono<Result<List<T>>> getTree(@PagedQuery Q qo) {
qo.setRecursive(true);
// 第一步查询全部并根据条件筛选
return reactiveService.getList(qo)

View File

@ -73,6 +73,16 @@ public class ReactiveRedisOperations {
return Mono.just(0L);
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public Mono<Long> del(Flux<String> key) {
return redisTemplate.delete(key);
}
// ============================String=============================
/**