import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.huawei.it.gts.ls.cache.CacheManagerHelper;
import com.huawei.it.gts.ls.common.domain.LSResponse;
import com.huawei.it.gts.ls.constants.GlobalConstants;
import com.huawei.it.gts.ls.constants.GlobalErrorCode;
import com.huawei.it.gts.ls.security.autoconfig.SdkConfig;
import com.huawei.it.gts.ls.security.drools.DataScopeRuleEngine;
import com.huawei.it.gts.ls.security.drools.DataScopeRuleEngineImpl;
import com.huawei.it.gts.ls.session.Authority;
import com.huawei.it.gts.ls.session.Realm;
import com.huawei.it.gts.ls.session.Role;
import com.huawei.it.gts.ls.session.SessionManagerHolder;
import com.huawei.it.gts.ls.session.SessionUserPrincipal;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Data
@Component
@ConditionalOnProperty(prefix = "security.config.authority.filter", name = "enabled", havingValue = "true")
public class AuthorityFilter implements GlobalFilter, Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthorityFilter.class);
private final String prefix = "[/\w]+/v[0-9]+[\.0-9]+";// URL验证正则表达式前缀部分,如:/api_gateway/uams_msa/v0.1
private final String replaceRegex = "\{\w+\}";// 权限资源URL正则匹配模式,如:将"{id}",替换掉java正则表达式
private final String replacement = "[\\w-%.]+";// java正则表达式
private final String WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; //5gstart协议头
@Autowired
private SdkConfig sdkConfig;
/**
* @param exchange
* @param chain
* @return
*/
@SuppressWarnings("unchecked")
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
LOGGER.debug("------------Spring Cloud Gateway AuthorityFilter init()----------");
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String uri = request.getPath().toString();
LOGGER.info("Request URI:{}", uri);
String requestMethod = request.getMethod().toString();
HttpHeaders headers = request.getHeaders();
String sessionId = headers.getFirst(GlobalConstants.SESSION_ID);
if (StringUtils.isBlank(sessionId)){// sessionId为空说明是访客角色,非访客角色不可能为空
LOGGER.info("GuestAuthorityCacheKey: {}", sdkConfig.getGuestAuthorityCacheKey());
List<String> guestAuthorityList;
try {
if (CacheManagerHelper.getCacheManager().hasKey(sdkConfig.getGuestAuthorityCacheKey())) {
// 查询缓存中guest角色对应的权限url
guestAuthorityList = (List<String>) CacheManagerHelper.getCacheManager().get(sdkConfig.getGuestAuthorityCacheKey());
//如果缓存中的白名单数据为空,则从配置文件中获取
if (CollectionUtils.isEmpty(guestAuthorityList)){
LOGGER.info("从缓存中获取的白名单资源为空{},开始从配置文件中获取!",guestAuthorityList);
guestAuthorityList = getGuestUrlFromConfig();
}
}else {
LOGGER.info("从缓存中获取的白名单资源key不存在{},开始从配置文件中获取!",sdkConfig.getGuestAuthorityCacheKey());
guestAuthorityList = getGuestUrlFromConfig();
}
}catch (Exception e){
LOGGER.error("获取白名单资源异常,开始从配置文件中获取:",e);
//当读取缓存出现异常时,从配置文件中获取白名单接口资源
guestAuthorityList = getGuestUrlFromConfig();
}
if (CollectionUtils.isNotEmpty(guestAuthorityList)) {
for (String guestUrl : guestAuthorityList) {
String[] splitUrls = guestUrl.split(":");// 将http
// request请求方法和URL分割;如:GET:/apps/v0.1/action
String newUrl = splitUrls[1].replaceAll(replaceRegex, replacement) + "$";
if (!StringUtils.startsWith(newUrl, "/")) {
newUrl = "/" + newUrl;
}
String regex = prefix + newUrl;// 将正则前缀部分+后续部分拼接成完整的URI校验正则表达式
Matcher matcher = Pattern.compile(regex).matcher(uri);
if (matcher.matches()) {
if (StringUtils.equalsIgnoreCase(splitUrls[0],requestMethod)){
return chain.filter(exchange.mutate().request(exchange.getRequest()).build());
}
}
}
}
}
String tenantId = headers.getFirst(GlobalConstants.TENANT_ID);
// 尝试获取WebSocket请求头Protocol
String wsheaders = headers.getFirst(WEBSOCKET_PROTOCOL);
if (wsheaders != null) {
tenantId = wsheaders.split(",")[0];
}
// 判断租户ID
if (StringUtils.isBlank(tenantId)) {
LOGGER.error("-----请求参数[tenantId]缺失!");
return outResponse(response, GlobalErrorCode.E1001_002);
}
SessionUserPrincipal principal = (SessionUserPrincipal) SessionManagerHolder.getPrincipal(sessionId);
String uId = principal.getUid();
// 判断Session是否过期
if (StringUtils.isBlank(uId)) {
LOGGER.error("-----用户会话已过期!");
return outResponse(response, GlobalErrorCode.E1001_012);
}
Map<String, Object> objectMap = new HashMap<>();
objectMap.put("userIdCache", uId);
LOGGER.info("userIdCache = {}", uId);
String principalTenantId = principal.getTenantId();
if (!StringUtils.equalsIgnoreCase(tenantId,principalTenantId)) {
LOGGER.error("-----用户[{}]关联的租户信息异常,请求头中租户信息[{}],缓存中租户信息[{}]!",
principal.getUid(),tenantId,principalTenantId);
return outResponse(response, GlobalErrorCode.E1001_002);
}
final String realmId = headers.getFirst(GlobalConstants.REALM_ID);
MultiValueMap<String, String> queryParams = request.getQueryParams();
Set<String> paramsSet = queryParams.keySet();
for (String param : paramsSet) {
objectMap.put(param + "Param", request.getQueryParams().getFirst(param));
}
String contentType = headers.getFirst("Content-Type");
// 只考虑contentType是json格式的
if (contentType!=null && contentType.contains("application/json")) {
Object cachedRequestBodyObject = exchange.getAttribute("cachedRequestBodyObject");
if (null != cachedRequestBodyObject) {
String body = cachedRequestBodyObject.toString();
if (body.startsWith("[")) {
List<Map<String, Object>> mapList = (List<Map<String, Object>>) cachedRequestBodyObject;
String jsonStr = JSON.toJSONString(mapList);
JSONArray jsonArray = JSONArray.parseArray(jsonStr);
objectMap.put("jsonArray", jsonArray);
} else {
Map<String, Object> requestBodylist = (Map<String, Object>) cachedRequestBodyObject;
objectMap.putAll(requestBodylist);
}
}
}
Map<String,Object> respMap = verifyURI(tenantId, realmId,sessionId, uri, requestMethod, objectMap);
// boolean matches = verifyURI(tenantId, realmId,sessionId, uri, requestMethod, objectMap);
boolean matches = (boolean) respMap.get("flag");
if (!matches) {
LOGGER.info("-----来自租户[{}]下的用户[{}]没有分配相关权限!", tenantId, principal.getUid());
// exchange.mutate().response(getRespDec(response,
// GlobalErrorCode.E1001_001)).build();
return outResponse(response, (String) respMap.get("status"));
} else {
LOGGER.info("租户信息为[{}],请求的uri是[{}],请求方法是[{}],请求的参数是[{}]。", tenantId, uri, requestMethod, objectMap);
// exchange.mutate().request(exchange.getRequest()).build();
return chain.filter(exchange);
}
}
private synchronized Map<String,Object> verifyURI(String tenantId, String realmId,String sessionId,
String requestURL, String method, Map<String, Object> objectMap) {
Map<String,Object> respMap = new HashMap<>(4);
Map<String, Object> resultMap = new HashMap<>(objectMap);
String uri = requestURL;
List<Role> roles = Lists.newArrayList();
List<Authority> resources = Lists.newArrayList();
if (StringUtils.isNotBlank(realmId)){
List<Realm> realms = SessionManagerHolder.getRealm(sessionId);// 获取领域列表
Realm realm = realms.stream().filter(s->s.getRealmId().equals(realmId)).findAny().orElse(null);
if (null != realm){
roles = realm.getRoles();
resources = realm.getAuthorities();
}else {
LOGGER.info("-----该领域[{}]下没有分配任何角色-----", realmId);
}
}else {
roles = SessionManagerHolder.getRole(sessionId);
resources = SessionManagerHolder.getAuthority(sessionId);
}
// 该用户没有关联角色,则无权访问
if (CollectionUtils.isEmpty(roles)) {
LOGGER.info("-----错误码[{}],该租户[{}]没有分配相关角色权限!-----", GlobalErrorCode.E1001_003, tenantId);
respMap.put("status",GlobalErrorCode.E1001_003);
respMap.put("flag",false);
return respMap;
}
List<String> roleIds = roles.stream().map(Role::getRoleId).collect(Collectors.toList());
resultMap.put("roleIdsCache", roleIds);
if (CollectionUtils.isEmpty(resources)) {
LOGGER.info("-----错误码[{}],该租户[{}]没有分配相关权限资源!-----", GlobalErrorCode.E1001_004, tenantId);
respMap.put("status",GlobalErrorCode.E1001_004);
respMap.put("flag",false);
return respMap;
// return false;
}
// 将所有的权限资源URL取出
List<String> urls = resources.stream().map(Authority::getResourceUrl).collect(Collectors.toList());
for (String url : urls) {
if (StringUtils.isBlank(url) || !StringUtils.startsWith(url, method.toUpperCase(Locale.US))) {
// URL为空或者不以request method为前缀的URL都跳过,排除非API接口权限资源
continue;
}
String[] splitUrls = url.split(":");// 将http
// request请求方法和URL分割;如:GET:/apps/v0.1/action
// 将权限资源URL进行替换成java正则的后缀部分
String suffix = splitUrls[1].replaceAll(replaceRegex, replacement) + "$";
if (!StringUtils.startsWith(suffix, "/")) {
suffix = "/" + suffix;
}
String regex = prefix + suffix;// 将正则前缀部分+后续部分拼接成完整的URI校验正则表达式
Matcher matcher = Pattern.compile(regex).matcher(uri);
if (matcher.matches()) {
LOGGER.debug("请求URL:{},匹配到正则表达式:{}", uri, regex);
Map<String, Object> map = getUrlParam(url, uri);
resultMap.putAll(map);
List<Authority> authority = resources.stream()
.filter(o -> (StringUtils.isNotBlank(o.getResourceUrl()) && o.getResourceUrl().equals(url)))
.collect(Collectors.toList());
List<String> scopeList = authority.stream().filter(o -> StringUtils.isNotBlank(o.getDataScope()))
.collect(Collectors.toList()).stream().map(Authority::getDataScope)
.collect(Collectors.toList());
LOGGER.info("---本次校验的参数是[{}]---", resultMap);
boolean flag = droolsHandle(scopeList, resultMap);
// 没有匹配到数据范围,判定当前请求参数范围没有权限
if (!flag) {
LOGGER.info("-----错误码[{}],本次请求[{}]没有对应数据范围权限!-----", GlobalErrorCode.E1001_005, requestURL);
respMap.put("status",GlobalErrorCode.E1001_005);
respMap.put("flag",false);
return respMap;
// return false;
}
respMap.put("flag",true);
return respMap;
// return true;
}
}
LOGGER.info("-----本次请求[{}]在租户[{}]下没有匹配相关权限资源!-----", requestURL, tenantId);
respMap.put("flag",false);
respMap.put("status",GlobalErrorCode.E1001_004);
return respMap;
// return false;
}
private Map<String, Object> getUrlParam(String url, String uri) {
Map<String, Object> map = new HashMap<>();
String[] splitResourceUrls = url.split("/");
String[] splitUris = uri.split("/");
for (int i = 0; i < splitResourceUrls.length; i++) {
Matcher m1 = Pattern.compile(replaceRegex).matcher(splitResourceUrls[i]);
if (m1.matches()) {
String afterStr = splitResourceUrls[i - 1];
for (int j = 0; j < splitUris.length; j++) {
if (afterStr.equals(splitUris[j])) {
String key = m1.group().substring(1, m1.group().length() - 1) + "Path";
map.put(key, splitUris[j + 1]);
}
}
}
}
return map;
}
private List<Role> getRolesByTenant(String realmId,String sessionId) {
List<Role> roles = null;
if (StringUtils.isBlank(realmId)) {// 前端没有上报领域id
roles = SessionManagerHolder.getRole(sessionId);// 获取租户下默认领域的所有角色列表
} else {
List<Realm> realms = SessionManagerHolder.getRealm(sessionId);// 获取领域列表
roles = getRolesByRealm(realms, realmId);// 获取相应领域下的角色列表
}
return roles;
}
* @Desc 获取领域下相应角色列表. <br/>
private List<Role> getRolesByRealm(List<Realm> realms, String realmId) {
for (Realm realm : realms) {
if (realmId.equals(realm.getRealmId())) {
return realm.getRoles();
}
}
LOGGER.info("-----该领域[{}]下没有分配任何角色-----", realmId);
return Lists.newArrayList();
}
private void outResponse(HttpServletResponse response, String status) throws IOException {
LSResponse<Object> lsResponse = new LSResponse<>();
lsResponse.setFaild(status);
lsResponse.setStatus(Integer.parseInt(status));
response.setStatus(401);
response.setHeader("content-type", "text/html;charset=UTF-8");
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
IOUtils.write(mapper.writeValueAsString(lsResponse), response.getOutputStream(), Charset.defaultCharset());
}
private Mono<Void> outResponse(ServerHttpResponse response, String status) {
try {
LSResponse<Object> lsResponse = new LSResponse<>();
lsResponse.setFaild(status);
lsResponse.setStatus(Integer.parseInt(status));
// 对象转JSON转byte数组
ObjectMapper objectMapper = new ObjectMapper();
byte[] data = objectMapper.writeValueAsBytes(lsResponse);
// 写入响应头
DataBuffer buffer = response.bufferFactory().wrap(data);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
} catch (JsonProcessingException e) {
LOGGER.error("lsResponse.parse.jsonException");
}
return null;
}
/**
* 执行drools引擎,匹配参数
*
* @param scopeList
* @param parameterMap
*/
private Boolean droolsHandle(List<String> scopeList, Map<String, Object> parameterMap) {
DataScopeRuleEngine ruleEngine = new DataScopeRuleEngineImpl();
if (CollectionUtils.isEmpty(scopeList)) {
return true;
}
boolean flag = false;
for (String scope : scopeList) {
ruleEngine.initEngine(scope);
boolean f = ruleEngine.executeRuleEngine(parameterMap);
flag = f || flag;
}
return flag;
}
/**
* 从配置文件中获取白名单资源
* @return
*/
private List<String> getGuestUrlFromConfig(){
String guestUrl = sdkConfig.getGuestUrl();
String[] strings = guestUrl.split(",");
return Arrays.asList(strings);
}
@Override
public int getOrder() {
return 150;
}
}