feat:(REQ-2509) 优化鉴权接口,把鉴权和查询菜单分开

This commit is contained in:
lilong 2024-05-31 10:51:35 +08:00
parent 76323e65f1
commit 99b822cc2f
19 changed files with 1159 additions and 52 deletions

View File

@ -1,9 +1,8 @@
package cn.axzo.tyr.client.model.req;
import cn.axzo.framework.auth.domain.TerminalInfo;
import cn.axzo.tyr.client.common.enums.WorkspaceJoinType;
import cn.axzo.tyr.client.model.enums.FeatureType;
import cn.axzo.tyr.client.model.enums.IdentityType;
import cn.axzo.tyr.client.model.res.IdentityAuthRes;
import cn.hutool.core.collection.CollectionUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -13,18 +12,11 @@ import lombok.NoArgsConstructor;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.axzo.tyr.client.common.enums.WorkspaceJoinType.ENT_GROUP;
import static cn.axzo.tyr.client.common.enums.WorkspaceJoinType.ENT_TEAM;
import static cn.axzo.tyr.client.common.enums.WorkspaceJoinType.PROJ_GROUP;
import static cn.axzo.tyr.client.common.enums.WorkspaceJoinType.PROJ_TEAM;
/**
* @author tanjie@axzo.cn
* @date 2023/10/13 15:23
@ -73,6 +65,23 @@ public class IdentityAuthReq {
@Builder.Default
private boolean useCache = true;
public IdentityAuthRes toEmpty() {
IdentityAuthRes result = new IdentityAuthRes();
result.setIdentity(this.getIdentityId());
result.setIdentityType(this.getIdentityType());
result.setPersonId(this.getPersonId());
List<IdentityAuthRes.WorkspacePermission> permissions = this.getWorkspaceOusPairs().stream()
.map(workspaceOuPair -> IdentityAuthRes.WorkspacePermission.builder()
.workspaceId(workspaceOuPair.getWorkspaceId())
.ouId(workspaceOuPair.getOuId())
.build())
.collect(Collectors.toList());
result.setPermissions(permissions);
return result;
}
public void distinctOUWorkspacePair() {
if (CollectionUtil.isEmpty(this.workspaceOusPairs)) {
return;

View File

@ -17,4 +17,10 @@ public class SaasPermissionRes {
* 资源编码-权限码
*/
private String featureCode;
/**
* 资源所属端
*/
private String terminal;
}

View File

