✨ Introducing new features. #2034 客户端管理 增加验证码、前端登录加密控制功能

This commit is contained in:
lengleng committed 2020-11-19 12:49:02 +08:00
1 parent 8895e96f71
commit 96a5d4c3f5
14 files changed
+407 -53

No files matched your search

@@ -24,15 +24,23 @@ import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.constant.enums.EncFlagTypeEnum;
import com.pig4cloud.pigx.common.core.util.WebUtils;
import com.pig4cloud.pigx.gateway.config.GatewayConfigProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
@@ -62,6 +70,8 @@ import java.util.function.Function;
*/
@Slf4j
@Component
@RequiredArgsConstructor
@SuppressWarnings("all")
public class PasswordDecoderFilter extends AbstractGatewayFilterFactory {
private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
@@ -70,24 +80,31 @@ public class PasswordDecoderFilter extends AbstractGatewayFilterFactory {
private static final String KEY_ALGORITHM = "AES";
@Autowired
private GatewayConfigProperties gatewayConfig;
private final RedisTemplate redisTemplate;
private final GatewayConfigProperties gatewayConfig;
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 不是登录请求,直接向下执行
// 1. 不是登录请求,直接向下执行
if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) {
return chain.filter(exchange);
}
// 刷新token,直接向下执行
// 2. 刷新token类型,直接向下执行
String grantType = request.getQueryParams().getFirst("grant_type");
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
return chain.filter(exchange);
}
// 3. 判断客户端是否需要解密,明文传输直接向下执行
if (!isEncClient(request)) {
return chain.filter(exchange);
}
// 4. 前端加密密文解密逻辑
Class inClass = String.class;
Class outClass = String.class;
ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
@@ -109,6 +126,34 @@ public class PasswordDecoderFilter extends AbstractGatewayFilterFactory {
};
}
/**
* 根据请求的clientId 查询客户端配置是否是加密传输
* @param request 请求上下文
* @return true 加密传输 、 false 原文传输
*/
private boolean isEncClient(ServerHttpRequest request) {
String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String clientId = WebUtils.extractClientId(header).orElse(null);
// 获取租户拼接区分租户的key
String tenantId = request.getHeaders().getFirst(CommonConstants.TENANT_ID);
String key = String.format("%s:%s:%s", StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1 : tenantId,
CacheConstants.CLIENT_FLAG, clientId);
redisTemplate.setKeySerializer(new StringRedisSerializer());
Object val = redisTemplate.opsForValue().get(key);
// 当配置不存在时,默认需要解密
if (val == null) {
return true;
}
JSONObject information = JSONUtil.parseObj(val.toString());
if (StrUtil.equals(EncFlagTypeEnum.NO.getType(), information.getStr(CommonConstants.ENC_FLAG))) {
return false;
}
return true;
}
/**
* 原文解密
* @return
@@ -18,6 +18,8 @@
package com.pig4cloud.pigx.gateway.filter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -25,12 +27,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.constant.enums.LoginTypeEnum;
import com.pig4cloud.pigx.common.core.constant.enums.CaptchaFlagTypeEnum;
import com.pig4cloud.pigx.common.core.exception.ValidateCodeException;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
import com.pig4cloud.pigx.common.core.util.WebUtils;
import com.pig4cloud.pigx.gateway.config.GatewayConfigProperties;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -53,22 +54,21 @@ import reactor.core.publisher.Mono;
@Slf4j
@Component
@AllArgsConstructor
@SuppressWarnings("all")
public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
private final ObjectMapper objectMapper;
private final RedisTemplate redisTemplate;
private final GatewayConfigProperties gatewayConfig;
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 不是登录请求,直接向下执行
// 不是账号密码登录、短信登录,直接向下执行
if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL,
SecurityConstants.SMS_TOKEN_URL, SecurityConstants.SOCIAL_TOKEN_URL)) {
SecurityConstants.SMS_TOKEN_URL)) {
return chain.filter(exchange);
}
@@ -78,25 +78,12 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
return chain.filter(exchange);
}
// 终端设置不校验, 直接向下执行
// 判断客户端是否跳过检验
if (!isCheckCaptchaClient(request)) {
return chain.filter(exchange);
}
try {
String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String clientId = WebUtils.extractClientId(header).orElse(null);
if (StrUtil.isNotBlank(clientId) && gatewayConfig.getIgnoreClients().contains(clientId)) {
return chain.filter(exchange);
}
// 如果是社交登录,判断是否包含SMS
if (StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.SOCIAL_TOKEN_URL)) {
String mobile = request.getQueryParams().getFirst("mobile");
if (StrUtil.containsAny(mobile, LoginTypeEnum.SMS.getType())) {
throw new ValidateCodeException("验证码不合法");
}
else {
return chain.filter(exchange);
}
}
// 校验验证码
checkCode(request);
}
@@ -117,6 +104,34 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
};
}
/**
* 是否需要校验客户端,根据client 查询客户端配置
* @param request 请求
* @return true 需要校验, false 不需要校验
*/
private boolean isCheckCaptchaClient(ServerHttpRequest request) {
String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String clientId = WebUtils.extractClientId(header).orElse(null);
// 获取租户拼接区分租户的key
String tenantId = request.getHeaders().getFirst(CommonConstants.TENANT_ID);
String key = String.format("%s:%s:%s", StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1 : tenantId,
CacheConstants.CLIENT_FLAG, clientId);
redisTemplate.setKeySerializer(new StringRedisSerializer());
Object val = redisTemplate.opsForValue().get(key);
// 当配置不存在时,默认需要校验
if (val == null) {
return true;
}
JSONObject information = JSONUtil.parseObj(val.toString());
if (StrUtil.equals(CaptchaFlagTypeEnum.OFF.getType(), information.getStr(CommonConstants.CAPTCHA_FLAG))) {
return false;
}
return true;
}
/**
* 检查code
* @param request
@@ -139,7 +154,6 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
if (!captchaService.verification(vo).isSuccess()) {
throw new ValidateCodeException("验证码不能为空");
}
return;
}