|
|
@@ -1,11 +1,14 @@
|
|
|
package com.inspur.smsb.gateway.filter;
|
|
|
|
|
|
+import com.alibaba.cola.dto.Response;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
import com.alibaba.nacos.common.utils.MD5Utils;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import com.google.common.base.Strings;
|
|
|
+import com.inspur.smsb.gateway.dto.KeycloakGroupsDto;
|
|
|
import com.inspur.smsb.gateway.dto.KeycloakUserDto;
|
|
|
import com.inspur.smsb.gateway.utils.HttpClientUtil;
|
|
|
import com.nimbusds.jose.JWSObject;
|
|
|
@@ -14,8 +17,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
|
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
|
|
+import org.springframework.core.io.buffer.DataBufferFactory;
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
|
+import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
import org.springframework.util.CollectionUtils;
|
|
|
import org.springframework.util.StringUtils;
|
|
|
@@ -25,9 +31,7 @@ import reactor.core.publisher.Mono;
|
|
|
import javax.annotation.Resource;
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
import java.text.ParseException;
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
+import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
@@ -50,6 +54,8 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
|
|
|
private String clientSecret;
|
|
|
@Value("${keycloak.adminUserId}")
|
|
|
private String adminUserId;
|
|
|
+ private static final String ROLE_SUPER_ADMIN = "ROLE_SUPER_ADMIN";
|
|
|
+ private static final String ROLE_ADMIN = "ROLE_ADMIN";
|
|
|
|
|
|
@Value("${wxapplet.secret}")
|
|
|
private String secret;
|
|
|
@@ -63,18 +69,23 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
|
|
|
@Override
|
|
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
|
String wxAppletId = exchange.getRequest().getHeaders().getFirst("WxAppletId");
|
|
|
+ Map<String, String> queryParams = exchange.getRequest().getQueryParams().toSingleValueMap();
|
|
|
+ String urlTenant = queryParams.get("tenant");
|
|
|
+ String urlOrg = queryParams.get("org");
|
|
|
if (!Strings.isNullOrEmpty(wxAppletId)) {
|
|
|
// search for userid
|
|
|
String tokenUrl = keyCloakServiceUrl + "realms/" + realm + "/protocol/openid-connect/token";
|
|
|
String token = getToken(tokenUrl, clientId, clientSecret);
|
|
|
if (StringUtils.hasText(token)) {
|
|
|
String url = keyCloakServiceUrl + "admin/realms/" + realm + "/users";
|
|
|
+ // 查找所有的用户信息
|
|
|
List<KeycloakUserDto> list = queryUsers(url, token);
|
|
|
list = list.stream().filter(keycloakUserDto -> {
|
|
|
Map attributes = keycloakUserDto.getAttributes();
|
|
|
if (attributes == null) {
|
|
|
return false;
|
|
|
}
|
|
|
+ // 获取用户attributes中的 wechat-applet-openid ,找到与请求头中一致的用户信息
|
|
|
JSONArray array = (JSONArray) attributes.get("wechat-applet-openid");
|
|
|
String openIdAttr = "";
|
|
|
if (array != null && !array.isEmpty()) {
|
|
|
@@ -83,12 +94,45 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
|
|
|
return StringUtils.hasText(openIdAttr) && openIdAttr.equals(wxAppletId);
|
|
|
}).collect(Collectors.toList());
|
|
|
if (!list.isEmpty()) {
|
|
|
- String userId = list.get(0).getId();
|
|
|
-
|
|
|
+ KeycloakUserDto user = list.get(0);
|
|
|
+ String userId = user.getId();
|
|
|
+ log.info("userId:{}", userId);
|
|
|
ServerHttpRequest request = exchange.getRequest()
|
|
|
.mutate()
|
|
|
.header("userId", userId)
|
|
|
.build();
|
|
|
+
|
|
|
+ if (StringUtils.hasText(urlTenant) || StringUtils.hasText(urlOrg)) {
|
|
|
+ // 1、获取用户id、用户部门(测试没有path、org时的情况)、用户租户;
|
|
|
+ JSONArray org = (JSONArray) user.getAttributes().get("org");
|
|
|
+ String userOrg = Objects.isNull(org) ? null : org.get(0).toString();
|
|
|
+ String userTenant = getGroupsByUserId(userId);
|
|
|
+ String userRealTenant = StringUtils.hasText(userTenant) ? userTenant :
|
|
|
+ "/" + Arrays.stream(userOrg.split("/")).collect(Collectors.toList()).get(1);
|
|
|
+ String userRealOrg = Objects.isNull(userOrg) ? userTenant : userOrg;
|
|
|
+ log.info("[微信小程序] userId:'{}, userOrg:{}, userTenant:{}, userRealTenant:{}, userRealOrg:{}", userId, userOrg, userTenant, userRealTenant,
|
|
|
+ userRealOrg);
|
|
|
+
|
|
|
+ // 2、有urlOrg或者urlTenant时,才去做过滤,获取真实的urlRealTenant和urlRealOrg
|
|
|
+ String urlRealTenant = Objects.isNull(urlTenant) ?
|
|
|
+ "/" + Arrays.stream(urlOrg.split("/")).filter(s -> !s.isEmpty()).collect(Collectors.toList()).get(0) : urlTenant;
|
|
|
+ String urlRealOrg = Objects.isNull(urlOrg) ? urlTenant : urlOrg;
|
|
|
+ log.info("[url信息] urlTenant:{}, urlOrg:{}, urlRealTenant:{}, urlRealOrg:{}", urlTenant, urlOrg, urlRealTenant, urlRealOrg);
|
|
|
+
|
|
|
+ // 3、根据用户角色去做判断
|
|
|
+ if (!userHasRole(userId, ROLE_SUPER_ADMIN) && userHasRole(userId, ROLE_ADMIN)) {
|
|
|
+ if (!urlRealTenant.equalsIgnoreCase(userRealTenant)) {
|
|
|
+ return getErrorResponse(exchange);
|
|
|
+ }
|
|
|
+ } else if (!userHasRole(userId, ROLE_SUPER_ADMIN) && !userHasRole(userId, ROLE_ADMIN)) {
|
|
|
+ String[] userArray = userRealOrg.split("/");
|
|
|
+ String[] urlOrgArray = urlRealOrg.split("/");
|
|
|
+ if (urlOrgArray.length < userArray.length || !isLegal(userArray, urlOrgArray)) {
|
|
|
+ return getErrorResponse(exchange);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 把新的 exchange 放回到过滤链
|
|
|
return chain.filter(exchange.mutate().request(request).build());
|
|
|
}
|
|
|
@@ -129,7 +173,6 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
|
|
|
|
|
|
String realToken = token.replace("Bearer ", "");
|
|
|
JWSObject jwsObject = JWSObject.parse(realToken);
|
|
|
-
|
|
|
if (adminUserId.equals(String.valueOf(jwsObject.getPayload().toJSONObject().get("sub")))) {
|
|
|
//just skip this adapter
|
|
|
return chain.filter(exchange);
|
|
|
@@ -137,12 +180,52 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
|
|
|
//租户转换类型
|
|
|
List<String> tenantMap = (List<String>) jwsObject.getPayload().toJSONObject().get("tenant");
|
|
|
String tenant = CollectionUtils.isEmpty(tenantMap) ? "unknown" : tenantMap.get(0);
|
|
|
+ String userId = String.valueOf(jwsObject.getPayload().toJSONObject().get("sub"));
|
|
|
+ String userName = String.valueOf(jwsObject.getPayload().toJSONObject().get("preferred_username"));
|
|
|
ServerHttpRequest request = exchange.getRequest()
|
|
|
.mutate()
|
|
|
- .header("userId", String.valueOf(jwsObject.getPayload().toJSONObject().get("sub")))
|
|
|
- .header("userName", String.valueOf(jwsObject.getPayload().toJSONObject().get("preferred_username")))
|
|
|
+ // 对应 smsb_department_user 的 user_id 字段
|
|
|
+ .header("userId", userId)
|
|
|
+ .header("userName", userName)
|
|
|
.header("tenant", String.valueOf(tenant))
|
|
|
.build();
|
|
|
+
|
|
|
+ if (StringUtils.hasText(urlTenant) || StringUtils.hasText(urlOrg)) {
|
|
|
+ // 1、有urlOrg或者urlTenant时,才去做过滤
|
|
|
+ String urlRealTenant = Objects.isNull(urlTenant) ?
|
|
|
+ "/" + Arrays.stream(urlOrg.split("/")).filter(s -> !s.isEmpty()).collect(Collectors.toList()).get(0) : urlTenant;
|
|
|
+ String urlRealOrg = Objects.isNull(urlOrg) ? urlTenant : urlOrg;
|
|
|
+ log.info("[url信息] urlTenant:{}, urlOrg:{}, urlRealTenant:{}, urlRealOrg:{}", urlTenant, urlOrg, urlRealTenant, urlRealOrg);
|
|
|
+
|
|
|
+ // 根据token, 获取当前用户的角色 ROLE_SUPER_ADMIN -> 超管, ROLE_ADMIN -> 租户管理员,tenant已经获取,再获取org
|
|
|
+ com.nimbusds.jose.shaded.json.JSONObject realmAccess = (com.nimbusds.jose.shaded.json.JSONObject)jwsObject.getPayload().toJSONObject().get("realm_access");
|
|
|
+ com.nimbusds.jose.shaded.json.JSONArray rolesArray = (com.nimbusds.jose.shaded.json.JSONArray) realmAccess.get("roles");
|
|
|
+ List<String> roles = new ArrayList<>();
|
|
|
+ for (Object role : rolesArray) {
|
|
|
+ roles.add((String) role);
|
|
|
+ }
|
|
|
+ // userOrg可能为空,userTenant应该是必有的
|
|
|
+ String userOrg = String.valueOf(jwsObject.getPayload().toJSONObject().get("org"));
|
|
|
+ String userTenant = tenant;
|
|
|
+ String userRealTenant = Objects.isNull(userTenant) ?
|
|
|
+ "/" + Arrays.stream(userOrg.split("/")).filter(s -> !s.isEmpty()).collect(Collectors.toList()).get(0) : userTenant;
|
|
|
+ String userRealOrg = "null".equalsIgnoreCase(userOrg) ? userTenant : userOrg ;
|
|
|
+ log.info("[用户信息] userOrg:{}, userTenant:{}, userRealTenant:{}, userRealOrg:{}", userOrg, userTenant, userRealTenant, userRealOrg);
|
|
|
+
|
|
|
+ // 2、根据用户角色去做判断(tenant一定有,org不一定有)
|
|
|
+ if (Boolean.FALSE.equals(roles.contains(ROLE_SUPER_ADMIN)) && Boolean.TRUE.equals(roles.contains(ROLE_ADMIN))) {
|
|
|
+ if (!urlRealTenant.equalsIgnoreCase(userRealTenant)) {
|
|
|
+ return getErrorResponse(exchange);
|
|
|
+ }
|
|
|
+ } else if (Boolean.FALSE.equals(roles.contains(ROLE_SUPER_ADMIN)) && Boolean.FALSE.equals(roles.contains(ROLE_ADMIN))) {
|
|
|
+ String[] userArray = userRealOrg.split("/");
|
|
|
+ String[] urlOrgArray = urlRealOrg.split("/");
|
|
|
+ if (urlOrgArray.length < userArray.length || !isLegal(userArray, urlOrgArray)) {
|
|
|
+ return getErrorResponse(exchange);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 把新的 exchange 放回到过滤链
|
|
|
return chain.filter(exchange.mutate().request(request).build());
|
|
|
} catch (ParseException e) {
|
|
|
@@ -152,6 +235,40 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 无权限访问
|
|
|
+ */
|
|
|
+ private Mono<Void> getErrorResponse(ServerWebExchange exchange){
|
|
|
+ ServerHttpResponse response = exchange.getResponse();
|
|
|
+ response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ return response.writeWith(Mono.fromSupplier(() -> {
|
|
|
+ DataBufferFactory bufferFactory = response.bufferFactory();
|
|
|
+ try {
|
|
|
+ return bufferFactory.wrap(objectMapper.writeValueAsBytes(Response.buildFailure(String.valueOf(HttpStatus.PRECONDITION_FAILED.value()), "无访问权限")));
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.error("Error writing response", e);
|
|
|
+ return bufferFactory.wrap(new byte[0]);
|
|
|
+ }
|
|
|
+ }));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断部门路径是否合法
|
|
|
+ * @param userRealOrg 用户真实的部门路径
|
|
|
+ * @param urlRealOrg url请求参数的真实路径
|
|
|
+ * @return 是否合法,true为合法,false为不合法
|
|
|
+ */
|
|
|
+ private boolean isLegal(String[] userArray, String[] urlArray){
|
|
|
+ boolean flag = true;
|
|
|
+ for (int i = 0; i < userArray.length; i++) {
|
|
|
+ if (!userArray[i].equalsIgnoreCase(urlArray[i])) {
|
|
|
+ flag = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return flag;
|
|
|
+ }
|
|
|
+
|
|
|
private boolean isEncryptedRequest(ServerHttpRequest request) {
|
|
|
String sign = request.getHeaders().getFirst("sign");
|
|
|
String time = request.getHeaders().getFirst("time");
|
|
|
@@ -192,4 +309,40 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
|
|
|
}
|
|
|
return userList;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据userId获取到租户路径
|
|
|
+ * @param userId 用户id
|
|
|
+ * @return 该用户的部门路径
|
|
|
+ */
|
|
|
+ private String getGroupsByUserId (String userId) {
|
|
|
+ String url = keyCloakServiceUrl + "admin/realms/" + realm + "/users/" + userId + "/groups";
|
|
|
+ String tokenUrl = keyCloakServiceUrl + "realms/" + realm + "/protocol/openid-connect/token";
|
|
|
+ String token = getToken(tokenUrl, clientId, clientSecret);
|
|
|
+ String group = HttpClientUtil.sendGet(url, "Bearer " + token);
|
|
|
+ KeycloakGroupsDto[] groupDto = JSONObject.parseObject(group, KeycloakGroupsDto[].class);
|
|
|
+ return groupDto.length > 0 ? groupDto[0].getPath() : "";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据userId判断该用户是否含有roleName
|
|
|
+ * @param userId 用户id
|
|
|
+ * @param roleName 角色名
|
|
|
+ * @return 是否包含
|
|
|
+ */
|
|
|
+ private Boolean userHasRole (String userId, String roleName) {
|
|
|
+ String url = keyCloakServiceUrl + "admin/realms/" + realm + "/roles/" + roleName + "/users";
|
|
|
+ String tokenUrl = keyCloakServiceUrl + "realms/" + realm + "/protocol/openid-connect/token";
|
|
|
+ String token = getToken(tokenUrl, clientId, clientSecret);
|
|
|
+ // 获取某个角色的所有用户
|
|
|
+ String userMap = HttpClientUtil.sendGet(url, "Bearer " + token);
|
|
|
+ KeycloakUserDto[] result = JSONObject.parseObject(userMap, KeycloakUserDto[].class);
|
|
|
+ // 判断userId是否在用户中
|
|
|
+ for (KeycloakUserDto dto : result) {
|
|
|
+ if (userId.equalsIgnoreCase(dto.getId())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
}
|