@ -1,10 +1,13 @@
package cn.axzo.tyr.client.model.roleuser.dto;
import cn.axzo.tyr.client.model.res.SaasPermissionRes;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@ -18,6 +21,8 @@ public class SaasRoleUserV2DTO {
private SaasRoleUser saasRoleUser;
private SaasRole saasRole;
@Data
@Builder
@NoArgsConstructor
@ -34,4 +39,38 @@ public class SaasRoleUserV2DTO {
*/
private String realName;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class SaasRole {
private Long id;
/**
* 角色名称
*/
private String name;
/**
* 角色类型
* cn.axzo.tyr.client.common.enums.RoleTypeEnum
*/
private String roleType;
private Long workspaceId;
private Long ownerOuId;
/**
* 产品单位类型
* 1:总包 2:建设单位 3:监理单位 4:劳务分包 5:专业分包 6:OMS通用 7:企业通用 8:企业内班组 9:项目内班组
*/
private Integer productUnitType;
/**
* 角色权限
*/
private List<SaasPermissionRes> saasPermissions;
}
}

View File

@ -2,7 +2,9 @@ package cn.axzo.tyr.client.model.roleuser.req;
import cn.axzo.foundation.dao.support.wrapper.CriteriaField;
import cn.axzo.foundation.dao.support.wrapper.Operator;
import cn.axzo.tyr.client.model.enums.IdentityType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@ -20,4 +22,62 @@ public class ListRoleUserRelationParam {
@CriteriaField(ignore = true)
private Boolean needUsers;
/**
* 自然人Id
*/
@CriteriaField(field = "naturalPersonId", operator = Operator.EQ)
private Long personId;
/**
* 身份Id
*/
@CriteriaField(field = "identityId", operator = Operator.EQ)
private Long identityId;
/**
* 身份类型 1:工人 2:从业人员 3:班组长 4:运营人员 5:政务人员
*/
@CriteriaField(field = "identityType", operator = Operator.EQ)
private IdentityType identityType;
/**
* workspaceId和ouId配对查询
* 例如((workspaceId = ## and ouId = ##) or (workspaceId = ## and ouId = ##))
*/
@CriteriaField(ignore = true)
private List<WorkspaceOuPair> workspaceOuPairs;
@CriteriaField(ignore = true)
private Boolean needRole;
/**
* 从saas_feature中查询权限点
*/
@CriteriaField(ignore = true)
private Boolean needRolePermissionOld;
/**
* 根据权限点id过滤
*/
@CriteriaField(ignore = true)
private List<Long> featureIds;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class WorkspaceOuPair {
/**
* 租户id
*/
private Long workspaceId;
/**
* 单位id
*/
private Long ouId;
}
}

View File

@ -9,7 +9,9 @@ import lombok.Getter;
public enum BizResultCode implements IResultCode {
CANT_DELETE_ROLE_GROUP("100001", "不能删除角色分组,当前角色分组下有子角色分组"),
ROLE_GROUP_NOT_FOUND("100002", "角色分组不存在");
ROLE_GROUP_NOT_FOUND("100002", "角色分组不存在"),
REDIS_ROLE_NOT_NULL("100003", "角色id不能为空"),
REDIS_PRODUCT_NOT_NULL("100004", "产品id不能为空");
private String errorCode;
private String errorMessage;

View File

@ -17,6 +17,8 @@ import cn.axzo.tyr.server.repository.entity.SaasFeatureResource;
import cn.axzo.tyr.server.repository.entity.SaasRole;
import cn.axzo.tyr.server.repository.entity.SaasRoleGroup;
import cn.axzo.tyr.server.repository.entity.SaasRoleGroupRelation;
import cn.axzo.tyr.server.service.ProductPermissionCacheService;
import cn.axzo.tyr.server.service.RolePermissionCacheService;
import cn.axzo.tyr.server.service.RoleService;
import cn.axzo.tyr.server.service.SaasCommonDictService;
import cn.axzo.tyr.server.service.SaasFeatureResourceService;
@ -67,6 +69,11 @@ public class PrivateController {
private SaasFeatureResourceDao saasFeatureResourceDao;
@Autowired
private SaasFeatureResourceCacheService saasFeatureResourceCacheService;
@Autowired
private RolePermissionCacheService rolePermissionCacheService;
@Autowired
private ProductPermissionCacheService productPermissionCacheService;
/**
@ -251,6 +258,22 @@ public class PrivateController {
});
}
@PostMapping("/api/private/productPermission/add")
public Object addProductPermission(@Validated @RequestBody ProductPermissionCacheService.StoreProductPermissionParam request) {
productPermissionCacheService.store(request);
return "ok";
}
@PostMapping("/api/private/productPermission/get")
public Object getProductPermission(@Validated @RequestBody ProductPermissionCacheService.ListProductPermissionParam request) {
return productPermissionCacheService.list(request);
}
@PostMapping("/api/private/productPermission/has")
public Object hasProductIds(@Validated @RequestBody ProductPermissionCacheService.HasProductPermissionParam request) {
return productPermissionCacheService.hasProductIds(request);
}
@Data
@Builder
@NoArgsConstructor

View File

@ -2,6 +2,13 @@ package cn.axzo.tyr.server.service;
import cn.axzo.tyr.client.model.res.IdentityAuthRes;
import cn.axzo.tyr.server.model.PermissionCacheKey;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Set;
/**
* 授权缓存服务
@ -23,4 +30,128 @@ public interface PermissionCacheService {
/** 标记缓存暂时不可用 - 等缓存全部失效 **/
void markTempDisable(PermissionCacheKey key);
/**
* 缓存权限码跟角色的信息采用set数据结构
* redisKeyfeatureCode
* redisValue: roleId
* @param param
*/
void cachePermissionRole(CachePermissionRoleParam param);
/**
* 根据权限码查询对应的角色信息
* @param param
* @return
*/
List<PermissionRole> listPermissionRole(ListPermissionRoleParam param);
/**
* 缓存权限码跟产品和单位类型的信息采用set数据结构
* redisKey: featureCode
* redisValue: 产品信息
* @param param
*/
void cachePermissionProduct(CachePermissionProductParam param);
/**
* 根据权限码查询对应的产品信息
* 一个权限点对应的产品数据比较少一般10多个所以没有聚合返回方便排查哪些权限点已经在redis中不用从数据库中查询
* @param param
* @return
*/
List<PermissionProduct> listPermissionProduct(ListPermissionProductParam param);
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class HasPermissionRoleParam {
private List<String> featureCodes;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class ListPermissionRoleParam {
private List<String> featureCodes;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class CachePermissionRoleParam {
private List<PermissionRole> permissionRoles;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class PermissionRole {
/**
* 权限码
*/
private String featureCode;
/**
* 角色id
*/
private List<Long> roleIds;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class ListPermissionProductParam {
private List<String> featureCodes;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class CachePermissionProductParam {
private List<PermissionProduct> permissionProducts;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class PermissionProduct {
/**
* 权限码
*/
private String featureCode;
/**
* 产品信息
*/
private List<Product> products;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Product {
/**
* 产品id
*/
private Long productId;
/**
* 单位类型
*/
private Integer ouType;
}
}

View File

@ -0,0 +1,68 @@
package cn.axzo.tyr.server.service;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
public interface ProductPermissionCacheService {
List<PermissionDTO> list(ListProductPermissionParam param);
void store(StoreProductPermissionParam param);
List<Long> hasProductIds(HasProductPermissionParam param);
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class HasProductPermissionParam {
private List<Long> productIds;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class StoreProductPermissionParam {
private List<ProductPermission> productPermissions;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class ProductPermission {
private Long productId;
private List<PermissionDTO> permissions;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class ListProductPermissionParam {
private List<Long> productIds;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class PermissionDTO {
/**
* 产品关联的字典 Code 原值
*/
private String dictCode;
private Long featureId;
private String featureCode;
}
}

View File

@ -0,0 +1,84 @@
package cn.axzo.tyr.server.service;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Set;
public interface RolePermissionCacheService {
List<PermissionDTO> list(ListRolePermissionParam param);
/**
* redisKey:roleId
* redisValue:permission
* @param param
*/
void store(StoreRolePermissionParam param);
List<Long> hasRoleIds(HasRolePermissionParam param);
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class HasRolePermissionParam {
private List<Long> roleIds;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class StoreRolePermissionParam {
private List<RolePermission> rolePermissions;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class RolePermission {
private Long roleId;
private List<PermissionDTO> permissions;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class ListRolePermissionParam {
private List<Long> roleIds;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class RolePermissionDTO {
private Long roleId;
private List<PermissionDTO> permissions;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class PermissionDTO {
private Long featureId;
private String featureCode;
/**
* 资源所属端
*/
private String terminal;
}
}

View File

@ -159,6 +159,9 @@ public interface RoleService extends IService<SaasRole> {
@CriteriaField(field = "id", operator = Operator.NE)
private Long idNE;
/**
* 权限点从saas_feature_resource表查询
*/
@CriteriaField(ignore = true)
private Boolean needPermission;
@ -167,6 +170,19 @@ public interface RoleService extends IService<SaasRole> {
@CriteriaField(ignore = true)
private Boolean needRoleUser;
/**
* 当前非oms和政务端的权限存储在saas_feature
* 权限点从saas_feature表查询
*/
@CriteriaField(ignore = true)
private Boolean needPermissionOld;
/**
* 根据权限点id过滤
*/
@CriteriaField(ignore = true)
private List<Long> featureIds;
}
@SuperBuilder

View File

@ -8,6 +8,7 @@ import cn.axzo.tyr.client.model.req.ListIdentityFromPermissionReq;
import cn.axzo.tyr.client.model.req.ListPermissionFromFeatureReq;
import cn.axzo.tyr.client.model.req.ListPermissionFromIdentityReq;
import cn.axzo.tyr.client.model.req.ListPermissionFromRoleGroupReq;
import cn.axzo.tyr.client.model.req.PermissionCheckReq;
import cn.axzo.tyr.client.model.req.WorkspacePermissionIdentityReq;
import cn.axzo.tyr.client.model.res.IdentityAuthRes;
import cn.axzo.tyr.client.model.res.ListIdentityFromPermissionResp;
@ -51,4 +52,11 @@ public interface TyrSaasAuthService {
* @return
*/
List<ListPermissionFromRoleGroupResp> listAuthByResourceAndRoleGroup(ListPermissionFromRoleGroupReq listPermissionFromRoleGroupReq);
/**
* 接口鉴权
* @param req
* @return
*/
boolean authPermission(PermissionCheckReq req);
}

View File

@ -1,20 +1,32 @@
package cn.axzo.tyr.server.service.impl;
import cn.axzo.pokonyan.config.redis.RedisClient;
import cn.axzo.pokonyan.config.redis.RedisUtil;
import cn.axzo.tyr.client.model.res.IdentityAuthRes;
import cn.axzo.tyr.server.model.PermissionCacheKey;
import cn.axzo.tyr.server.service.PermissionCacheService;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Streams;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 授权缓存服务实现
@ -35,7 +47,10 @@ public class PermissionCacheServiceImpl implements PermissionCacheService {
/** 授权缓存过期时间 **/
@Value("${axzo.cache.auth.expire:30}")
private Long expireInMinutes;
@Autowired
protected StringRedisTemplate redisTemplate;
private static final String PERMISSION_ROLE_KEY = "permission:role:%s";
private static final String PERMISSION_PRODUCT_KEY = "permission:product:%s";
@Override
public boolean cacheDisable(PermissionCacheKey key) {
@ -95,4 +110,111 @@ public class PermissionCacheServiceImpl implements PermissionCacheService {
log.error("mark permission refresh error", ex);
}
}
@Override
public void cachePermissionRole(CachePermissionRoleParam param) {
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (PermissionRole permissionRole : param.getPermissionRoles()) {
String redisKey = getKey(PERMISSION_ROLE_KEY, permissionRole.getFeatureCode());
String[] redisValues = permissionRole.getRoleIds().stream().toArray(String[]::new);
RedisClient.SetOps.sAdd(redisKey, redisValues);
redisTemplate.expire(redisKey, 4, TimeUnit.DAYS);
log.info("succeed to store permission role: redisKey:{} value:{}", redisKey, redisValues);
}
return null;
}
});
}
@Override
public List<PermissionRole> listPermissionRole(ListPermissionRoleParam param) {
if (CollectionUtils.isEmpty(param.getFeatureCodes())) {
return Collections.emptyList();
}
List<Object> redisValues = redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (String featureCode : param.getFeatureCodes()) {
String redisKey = getKey(PERMISSION_ROLE_KEY, featureCode);
operations.opsForSet().members(redisKey);
}
return null;
}
});
return Streams.zip(param.getFeatureCodes().stream(),
redisValues.stream(),
(featureCode, redisValue) -> {
PermissionRole permissionProduct = PermissionRole.builder()
.featureCode(featureCode)
.roleIds(JSONArray.parseArray(JSONArray.toJSONString(redisValue), Long.class))
.build();
return permissionProduct;
})
.collect(Collectors.toList());
}
@Override
public void cachePermissionProduct(CachePermissionProductParam param) {
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (PermissionProduct permissionProduct : param.getPermissionProducts()) {
String redisKey = getKey(PERMISSION_PRODUCT_KEY, permissionProduct.getFeatureCode());
String[] redisValues = permissionProduct.getProducts().stream()
.map(JSONObject::toJSONString)
.toArray(String[]::new);
RedisClient.SetOps.sAdd(redisKey, redisValues);
redisTemplate.expire(redisKey, 4, TimeUnit.DAYS);
log.info("succeed to store permission product: redisKey:{} value:{}", redisKey, redisValues);
}
return null;
}
});
}
@Override
public List<PermissionProduct> listPermissionProduct(ListPermissionProductParam param) {
if (CollectionUtils.isEmpty(param.getFeatureCodes())) {
return Collections.emptyList();
}
List<Object> redisValues = redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (String featureCode : param.getFeatureCodes()) {
String redisKey = getKey(PERMISSION_PRODUCT_KEY, featureCode);
operations.opsForSet().members(redisKey);
}
return null;
}
});
return Streams.zip(param.getFeatureCodes().stream(),
redisValues.stream(),
(featureCode, redisValue) -> {
PermissionProduct permissionProduct = PermissionProduct.builder()
.featureCode(featureCode)
.products(JSONArray.parseArray(JSONArray.toJSONString(redisValue), Product.class))
.build();
return permissionProduct;
})
.collect(Collectors.toList());
}
private String getKey(String pref, Object... params) {
return String.format(pref, params);
}
}

View File

@ -50,6 +50,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@ -74,6 +76,7 @@ import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
@RefreshScope
public class PermissionQueryServiceImpl implements PermissionQueryService {
private final SaasFeatureResourceService featureResourceService;
@ -84,6 +87,8 @@ public class PermissionQueryServiceImpl implements PermissionQueryService {
private final ProductModuleDao productModuleDao;
private final ProductFeatureRelationService productFeatureRelationService;
@Value("${use.old.auth:true}")
private boolean USE_OLD_AUTH;
@Override
public List<NavTreeResp> getNavTree(NavTreeReq req) {
//构造参数
@ -130,7 +135,10 @@ public class PermissionQueryServiceImpl implements PermissionQueryService {
//这里暂时硬编码-非OMS端鉴权请求 直接转老接口处理
if (!StrUtil.equals("NT_OMS_WEB" ,req.getTerminal())
&& !Objects.equals(TerminalInfo.NT_PC_GA_GENERAL, req.getTerminal())) {
return hasPermissionV2(req);
if (USE_OLD_AUTH) {
return hasPermissionV2(req);
}
return saasAuthService.authPermission(req);
}
//权限编码转ID
List<ResourcePermission> resourcePermissions = featureResourceService.permissionQuery(

View File

@ -0,0 +1,106 @@
package cn.axzo.tyr.server.service.impl;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.pokonyan.config.redis.RedisClient;
import cn.axzo.tyr.server.service.ProductPermissionCacheService;
import cn.hutool.core.lang.Pair;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Streams;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static cn.axzo.tyr.server.config.exception.BizResultCode.REDIS_PRODUCT_NOT_NULL;
@Slf4j
@Service
public class ProductPermissionCacheServiceImpl implements ProductPermissionCacheService {
private static final String PRODUCT_PERMISSION_KEY = "product:permission:%s";
@Autowired
protected StringRedisTemplate redisTemplate;
@Override
public List<PermissionDTO> list(ListProductPermissionParam param) {
if (CollectionUtils.isEmpty(param.getProductIds())) {
return Collections.emptyList();
}
Set<String> redisKeys = param.getProductIds().stream()
.map(this::getKey)
.collect(Collectors.toSet());
Set<String> redisValues = redisTemplate.opsForSet().union(redisKeys);
return redisValues.stream()
.filter(StringUtils::isNotBlank)
.map(value -> JSONObject.parseObject(value, PermissionDTO.class))
.collect(Collectors.toList());
}
@Override
public void store(StoreProductPermissionParam param) {
Axssert.check(param.getProductPermissions().stream().allMatch(e -> Objects.nonNull(e.getProductId())), REDIS_PRODUCT_NOT_NULL);
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (ProductPermission productPermission : param.getProductPermissions()) {
String redisKey = getKey(productPermission.getProductId());
String[] redisValues = productPermission.getPermissions().stream()
.map(JSONObject::toJSONString)
.toArray(String[]::new);
RedisClient.SetOps.sAdd(redisKey, redisValues);
redisTemplate.expire(redisKey, 4, TimeUnit.DAYS);
log.info("succeed to store product permission: redisKey:{} value:{}", redisKey, redisValues);
}
return null;
}
});
}
@Override
public List<Long> hasProductIds(HasProductPermissionParam param) {
List<Object> redisValues = redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (Long productId : param.getProductIds()) {
String redisKey = getKey(productId);
operations.hasKey(redisKey);
}
return null;
}
});
return Streams.zip(param.getProductIds().stream(),
redisValues.stream(),
(productId, redisValue) -> Pair.of(productId, redisValue))
.filter(e -> BooleanUtils.isTrue((Boolean) e.getValue()))
.map(Pair::getKey)
.collect(Collectors.toList());
}
private String getKey(Object... params) {
return String.format(PRODUCT_PERMISSION_KEY, params);
}
}

View File

@ -0,0 +1,106 @@
package cn.axzo.tyr.server.service.impl;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.pokonyan.config.redis.RedisClient;
import cn.axzo.tyr.server.service.RolePermissionCacheService;
import cn.hutool.core.lang.Pair;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Streams;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static cn.axzo.tyr.server.config.exception.BizResultCode.REDIS_ROLE_NOT_NULL;
@Slf4j
@Service
public class RolePermissionCacheServiceImpl implements RolePermissionCacheService {
private static final String ROLE_PERMISSION_KEY = "role:permission:%s";
@Autowired
protected StringRedisTemplate redisTemplate;
@Override
public List<PermissionDTO> list(ListRolePermissionParam param) {
if (CollectionUtils.isEmpty(param.getRoleIds())) {
return Collections.emptyList();
}
Set<String> redisKeys = param.getRoleIds().stream()
.map(this::getKey)
.collect(Collectors.toSet());
// 聚合是因为角色的权限点有重复网络io压力大
Set<String> redisValues = redisTemplate.opsForSet().union(redisKeys);
return redisValues.stream()
.filter(StringUtils::isNotBlank)
.map(value -> JSONObject.parseObject(value, PermissionDTO.class))
.collect(Collectors.toList());
}
@Override
public void store(StoreRolePermissionParam param) {
Axssert.check(param.getRolePermissions().stream().allMatch(e -> Objects.nonNull(e.getRoleId())), REDIS_ROLE_NOT_NULL);
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
connection.openPipeline();
for (RolePermission rolePermission : param.getRolePermissions()) {
String redisKey = getKey(rolePermission.getRoleId());
String[] redisValues = rolePermission.getPermissions().stream()
.map(JSONObject::toJSONString)
.toArray(String[]::new);
RedisClient.SetOps.sAdd(redisKey, redisValues);
redisTemplate.expire(redisKey, 4, TimeUnit.DAYS);
log.info("succeed to store role permission: redisKey:{} value:{}", redisKey, redisValues);
}
return null;
});
}
@Override
public List<Long> hasRoleIds(HasRolePermissionParam param) {
List<Object> redisValues = redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (Long roleId : param.getRoleIds()) {
String redisKey = getKey(roleId);
operations.hasKey(redisKey);
}
return null;
}
});
return Streams.zip(param.getRoleIds().stream(),
redisValues.stream(),
(roleId, redisValue) -> Pair.of(roleId, redisValue))
.filter(e -> BooleanUtils.isTrue((Boolean) e.getValue()))
.map(Pair::getKey)
.collect(Collectors.toList());
}
private String getKey(Object... params) {
return String.format(ROLE_PERMISSION_KEY, params);
}
}

View File

@ -1182,11 +1182,13 @@ public class RoleServiceImpl extends ServiceImpl<SaasRoleMapper, SaasRole>
Map<Long, List<SaasPermissionRes>> saasPermissions = listRolePermissions(param, page.getRecords());
Map<Long, List<SaasPermissionRes>> saasPermissionsOld = listRolePermissionsOld(param, page.getRecords());
Map<Long, List<SaasRoleUserV2DTO.SaasRoleUser>> saasRoleUsers = listSaasRoleUser(param, page.getRecords());
return PageConverter.toResp(page, (record) -> from(record,
saasRoleGroups,
saasPermissions,
BooleanUtils.isTrue(param.getNeedPermissionOld()) ? saasPermissionsOld : saasPermissions,
saasRoleUsers));
}
@ -1410,4 +1412,68 @@ public class RoleServiceImpl extends ServiceImpl<SaasRoleMapper, SaasRole>
.map(e -> Pair.of(e.getRoleId(), e.getSaasRoleUser()))
.collect(Collectors.groupingBy(Pair::getKey, Collectors.mapping(Pair::getValue, Collectors.toList())));
}
/**
* 待所有端的权限点都迁移到saas_feature_resource后就删除
* @param param
* @param saasRoles
* @return
*/
private Map<Long, List<SaasPermissionRes>> listRolePermissionsOld(PageSaasRoleParam param,
List<SaasRole> saasRoles) {
if (CollectionUtils.isEmpty(saasRoles) || BooleanUtils.isNotTrue(param.getNeedPermissionOld())) {
return Collections.emptyMap();
}
List<SaasPgroupRoleRelation> saasPgroupRoleRelations = saasPgroupRoleRelationDao.findByRoleIds(Lists.transform(saasRoles, SaasRole::getId));
if (CollectionUtils.isEmpty(saasPgroupRoleRelations)) {
return Collections.emptyMap();
}
List<SaasPgroupPermissionRelation> saasPgroupPermissionRelations = saasPgroupPermissionRelationDao.lambdaQuery()
.in(SaasPgroupPermissionRelation::getGroupId, Lists.transform(saasPgroupRoleRelations, SaasPgroupRoleRelation::getGroupId))
.in(CollectionUtils.isNotEmpty(param.getFeatureIds()), SaasPgroupPermissionRelation::getFeatureId, param.getFeatureIds())
.eq(SaasPgroupPermissionRelation::getIsDelete, TableIsDeleteEnum.NORMAL.value)
.list();
if (CollectionUtils.isEmpty(saasPgroupPermissionRelations)) {
return Collections.emptyMap();
}
List<Long> featureIds = Lists.transform(saasPgroupPermissionRelations, SaasPgroupPermissionRelation::getFeatureId);
Map<Long, SaasPermissionRes> resourcePermissions = saasFeatureDao.listByIds(featureIds).stream()
.map(e -> SaasPermissionRes.builder()
.id(e.getId())
.featureCode(e.getFeatureCode())
.terminal(e.getTerminal())
.build())
.collect(Collectors.toMap(SaasPermissionRes::getId, Function.identity()));
Map<Long, List<Long>> pgroupPermissions = saasPgroupPermissionRelations.stream()
.collect(Collectors.groupingBy(SaasPgroupPermissionRelation::getGroupId,
Collectors.mapping(SaasPgroupPermissionRelation::getFeatureId, Collectors.toList())));
return saasPgroupRoleRelations.stream()
.map(e -> {
List<Long> permissionIds = pgroupPermissions.get(e.getGroupId());
if (CollectionUtils.isEmpty(permissionIds)) {
return null;
}
return permissionIds.stream()
.map(permissionId -> {
SaasPermissionRes saasPermissionRes = resourcePermissions.get(permissionId);
return SaasPermissionWrapper.from(e, saasPermissionRes);
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
})
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(SaasPermissionWrapper::getRoleId,
Collectors.mapping(e -> SaasPermissionRes.builder().id(e.getId()).featureCode(e.getFeatureCode()).build(),
Collectors.toList())));
}
}

