|
|
@@ -0,0 +1,281 @@
|
|
|
+package com.sckw.gateway.filter;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
+import com.sckw.core.model.constant.Global;
|
|
|
+import com.sckw.core.model.enums.ClientTypeEnum;
|
|
|
+import com.sckw.core.model.enums.SystemTypeEnum;
|
|
|
+import com.sckw.core.utils.CollectionUtils;
|
|
|
+import com.sckw.core.utils.EncryUtil;
|
|
|
+import com.sckw.core.utils.NumberUtils;
|
|
|
+import com.sckw.core.utils.StringUtils;
|
|
|
+import com.sckw.core.web.config.CustomConfig;
|
|
|
+import com.sckw.core.web.constant.HttpStatus;
|
|
|
+import com.sckw.core.web.constant.RequestConstant;
|
|
|
+import com.sckw.core.web.model.LoginEntInfo;
|
|
|
+import com.sckw.core.web.model.LoginUserInfo;
|
|
|
+import com.sckw.gateway.pojo.HttpResult;
|
|
|
+import com.sckw.redis.utils.RedissonUtils;
|
|
|
+import jakarta.annotation.PostConstruct;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.redisson.api.RSet;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+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.server.reactive.ServerHttpRequest;
|
|
|
+import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.web.server.ServerWebExchange;
|
|
|
+import reactor.core.publisher.Mono;
|
|
|
+
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+public class AuthenticationFilter implements GlobalFilter, Ordered {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ CustomConfig customConfig;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 直接放行不需要校验Access-Token的请求
|
|
|
+ */
|
|
|
+ private static final List<String> EXCLUDEPATH = new ArrayList<>();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 直接放行不需要校验Access-Special的请求
|
|
|
+ */
|
|
|
+ private static final List<String> IMPORT_PASS_PATH = new ArrayList<>();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 需要校验token但不用接口权限校验的请求
|
|
|
+ */
|
|
|
+ private static final List<String> WITHOUTPATH = new ArrayList<>();
|
|
|
+
|
|
|
+ private static final String REGISTER = "/kwsEnt/register";
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @desc: 初始化放行路径
|
|
|
+ * @author: czh
|
|
|
+ */
|
|
|
+ @PostConstruct
|
|
|
+ private void initExcludePath() {
|
|
|
+ String links = customConfig.getLinks();
|
|
|
+ if (StringUtils.isNotBlank(links)) {
|
|
|
+ EXCLUDEPATH.addAll(Arrays.asList(links.split(Global.COMMA)));
|
|
|
+ }
|
|
|
+
|
|
|
+ String specialLinks = customConfig.getSpecialLinks();
|
|
|
+ if (StringUtils.isNotBlank(specialLinks)) {
|
|
|
+ IMPORT_PASS_PATH.addAll(Arrays.asList(specialLinks.split(Global.COMMA)));
|
|
|
+ }
|
|
|
+// String withoutLinks = customConfig.getWithoutLinks();
|
|
|
+// if (StringUtils.isNotBlank(withoutLinks)) {
|
|
|
+// WITHOUTPATH.addAll(Arrays.asList(withoutLinks.split(Global.COMMA)));
|
|
|
+// }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
|
+ ServerHttpRequest request = exchange.getRequest();
|
|
|
+ ServerHttpResponse response = exchange.getResponse();
|
|
|
+ String token = request.getHeaders().getFirst(RequestConstant.TOKEN);
|
|
|
+ String clientType = request.getHeaders().getFirst(RequestConstant.CLIENT_TYPE);
|
|
|
+ String systemTypeStr = request.getHeaders().getFirst(RequestConstant.SYSTEM_TYPE);
|
|
|
+ Integer systemType = systemTypeStr == null ? null : Integer.parseInt(systemTypeStr);
|
|
|
+// String accessSpecial = request.getHeader(RequestConstant.ACCESS_SPECIAL);
|
|
|
+ String requestUri = request.getPath().value();
|
|
|
+ /*1、非token校验接口放行*/
|
|
|
+ if (EXCLUDEPATH.contains(requestUri)) {
|
|
|
+ return chain.filter(exchange);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加对Swagger相关路径的放行
|
|
|
+ if (requestUri.startsWith("/swagger-ui") ||
|
|
|
+ requestUri.startsWith("/v3/api-docs") || requestUri.startsWith("/doc.htm")||
|
|
|
+ requestUri.startsWith("/webjars/")) {
|
|
|
+ return chain.filter(exchange);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*2、校验token**/
|
|
|
+ /*2.1、校验token非空*/
|
|
|
+ HttpResult result = checkBlank(token, clientType, systemType, requestUri);
|
|
|
+ if (result.getCode() != HttpStatus.SUCCESS_CODE) {
|
|
|
+ return writeResponse(response,result.getCode(),result.getMsg());
|
|
|
+ }
|
|
|
+
|
|
|
+ /*2.2、token解析*/
|
|
|
+ Map<String, Object> tokenMap = EncryUtil.descryV2(Global.PRI_KEY, token);
|
|
|
+ if (tokenMap == null) {
|
|
|
+ return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.TOKEN_INVALID_MESSAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*2.3、从redis获取用户登录token*/
|
|
|
+ Long userId = StringUtils.isNotBlank(tokenMap.get("userId")) ? NumberUtils.parseLong(tokenMap.get("userId")) : null;
|
|
|
+ String key = Global.getFullUserTokenKey(clientType, userId);
|
|
|
+ String redisUserToken = RedissonUtils.getString(key);
|
|
|
+ if (StringUtils.isBlank(redisUserToken)) {
|
|
|
+ return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.TOKEN_INVALID_MESSAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*2.4、请求token和redis中token不一致,说明账号在别处登录了*/
|
|
|
+ if (!token.equals(redisUserToken)) {
|
|
|
+ return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.ACCOUNT_OTHER_LOGIN_MESSAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*3、校验登录用户信息*/
|
|
|
+ key = Global.getFullUserLoginKey(systemType, userId);
|
|
|
+ String userInfoStr = RedissonUtils.getString(key);
|
|
|
+ LoginUserInfo loginUserInfo = StringUtils.isNotBlank(userInfoStr) ? JSON.parseObject(userInfoStr, LoginUserInfo.class) : null;
|
|
|
+ if (Objects.isNull(loginUserInfo)) {
|
|
|
+ return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.TOKEN_INVALID_MESSAGE);
|
|
|
+ }
|
|
|
+ loginUserInfo.setClientType(clientType);
|
|
|
+
|
|
|
+ //校验用户账号是否冻结
|
|
|
+ if (loginUserInfo.getStatus() == Global.YES) {
|
|
|
+ return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,"您的账号已被冻结,请联系系统管理员!");
|
|
|
+ }
|
|
|
+
|
|
|
+ /*4、登录中的企业信息*/
|
|
|
+ String loginEntStr = RedissonUtils.getString(Global.getFullUserEntKey(loginUserInfo.getEntId()));
|
|
|
+ LoginEntInfo loginEntInfo = StringUtils.isNotBlank(loginEntStr) ? JSON.parseObject(loginEntStr, LoginEntInfo.class) : null;
|
|
|
+
|
|
|
+ //运营端
|
|
|
+ if (SystemTypeEnum.MANAGE.getCode().equals(systemType)) {
|
|
|
+ //redis 获取客户经理绑定企业ID
|
|
|
+ String managerKey = Global.getCustomerManagerUserLoginKey(SystemTypeEnum.MANAGE.getCode(), loginUserInfo.getId());
|
|
|
+ RSet<Object> set = RedissonUtils.getSet(managerKey);
|
|
|
+ if (Objects.nonNull(set)) {
|
|
|
+ List authUserIdList = JSONObject.parseObject(set.toString(), List.class);
|
|
|
+ if (CollectionUtils.isNotEmpty(authUserIdList)) {
|
|
|
+ loginUserInfo.setAuthEntIdList(authUserIdList);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //重置缓存有效期
|
|
|
+ RedissonUtils.putString(Global.getFullUserTokenKey(clientType, userId), token, ClientTypeEnum.expireTime(clientType));
|
|
|
+ RedissonUtils.putString(Global.getFullUserLoginKey(systemType, loginUserInfo.getId()), JSON.toJSONString(loginUserInfo), Global.APP_TOKEN_EXPIRE);
|
|
|
+ RedissonUtils.putString(Global.getFullUserEntKey(loginEntInfo.getId()), JSON.toJSONString(loginEntInfo), Global.APP_TOKEN_EXPIRE);
|
|
|
+ //将用户信息和企业信息放入header方便后续微服务获取
|
|
|
+ ServerWebExchange build = buildNewExchange(exchange,loginUserInfo,loginEntInfo);
|
|
|
+ return chain.filter(build);
|
|
|
+ }
|
|
|
+
|
|
|
+ //非运营端
|
|
|
+ if ((StringUtils.isBlank(loginEntStr) || loginEntInfo == null)) {
|
|
|
+ return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,HttpStatus.UN_LOGIN_MESSAGE);
|
|
|
+ } else {
|
|
|
+ //校验用户企业是否冻结
|
|
|
+ if (loginEntInfo.getStatus() == Global.YES) {
|
|
|
+ return writeResponse(response,HttpStatus.TOKEN_INVALID_CODE,"您所属企业已被冻结,请联系系统管理员!");
|
|
|
+ }
|
|
|
+
|
|
|
+ //校验用户企业审批状态
|
|
|
+ if (!loginEntInfo.getValid() && !REGISTER.equals(requestUri)) {
|
|
|
+ if (loginEntInfo.getApproval() == Global.NO) {
|
|
|
+ return writeResponse(response,HttpStatus.CODE_60603,HttpStatus.ENTCERTIFICATES_NOT_REGISTER);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (loginEntInfo.getApproval() == Global.NUMERICAL_THREE) {
|
|
|
+ return writeResponse(response,HttpStatus.CODE_60603,HttpStatus.ENTCERTIFICATES_NOT_PASS);
|
|
|
+ }
|
|
|
+
|
|
|
+ return writeResponse(response,HttpStatus.CODE_60603,HttpStatus.ENTCERTIFICATES_INVAILD);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*5、请求权限校验*/
|
|
|
+ //非管理员有接口权限才放行
|
|
|
+ if (loginUserInfo.getIsMain() == Global.NO
|
|
|
+ && !WITHOUTPATH.contains(requestUri)
|
|
|
+ && !checkMenu(clientType, loginUserInfo.getId(), requestUri)) {
|
|
|
+ return writeResponse(response,HttpStatus.AUTHORITY_NO_CODE,HttpStatus.ACCESS_FIAL);
|
|
|
+ }
|
|
|
+ RedissonUtils.putString(Global.getFullUserLoginKey(systemType, loginUserInfo.getId()), JSON.toJSONString(loginUserInfo), Global.APP_TOKEN_EXPIRE);
|
|
|
+ RedissonUtils.putString(Global.getFullUserEntKey(loginEntInfo.getId()), JSON.toJSONString(loginEntInfo), Global.APP_TOKEN_EXPIRE);
|
|
|
+ RedissonUtils.putString(Global.getFullUserTokenKey(clientType, userId), token, ClientTypeEnum.expireTime(clientType));
|
|
|
+ //将用户信息和企业信息放入header方便后续微服务获取
|
|
|
+ ServerWebExchange build = buildNewExchange(exchange,loginUserInfo,loginEntInfo);
|
|
|
+ return chain.filter(build);
|
|
|
+ }
|
|
|
+
|
|
|
+ private ServerWebExchange buildNewExchange(ServerWebExchange exchange, LoginUserInfo loginUserInfo, LoginEntInfo loginEntInfo) {
|
|
|
+ SimplePropertyPreFilter filter = new SimplePropertyPreFilter("id","entTypes", "firmName","approval","status","special");
|
|
|
+ String userInfoStrEncode = URLEncoder.encode(JSON.toJSONString(loginUserInfo), StandardCharsets.UTF_8);
|
|
|
+ String entInfoStrEncode = URLEncoder.encode(JSON.toJSONString(loginEntInfo,filter), StandardCharsets.UTF_8);
|
|
|
+ ServerHttpRequest newRequest = exchange.getRequest().mutate()
|
|
|
+ .headers(h -> {
|
|
|
+ h.add(Global.USER_INFO_STR_ENCODE, userInfoStrEncode);
|
|
|
+ h.add(Global.ENT_INFO_STR_ENCODE, entInfoStrEncode);
|
|
|
+ h.remove(RequestConstant.TOKEN);
|
|
|
+ }).build();
|
|
|
+ return exchange.mutate().request(newRequest).build();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建返回内容
|
|
|
+ *
|
|
|
+ * @param response ServerHttpResponse
|
|
|
+ * @param code 返回码
|
|
|
+ * @param msg 返回数据
|
|
|
+ * @return Mono
|
|
|
+ */
|
|
|
+ protected Mono<Void> writeResponse(ServerHttpResponse response, Integer code, String msg) {
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ message.put("code", code);
|
|
|
+ message.put("msg", msg);
|
|
|
+ byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
|
|
|
+ DataBuffer buffer = response.bufferFactory().wrap(bits);
|
|
|
+ response.setStatusCode(org.springframework.http.HttpStatus.OK);
|
|
|
+ // 指定编码,否则在浏览器中会中文乱码
|
|
|
+ response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
|
|
|
+ return response.writeWith(Mono.just(buffer));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param token token
|
|
|
+ * @param clientType 客户端类型
|
|
|
+ * @param systemType 系统类型
|
|
|
+ * @param requestUri 请求地址
|
|
|
+ * @return 校验结果
|
|
|
+ * @desc Hearder内容校验
|
|
|
+ * @author zk
|
|
|
+ * @date 2023/12/14
|
|
|
+ **/
|
|
|
+ private HttpResult checkBlank(String token, String clientType, Integer systemType, String requestUri) {
|
|
|
+ if (StringUtils.isBlank(token)) {
|
|
|
+ return HttpResult.error(HttpStatus.PARAMETERS_MISSING_CODE, HttpStatus.UN_LOGIN_MESSAGE);
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(clientType)) {
|
|
|
+ return HttpResult.error(HttpStatus.PARAMETERS_MISSING_CODE, HttpStatus.INVALID_REQUEST);
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(systemType)) {
|
|
|
+ return HttpResult.error(HttpStatus.PARAMETERS_MISSING_CODE, HttpStatus.INVALID_REQUEST);
|
|
|
+ }
|
|
|
+ return HttpResult.ok();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param userId 用户菜单权限key url 当前请求url
|
|
|
+ * @return boolean
|
|
|
+ * @desc: 校验url权限
|
|
|
+ * @author: czh
|
|
|
+ * @date: 2023/6/28
|
|
|
+ */
|
|
|
+ private boolean checkMenu(String clientType, Long userId, String url) {
|
|
|
+ return true;
|
|
|
+ //return RedissonUtils.contains(Global.REDIS_SYS_MENU_PREFIX + clientType + Global.COLON + userId, url);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int getOrder() {
|
|
|
+ return 2;
|
|
|
+ }
|
|
|
+}
|