View File

@ -19,6 +19,7 @@ import cn.axzo.tyr.client.model.roleuser.req.AutoOwnRoleUserReq;
import cn.axzo.tyr.client.model.roleuser.req.CreateSuperAdminRoleParam;
import cn.axzo.tyr.client.model.roleuser.req.GantOrUnGantaWorkerLeaderRoleReq;
import cn.axzo.tyr.client.model.roleuser.req.GetUserAutoOwnRoleReq;
import cn.axzo.tyr.client.model.roleuser.req.GetUserFeatureResourceIdsReq;
import cn.axzo.tyr.client.model.roleuser.req.RoleUserReq;
import cn.axzo.tyr.client.model.roleuser.req.SuperAdminParam;
import cn.axzo.tyr.client.model.roleuser.req.WorkerManagerRoleUserReq;

View File

@ -19,6 +19,7 @@ import cn.axzo.tyr.server.repository.dao.SaasRoleUserRelationDao;
import cn.axzo.tyr.server.repository.entity.SaasRole;
import cn.axzo.tyr.server.repository.entity.SaasRoleUserRelation;
import cn.axzo.tyr.server.repository.mapper.SaasRoleUserRelationMapper;
import cn.axzo.tyr.server.service.RoleService;
import cn.axzo.tyr.server.service.SaasRoleUserRelationService;
import cn.axzo.tyr.server.util.RpcInternalUtil;
import cn.hutool.core.bean.BeanUtil;
@ -61,6 +62,8 @@ public class SaasRoleUserRelationServiceImpl extends ServiceImpl<SaasRoleUserRel
@Autowired
private UserProfileServiceApi userProfileServiceApi;
@Autowired
private RoleService roleService;
@Override
public List<SaasRoleUserDTO> list(RoleUserParam param) {
@ -141,11 +144,13 @@ public class SaasRoleUserRelationServiceImpl extends ServiceImpl<SaasRoleUserRel
Map<Long, SaasRoleUserV2DTO.SaasRoleUser> saasRoleUsers = listSaasRoleUser(param, page.getRecords());
return PageConverter.toResp(page, (record) -> from(record, saasRoleUsers));
Map<Long, SaasRoleUserV2DTO.SaasRole> saasRoles = listSaasRole(param, page.getRecords());
return PageConverter.toResp(page, (record) -> from(record, saasRoleUsers, saasRoles));
}
private Map<Long, SaasRoleUserV2DTO.SaasRoleUser> listSaasRoleUser(PageRoleUserRelationParam param,
List<SaasRoleUserRelation> saasRoleUserRelations) {
List<SaasRoleUserRelation> saasRoleUserRelations) {
if (CollectionUtils.isEmpty(saasRoleUserRelations) || BooleanUtils.isNotTrue(param.getNeedUsers())) {
return Collections.emptyMap();
}
@ -182,11 +187,38 @@ public class SaasRoleUserRelationServiceImpl extends ServiceImpl<SaasRoleUserRel
}
private SaasRoleUserV2DTO from(SaasRoleUserRelation saasRoleUserRelation,
Map<Long, SaasRoleUserV2DTO.SaasRoleUser> saasRoleUsers) {
Map<Long, SaasRoleUserV2DTO.SaasRoleUser> saasRoleUsers,
Map<Long, SaasRoleUserV2DTO.SaasRole> saasRoles) {
return SaasRoleUserV2DTO.builder()
.roleId(saasRoleUserRelation.getRoleId())
.saasRoleUser(saasRoleUsers.get(saasRoleUserRelation.getNaturalPersonId()))
.saasRole(saasRoles.get(saasRoleUserRelation.getRoleId()))
.build();
}
private Map<Long, SaasRoleUserV2DTO.SaasRole> listSaasRole(PageRoleUserRelationParam param,
List<SaasRoleUserRelation> saasRoleUserRelations) {
if (CollectionUtils.isEmpty(saasRoleUserRelations) || BooleanUtils.isNotTrue(param.getNeedRole())) {
return Collections.emptyMap();
}
List<Long> roleIds = saasRoleUserRelations.stream()
.map(SaasRoleUserRelation::getRoleId)
.distinct()
.collect(Collectors.toList());
RoleService.ListSaasRoleParam listSaasRoleParam = RoleService.ListSaasRoleParam.builder()
.roleIds(roleIds)
.needPermissionOld(param.getNeedRolePermissionOld())
.featureIds(param.getFeatureIds())
.build();
return roleService.list(listSaasRoleParam).stream()
.map(e -> {
SaasRoleUserV2DTO.SaasRole saasRole = SaasRoleUserV2DTO.SaasRole.builder().build();
BeanUtils.copyProperties(e, saasRole);
return saasRole;
})
.collect(Collectors.toMap(SaasRoleUserV2DTO.SaasRole::getId, Function.identity()));
}
}

View File

@ -1,9 +1,8 @@
package cn.axzo.tyr.server.service.impl;
import cn.axzo.basics.common.BeanMapper;
import cn.axzo.basics.common.constant.enums.TableIsDeleteEnum;
import cn.axzo.framework.domain.ServiceException;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.pokonyan.config.mybatisplus.BaseEntity;
import cn.axzo.pokonyan.util.TraceSupplier;
import cn.axzo.thrones.client.saas.ServicePkgClient;
import cn.axzo.thrones.client.saas.entity.serivicepgkproduct.ServicePkgProduct;
@ -15,40 +14,61 @@ import cn.axzo.tyr.client.model.enums.IdentityType;
import cn.axzo.tyr.client.model.permission.PermissionPointListQueryRequest;
import cn.axzo.tyr.client.model.permission.PermissionPointTreeNode;
import cn.axzo.tyr.client.model.product.ProductFeatureRelationVO;
import cn.axzo.tyr.client.model.req.*;
import cn.axzo.tyr.client.model.req.CheckIdentityPermissionReq;
import cn.axzo.tyr.client.model.req.IdentityAuthReq;
import cn.axzo.tyr.client.model.req.ListIdentityFromPermissionReq;
import cn.axzo.tyr.client.model.req.ListPermissionFromFeatureReq;
import cn.axzo.tyr.client.model.req.ListPermissionFromIdentityReq;
import cn.axzo.tyr.client.model.req.ListPermissionFromRoleGroupReq;
import cn.axzo.tyr.client.model.req.OUWorkspacePair;
import cn.axzo.tyr.client.model.req.PermissionCheckReq;
import cn.axzo.tyr.client.model.req.QueryPermissionByIdsReq;
import cn.axzo.tyr.client.model.req.QuerySaasRoleReq;
import cn.axzo.tyr.client.model.req.WorkspacePermissionIdentityReq;
import cn.axzo.tyr.client.model.res.IdentityAuthRes;
import cn.axzo.tyr.client.model.res.ListIdentityFromPermissionResp;
import cn.axzo.tyr.client.model.res.ListPermissionFromRoleGroupResp;
import cn.axzo.tyr.client.model.res.QueryIdentityByPermissionResp;
import cn.axzo.tyr.client.model.res.SimpleFeatureInfo;
import cn.axzo.tyr.client.model.res.SaasPermissionRes;
import cn.axzo.tyr.client.model.res.SaasRoleRes;
import cn.axzo.tyr.client.model.res.SimplePermissionPointResp;
import cn.axzo.tyr.client.model.roleuser.dto.SaasRoleUserV2DTO;
import cn.axzo.tyr.client.model.roleuser.req.ListRoleUserRelationParam;
import cn.axzo.tyr.client.model.vo.SaasPermissionGroupVO;
import cn.axzo.tyr.client.model.roleuser.dto.SuperAminInfoResp;
import cn.axzo.tyr.client.model.roleuser.req.SuperAdminParam;
import cn.axzo.tyr.client.model.vo.SaasRoleVO;
import cn.axzo.tyr.server.model.FilterRoleAuth;
import cn.axzo.tyr.server.model.PermissionCacheKey;
import cn.axzo.tyr.server.repository.entity.*;
import cn.axzo.tyr.server.repository.dao.SaasFeatureDao;
import cn.axzo.tyr.server.repository.dao.SaasProductModuleFeatureRelationDao;
import cn.axzo.tyr.server.repository.entity.ProductFeatureInfo;
import cn.axzo.tyr.server.repository.entity.ProductFeatureQuery;
import cn.axzo.tyr.server.repository.entity.RolePermission;
import cn.axzo.tyr.server.repository.entity.SaasFeature;
import cn.axzo.tyr.server.repository.entity.SaasProductModuleFeatureRelation;
import cn.axzo.tyr.server.repository.entity.SaasRole;
import cn.axzo.tyr.server.repository.entity.SaasRoleGroup;
import cn.axzo.tyr.server.repository.entity.SaasRoleUserRelation;
import cn.axzo.tyr.server.repository.entity.SaasRoleWithUser;
import cn.axzo.tyr.server.repository.mapper.TyrSaasAuthMapper;
import cn.axzo.tyr.server.service.PermissionCacheService;
import cn.axzo.tyr.server.service.PermissionPointService;
import cn.axzo.tyr.server.service.ProductFeatureRelationService;
import cn.axzo.tyr.server.service.RoleService;
import cn.axzo.tyr.server.service.SaasRoleGroupService;
import cn.axzo.tyr.server.service.SaasRoleUserRelationService;
import cn.axzo.tyr.server.service.TyrSaasAuthService;
import cn.axzo.tyr.server.util.KeyUtil;
import cn.axzo.tyr.server.utils.RpcExternalUtil;
import cn.axzo.tyr.server.utils.RpcInternalUtil;
import cn.azxo.framework.common.model.CommonResponse;
import cn.azxo.framework.common.utils.LogUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -59,16 +79,26 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.axzo.tyr.server.util.RpcInternalUtil.checkAndGetData;
import static cn.axzo.tyr.server.util.RpcInternalUtil.rpcListProcessor;
/**
* @author tanjie@axzo.cn
@ -94,7 +124,9 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
private final PermissionCacheService permissionCacheService;
private final SaasRoleGroupService roleGroupService;
private final SaasRoleUserRelationService saasRoleUserRelationService;
private final SaasFeatureDao saasFeatureDao;
private final SaasProductModuleFeatureRelationDao saasProductModuleFeatureRelationDao;
/**
* 通过身份查询人员权限
@ -317,7 +349,10 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
private IdentityAuthRes findIdentityAuth(IdentityAuthReq identityAuthReq) {
//用户角色关系
List<SaasRoleUserRelation> saasRoleUserRelations = listRoleUserRelations(identityAuthReq);
if (CollectionUtils.isEmpty(saasRoleUserRelations)) {
log.warn("no user role relations found");
return identityAuthReq.toEmpty();
}
Set<Long> realWorkspaceId = saasRoleUserRelations.stream().map(SaasRoleUserRelation::getWorkspaceId).collect(Collectors.toSet());
//工作台对应产品 key = workspaceId
@ -363,7 +398,7 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
return resultPermission;
}
Set<SaasRoleVO> roles = ouwRoleInfo.getRoles();
Set<SaasRoleRes> roles = ouwRoleInfo.getRoles();
if (CollectionUtil.isEmpty(roles)) {
log.warn("no roles for ou:{} workspace:{}", ouwRoleInfo.getOuId(), ouwRoleInfo.getWorkspaceId());
return resultPermission;
@ -404,17 +439,17 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
Set<Long> allMatchedProductFeatureIds = new HashSet<>();
Set<Long> allAuthPermissionIds = new HashSet<>();
//聚合实际授权的权限角色权限和产品权限交集
for (SaasRoleVO role : userRoleInfoMap.getRoles()) {
for (SaasRoleRes role : userRoleInfoMap.getRoles()) {
//跳过超管和管理员
if (RoleTypeEnum.SUPER_ADMIN.getValue().equals(role.getRoleType())
|| RoleTypeEnum.ADMIN.getValue().equals(role.getRoleType())) {
continue;
}
log.info("build permission for role:{}", role.getId());
Set<Long> rolePermissionIds = role.getMatchFeature(userRoleInfoMap.getWorkspaceId(), userRoleInfoMap.ouId)
.stream()
Set<Long> rolePermissionIds = role.getSaasPermissions().stream()
.filter(Objects::nonNull)
.map(PermissionPointTreeNode::getPermissionPointId)
.map(SaasPermissionRes::getId)
.collect(Collectors.toSet());
//角色标签类型匹配产品标签类型
Set<Long> productPermissionIds = productFeatures.stream()
@ -446,9 +481,8 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
private Pair<Boolean, Set<Long>> buildAdminPermission(OUWRoleInfo userRoleInfoMap, List<ProductFeatureRelationVO> productFeatures) {
Boolean superAdmin = false;
//超管和管理员角色
List<SaasRoleVO> adminRoles = userRoleInfoMap.getRoles().stream()
.filter(r -> RoleTypeEnum.SUPER_ADMIN.getValue().equals(r.getRoleType())
|| RoleTypeEnum.ADMIN.getValue().equals(r.getRoleType()))
List<SaasRoleRes> adminRoles = userRoleInfoMap.getRoles().stream()
.filter(r -> RoleTypeEnum.isAdmin(r.getRoleType()))
.collect(Collectors.toList());
if (CollectionUtil.isEmpty(adminRoles)) {
log.info("no admin roles");
@ -459,7 +493,7 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
//聚合超管和管理员的权限点 直接取角色标签和产品标签相匹配的权限点
Set<Long> permissionIds = new HashSet<>();
for (SaasRoleVO adminRole : adminRoles) {
for (SaasRoleRes adminRole : adminRoles) {
//超管查询工作台对应产品获取权限点 权限点通过单位类型过滤)
if (RoleTypeEnum.SUPER_ADMIN.getValue().equals(adminRole.getRoleType())) {
superAdmin = true;
@ -485,14 +519,10 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
private List<OUWRoleInfo> listRolesWithPermission(List<SaasRoleUserRelation> roleUserRelations, IdentityAuthReq identityAuthReq) {
//拼装参数
Set<Long> realWorkspaceIds = new HashSet<>();
Set<Long> realOuIds = new HashSet<>();
Set<Long> roleIds = new HashSet<>();
//按ow分组角色ID: workspaceId-ouId --> roleIds
Map<String, Set<Long>> owRoleIdMap = new HashMap<>();
for (SaasRoleUserRelation relation : roleUserRelations) {
realWorkspaceIds.add(relation.getWorkspaceId());
realOuIds.add(relation.getOuId());
roleIds.add(relation.getRoleId());
String key = KeyUtil.buildKeyBySeparator(relation.getWorkspaceId(), relation.getOuId());
Set<Long> owRoleIds = owRoleIdMap.getOrDefault(key, new HashSet<>());
@ -500,13 +530,12 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
owRoleIdMap.put(key, owRoleIds);
}
//获取角色和关联权限信息
List<SaasRoleVO> roles = roleService.query(QuerySaasRoleReq.builder()
//角色ID
.ids(new ArrayList<>(roleIds))
.workspaceId(new ArrayList<>(realWorkspaceIds))
.ouId(new ArrayList<>(realOuIds))
.includePermissionGroup(true)
.build());
RoleService.ListSaasRoleParam listSaasRoleParam = RoleService.ListSaasRoleParam.builder()
.roleIds(Lists.newArrayList(roleIds))
.needPermissionOld(true)
.build();
Map<Long, SaasRoleRes> saasRoleRes = roleService.list(listSaasRoleParam).stream()
.collect(Collectors.toMap(SaasRoleRes::getId, Function.identity()));
//按ow组装拥有的角色
List<OUWRoleInfo> owRoleMap = new ArrayList<>();
@ -521,8 +550,8 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
log.info("no roles found for ow:{}", key);
owRoleInfo.setRoles(Collections.emptySet());
} else {
owRoleInfo.setRoles(roles.stream()
.filter(r -> owRoleIds.contains(r.getId()))
owRoleInfo.setRoles(owRoleIds.stream()
.map(saasRoleRes::get)
.collect(Collectors.toSet()));
}
owRoleMap.add(owRoleInfo);
@ -888,7 +917,7 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
Integer workspaceType;
Long ouId;
WorkspaceJoinType workspaceJoinType;
Set<SaasRoleVO> roles = new HashSet<>();
Set<SaasRoleRes> roles = new HashSet<>();
}
@ -1006,4 +1035,195 @@ public class TyrSaasAuthServiceImpl implements TyrSaasAuthService {
return new ArrayList<>(distinctMap.values());
}
/**
* 判断用户是否有指定权限码的权限
* 1查询用户的角色id租户的产品id(db
* 2根据权限点找对应的产品单位类型(redis)
* 3租户开通的产品是否在权限点对应的产品不满足条件直接返回false
* 4查询是否有免授权的权限点
* 4有管理员角色租户的产品要在权限点的产品里单位类型要是管理员角色的单位类型满足条件则返回true
* 6根据权限点找对应的角色(redis)
* 7有非管理员角色
* @param req
* @return
*/
public boolean authPermission(PermissionCheckReq req) {
// saas_feature表会被废弃所以直接查询没提供统一的查询
List<SaasFeature> saasFeatures = saasFeatureDao.lambdaQuery()
.in(SaasFeature::getFeatureCode, req.getFeatureCodes())
.eq(SaasFeature::getIsDelete, TableIsDeleteEnum.NORMAL.value)
.list();
if (CollectionUtils.isEmpty(saasFeatures)) {
log.info("featureCode not found:{}", req.getFeatureCodes());
return false;
}
//用户角色关系以及对应角色的权限点
List<SaasRoleUserV2DTO> saasRoleUserRelations = listRoleUserRelations(req, saasFeatures);
if (CollectionUtils.isEmpty(saasRoleUserRelations)) {
return false;
}
// 查询租户开通的所有产品
Set<Long> productIds = listProducts(req);
if (CollectionUtils.isEmpty(productIds)) {
log.info("product not found:{}", req.getWorkspaceId());
return false;
}
// 查询产品开通的这些权限点的信息
List<SaasProductModuleFeatureRelation> permissionProducts = listPermissionProduct(saasFeatures, productIds);
if (CollectionUtils.isEmpty(productIds)) {
log.info("permission product not found:{}", req.getWorkspaceId());
return false;
}
// 是否有免授权的权限码且在租户开通了这个产品
boolean matchedNoNeedAuthFeature = matchNoAuthFeature(saasFeatures, permissionProducts);
if (BooleanUtil.isTrue(matchedNoNeedAuthFeature)) {
log.info("has no need auth feature:{}", req.getWorkspaceId());
return true;
}
// 是否有管理员角色且租户开通了管理员角色的单位类型对应的产品权限码
boolean matchedAdminRole = matchAdminRole(saasRoleUserRelations, permissionProducts);
if (BooleanUtil.isTrue(matchedAdminRole)) {
log.info("admin role has permission:{}", req.getWorkspaceId());
return true;
}
return matchNormalRole(saasRoleUserRelations, permissionProducts);
}
private boolean matchNormalRole(List<SaasRoleUserV2DTO> saasRoleUserRelations,
List<SaasProductModuleFeatureRelation> permissionProducts) {
List<SaasRoleUserV2DTO> normalRoles = saasRoleUserRelations.stream()
.filter(e -> !RoleTypeEnum.isAdmin(e.getSaasRole().getRoleType()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(normalRoles)) {
return false;
}
// 权限点对应角色的单位类型要与权限点对应产品的单位类型一致才能算有权限
Map<Long, SaasProductModuleFeatureRelation> permissionProductMap = permissionProducts.stream()
.collect(Collectors.toMap(SaasProductModuleFeatureRelation::getFeatureId, Function.identity()));
for (SaasRoleUserV2DTO permissionRole : normalRoles) {
Set<String> ouTypesOfProduct = permissionRole.getSaasRole().getSaasPermissions().stream()
.map(permissionProductMap::get)
.filter(Objects::nonNull)
.map(SaasProductModuleFeatureRelation::getDictCode)
.collect(Collectors.toSet());
if (CollectionUtils.isEmpty(ouTypesOfProduct)) {
continue;
}
if (ouTypesOfProduct.contains(String.valueOf(permissionRole.getSaasRole().getProductUnitType()))) {
return true;
}
}
return false;
}
/**
* 租户开通的产品是否有不需要鉴权的权限码
* @param saasFeatures
* @param permissionProducts
* @return
*/
private boolean matchNoAuthFeature(List<SaasFeature> saasFeatures,
List<SaasProductModuleFeatureRelation> permissionProducts) {
Set<Long> noNeedAuthFeatureIds = saasFeatures.stream()
.filter(e -> Objects.equals(e.getDelegatedType(), DelegatedType.NO_NEED.getCode()))
.map(SaasFeature::getId)
.collect(Collectors.toSet());
if (CollectionUtils.isEmpty(noNeedAuthFeatureIds)) {
log.info("not found no need auth featureCode");
return false;
}
return permissionProducts.stream()
.anyMatch(e -> noNeedAuthFeatureIds.contains(e.getFeatureId()));
}
/**
* 匹配管理员角色是否有权限点的权限
* @param saasRoleUserRelations
* @param permissionProducts
* @return
*/
private boolean matchAdminRole(List<SaasRoleUserV2DTO> saasRoleUserRelations,
List<SaasProductModuleFeatureRelation> permissionProducts) {
List<SaasRoleUserV2DTO> adminRoles = saasRoleUserRelations.stream()
.filter(e -> RoleTypeEnum.isAdmin(e.getSaasRole().getRoleType()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(adminRoles)) {
return false;
}
Set<String> ouTypes = permissionProducts.stream()
.map(SaasProductModuleFeatureRelation::getDictCode)
.collect(Collectors.toSet());
return adminRoles.stream()
.anyMatch(adminRole -> ouTypes.contains(String.valueOf(adminRole.getSaasRole().getProductUnitType())));
}
private List<SaasProductModuleFeatureRelation> listPermissionProduct(List<SaasFeature> saasFeatures,
Set<Long> productIds) {
return saasProductModuleFeatureRelationDao.lambdaQuery()
.in(SaasProductModuleFeatureRelation::getProductModuleId, productIds)
.in(SaasProductModuleFeatureRelation::getFeatureId, Lists.transform(saasFeatures, SaasFeature::getId))
.eq(SaasProductModuleFeatureRelation::getIsDelete, TableIsDeleteEnum.NORMAL.value)
.list();
}
private List<SaasRoleUserV2DTO> listRoleUserRelations(PermissionCheckReq identityAuthReq,
List<SaasFeature> saasFeatures) {
List<ListRoleUserRelationParam.WorkspaceOuPair> workspaceOuPairs = Lists.newArrayList(
ListRoleUserRelationParam.WorkspaceOuPair.builder()
.workspaceId(identityAuthReq.getWorkspaceId())
.ouId(identityAuthReq.getOuId())
.build()
);
ListRoleUserRelationParam listRoleUserRelationParam = ListRoleUserRelationParam.builder()
.personId(identityAuthReq.getPersonId())
.workspaceOuPairs(Lists.newArrayList(workspaceOuPairs))
.needRole(true)
.needRolePermissionOld(true)
.featureIds(Lists.transform(saasFeatures, SaasFeature::getId))
.build();
return saasRoleUserRelationService.listV2(listRoleUserRelationParam).stream()
.filter(e -> e.getSaasRole() != null)
// 角色没有需要鉴权的权限点也需要过滤
.filter(e -> !CollectionUtils.isEmpty(e.getSaasRole().getSaasPermissions()))
.collect(Collectors.toList());
}
public Set<Long> listProducts(PermissionCheckReq req) {
List<ServicePkgDetailRes> servicePkgDetailRes = rpcListProcessor(() -> servicePkgClient.getServicePkgDetailBySpaceId(Sets.newHashSet(req.getWorkspaceId())),
"查询租户的产品", req.getWorkspaceId()).getData();
if (CollectionUtil.isEmpty(servicePkgDetailRes)) {
return Collections.emptySet();
}
return servicePkgDetailRes.stream()
.map(ServicePkgDetailRes::getProducts)
.filter(CollectionUtil::isNotEmpty)
.flatMap(Collection::stream)
.map(ServicePkgProduct::getProductId)
.collect(Collectors.toSet());
}
}