mirror of
https://gitee.com/log4j/pig.git
synced 2024-12-22 12:48:58 +08:00
♻️ Refactoring code. 重构 PasswordDecoderFilter ValidateCodeFilter 代码至 pig-auth 模块简化网关逻辑
This commit is contained in:
parent
4b03d61d18
commit
baa890379b
@ -0,0 +1,48 @@
|
||||
package com.pig4cloud.pig.auth.endpoint;
|
||||
|
||||
import com.pig4cloud.pig.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
|
||||
import io.springboot.captcha.ArithmeticCaptcha;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 验证码相关的接口
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2022/6/27
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/code")
|
||||
@RequiredArgsConstructor
|
||||
public class ImageCodeEndpoint {
|
||||
|
||||
private static final Integer DEFAULT_IMAGE_WIDTH = 100;
|
||||
|
||||
private static final Integer DEFAULT_IMAGE_HEIGHT = 40;
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
/**
|
||||
* 创建图形验证码
|
||||
*/
|
||||
@SneakyThrows
|
||||
@GetMapping("/image")
|
||||
public void image(String randomStr, HttpServletResponse response) {
|
||||
ArithmeticCaptcha captcha = new ArithmeticCaptcha(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT);
|
||||
|
||||
String result = captcha.text();
|
||||
redisTemplate.opsForValue()
|
||||
.set(CacheConstants.DEFAULT_CODE_KEY + randomStr, result, SecurityConstants.CODE_TIME, TimeUnit.SECONDS);
|
||||
// 转换流信息写出
|
||||
captcha.out(response.getOutputStream());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pig.auth.support.filter;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.Mode;
|
||||
import cn.hutool.crypto.Padding;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.symmetric.AES;
|
||||
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pig.common.core.servlet.RepeatBodyRequestWrapper;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019 /2/1 密码解密工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PasswordDecoderFilter extends OncePerRequestFilter {
|
||||
|
||||
private final AuthSecurityConfigProperties authSecurityConfigProperties;
|
||||
|
||||
private static final String PASSWORD = "password";
|
||||
|
||||
private static final String KEY_ALGORITHM = "AES";
|
||||
|
||||
|
||||
static {
|
||||
// 关闭hutool 强制关闭Bouncy Castle库的依赖
|
||||
SecureUtil.disableBouncyCastle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
|
||||
// 不是登录请求,直接向下执行
|
||||
if (!StrUtil.containsAnyIgnoreCase(request.getRequestURI(), SecurityConstants.OAUTH_TOKEN_URL)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 将请求流转换为可多次读取的请求流
|
||||
RepeatBodyRequestWrapper requestWrapper = new RepeatBodyRequestWrapper(request);
|
||||
Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
|
||||
|
||||
// 构建前端对应解密AES 因子
|
||||
AES aes = new AES(Mode.CFB, Padding.NoPadding,
|
||||
new SecretKeySpec(authSecurityConfigProperties.getEncodeKey().getBytes(), KEY_ALGORITHM),
|
||||
new IvParameterSpec(authSecurityConfigProperties.getEncodeKey().getBytes()));
|
||||
|
||||
|
||||
parameterMap.forEach((k, v) -> {
|
||||
String[] values = parameterMap.get(k);
|
||||
if (!PASSWORD.equals(k) || ArrayUtil.isEmpty(values)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解密密码
|
||||
String decryptPassword = aes.decryptStr(values[0]);
|
||||
parameterMap.put(k, new String[]{decryptPassword});
|
||||
});
|
||||
chain.doFilter(requestWrapper, response);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package com.pig4cloud.pig.auth.support.filter;
|
||||
|
||||
/**
|
||||
* 登录前处理器
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2024/4/3
|
||||
*/
|
||||
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.pig4cloud.pig.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pig.common.core.exception.ValidateCodeException;
|
||||
import com.pig4cloud.pig.common.core.util.SpringContextHolder;
|
||||
import com.pig4cloud.pig.common.core.util.WebUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author lbw
|
||||
* @date 2024-01-06
|
||||
* <p>
|
||||
* 登录前置处理器: 前端密码传输密文解密,验证码处理
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ValidateCodeFilter extends OncePerRequestFilter {
|
||||
|
||||
private final AuthSecurityConfigProperties authSecurityConfigProperties;
|
||||
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
String requestUrl = request.getServletPath();
|
||||
|
||||
// 不是登录URL 请求直接跳过
|
||||
if (!SecurityConstants.OAUTH_TOKEN_URL.equals(requestUrl)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果登录URL 但是刷新token的请求,直接向下执行
|
||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 客户端配置跳过验证码
|
||||
boolean isIgnoreClient = authSecurityConfigProperties.getIgnoreClients().contains(WebUtils.getClientId());
|
||||
if (isIgnoreClient) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验验证码 1. 客户端开启验证码 2. 短信模式
|
||||
try {
|
||||
checkCode();
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (ValidateCodeException validateCodeException) {
|
||||
throw new OAuth2AuthenticationException(validateCodeException.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*/
|
||||
private void checkCode() throws ValidateCodeException {
|
||||
Optional<HttpServletRequest> request = WebUtils.getRequest();
|
||||
String code = request.get().getParameter("code");
|
||||
|
||||
if (StrUtil.isBlank(code)) {
|
||||
throw new ValidateCodeException("验证码不能为空");
|
||||
}
|
||||
|
||||
String randomStr = request.get().getParameter("randomStr");
|
||||
|
||||
// https://gitee.com/log4j/pig/issues/IWA0D
|
||||
String mobile = request.get().getParameter("mobile");
|
||||
if (StrUtil.isNotBlank(mobile)) {
|
||||
randomStr = mobile;
|
||||
}
|
||||
|
||||
String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
|
||||
RedisTemplate<String, String> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
|
||||
if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
|
||||
Object codeObj = redisTemplate.opsForValue().get(key);
|
||||
|
||||
if (codeObj == null) {
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
|
||||
String saveCode = codeObj.toString();
|
||||
if (StrUtil.isBlank(saveCode)) {
|
||||
redisTemplate.delete(key);
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
|
||||
if (!StrUtil.equals(saveCode, code)) {
|
||||
redisTemplate.delete(key);
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pig.common.core.servlet;
|
||||
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Request包装类:允许 body 重复读取
|
||||
*
|
||||
* @author Hccake
|
||||
*/
|
||||
@Slf4j
|
||||
public class RepeatBodyRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private final byte[] bodyByteArray;
|
||||
|
||||
private final Map<String, String[]> parameterMap;
|
||||
|
||||
public RepeatBodyRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
this.bodyByteArray = getByteBody(request);
|
||||
this.parameterMap = super.getParameterMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() {
|
||||
return ObjectUtils.isEmpty(this.bodyByteArray) ? null
|
||||
: new BufferedReader(new InputStreamReader(getInputStream()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() {
|
||||
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.bodyByteArray);
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
// doNoting
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return byteArrayInputStream.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static byte[] getByteBody(HttpServletRequest request) {
|
||||
byte[] body = new byte[0];
|
||||
try {
|
||||
body = StreamUtils.copyToByteArray(request.getInputStream());
|
||||
} catch (IOException e) {
|
||||
log.error("解析流中数据异常", e);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写 getParameterMap() 方法 解决 undertow 中流被读取后,会进行标记,从而导致无法正确获取 body 中的表单数据的问题
|
||||
*
|
||||
* @return Map<String, String [ ]> parameterMap
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
return this.parameterMap;
|
||||
}
|
||||
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package com.pig4cloud.pig.gateway.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/10/4
|
||||
* <p>
|
||||
* 网关配置文件
|
||||
*/
|
||||
@Data
|
||||
@RefreshScope
|
||||
@ConfigurationProperties("gateway")
|
||||
public class GatewayConfigProperties {
|
||||
|
||||
/**
|
||||
* 网关解密登录前端密码 秘钥 {@link com.pig4cloud.pig.gateway.filter.PasswordDecoderFilter}
|
||||
*/
|
||||
private String encodeKey;
|
||||
|
||||
/**
|
||||
* 网关不需要校验验证码的客户端 {@link com.pig4cloud.pig.gateway.filter.ValidateCodeGatewayFilter}
|
||||
*/
|
||||
private List<String> ignoreClients;
|
||||
|
||||
}
|
@ -1,16 +1,10 @@
|
||||
package com.pig4cloud.pig.gateway.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pig4cloud.pig.gateway.filter.PasswordDecoderFilter;
|
||||
import com.pig4cloud.pig.gateway.filter.PigRequestGlobalFilter;
|
||||
import com.pig4cloud.pig.gateway.filter.ValidateCodeGatewayFilter;
|
||||
import com.pig4cloud.pig.gateway.handler.GlobalExceptionHandler;
|
||||
import com.pig4cloud.pig.gateway.handler.ImageCodeHandler;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
/**
|
||||
* 网关配置
|
||||
@ -18,21 +12,8 @@ import org.springframework.data.redis.core.RedisTemplate;
|
||||
* @author L.cm
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(GatewayConfigProperties.class)
|
||||
public class GatewayConfiguration {
|
||||
|
||||
/**
|
||||
* 创建密码解码器过滤器
|
||||
* @param modifyRequestBodyGatewayFilterFactory 修改请求体网关过滤器工厂
|
||||
* @param configProperties 配置属性
|
||||
* @return 密码解码器过滤器
|
||||
*/
|
||||
@Bean
|
||||
public PasswordDecoderFilter passwordDecoderFilter(
|
||||
ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory,
|
||||
GatewayConfigProperties configProperties) {
|
||||
return new PasswordDecoderFilter(modifyRequestBodyGatewayFilterFactory, configProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建PigRequest全局过滤器
|
||||
@ -43,17 +24,6 @@ public class GatewayConfiguration {
|
||||
return new PigRequestGlobalFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建验证码网关过滤器
|
||||
* @param configProperties 配置属性
|
||||
* @param redisTemplate Redis模板
|
||||
* @return 验证码网关过滤器
|
||||
*/
|
||||
@Bean
|
||||
public ValidateCodeGatewayFilter validateCodeGatewayFilter(GatewayConfigProperties configProperties,
|
||||
RedisTemplate redisTemplate) {
|
||||
return new ValidateCodeGatewayFilter(configProperties, redisTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建全局异常处理程序
|
||||
@ -65,14 +35,5 @@ public class GatewayConfiguration {
|
||||
return new GlobalExceptionHandler(objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建图片验证码处理器
|
||||
* @param redisTemplate Redis模板
|
||||
* @return 图片验证码处理器
|
||||
*/
|
||||
@Bean
|
||||
public ImageCodeHandler imageCodeHandler(RedisTemplate redisTemplate) {
|
||||
return new ImageCodeHandler(redisTemplate);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pig.gateway.config;
|
||||
|
||||
import com.pig4cloud.pig.gateway.handler.ImageCodeHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
/**
|
||||
* 路由配置信息
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2020-06-11
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@RequiredArgsConstructor
|
||||
public class RouterFunctionConfiguration {
|
||||
|
||||
private final ImageCodeHandler imageCodeHandler;
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> routerFunction() {
|
||||
return RouterFunctions.route(
|
||||
RequestPredicates.path("/code/image").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
|
||||
imageCodeHandler);
|
||||
}
|
||||
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pig.gateway.filter;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.Mode;
|
||||
import cn.hutool.crypto.Padding;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.symmetric.AES;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pig.gateway.config.GatewayConfigProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2019 /2/1 密码解密工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PasswordDecoderFilter extends AbstractGatewayFilterFactory {
|
||||
|
||||
private final ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;
|
||||
|
||||
private static final String PASSWORD = "password";
|
||||
|
||||
private static final String KEY_ALGORITHM = "AES";
|
||||
|
||||
private final GatewayConfigProperties gatewayConfig;
|
||||
|
||||
static {
|
||||
// 关闭hutool 强制关闭Bouncy Castle库的依赖
|
||||
SecureUtil.disableBouncyCastle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Object config) {
|
||||
return (exchange, chain) -> {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
// 不是登录请求,直接向下执行
|
||||
if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
return modifyRequestBodyFilter
|
||||
.apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(String.class, String.class,
|
||||
(webExchange, body) -> Mono.just(modifyRequestPassword(body))))
|
||||
.filter(exchange, chain);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改请求报文的密码密文为名为
|
||||
* @param requestBody 请求报文
|
||||
* @return 修改后的报文
|
||||
*/
|
||||
private String modifyRequestPassword(String requestBody) {
|
||||
// 构建前端对应解密AES 因子
|
||||
AES aes = new AES(Mode.CFB, Padding.NoPadding,
|
||||
new SecretKeySpec(gatewayConfig.getEncodeKey().getBytes(), KEY_ALGORITHM),
|
||||
new IvParameterSpec(gatewayConfig.getEncodeKey().getBytes()));
|
||||
|
||||
// 获取请求密码并解密
|
||||
Map<String, String> inParamsMap = HttpUtil.decodeParamMap(requestBody, CharsetUtil.CHARSET_UTF_8);
|
||||
if (inParamsMap.containsKey(PASSWORD)) {
|
||||
String password = aes.decryptStr(inParamsMap.get(PASSWORD));
|
||||
// 返回修改后报文字符
|
||||
inParamsMap.put(PASSWORD, password);
|
||||
}
|
||||
|
||||
return HttpUtil.toParams(inParamsMap, Charset.defaultCharset(), true);
|
||||
}
|
||||
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pig.gateway.filter;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pig4cloud.pig.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pig.common.core.exception.ValidateCodeException;
|
||||
import com.pig4cloud.pig.common.core.util.WebUtils;
|
||||
import com.pig4cloud.pig.gateway.config.GatewayConfigProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The type Validate code gateway filter.
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2018 /7/4 验证码处理
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory<Object> {
|
||||
|
||||
private final GatewayConfigProperties configProperties;
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 应用网关过滤器
|
||||
* @param config 配置对象
|
||||
* @return 网关过滤器
|
||||
*/
|
||||
@Override
|
||||
public GatewayFilter apply(Object config) {
|
||||
|
||||
return (exchange, chain) -> {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
// 不是登录请求,直接向下执行
|
||||
if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 客户端配置跳过,直接向下执行
|
||||
boolean isIgnoreClient = configProperties.getIgnoreClients().contains(WebUtils.getClientId(request));
|
||||
if (isIgnoreClient) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 构建缓存body,可重复读获取form data
|
||||
return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> {
|
||||
// get cacheRequestBody
|
||||
DataBuffer cachedRequestBody = exchange.getAttribute("cachedRequestBody");
|
||||
CharBuffer charBuffer = StandardCharsets.UTF_8
|
||||
.decode(Objects.requireNonNull(cachedRequestBody).asByteBuffer());
|
||||
Map<String, String> requestBodyMap = HttpUtil.decodeParamMap(charBuffer.toString(),
|
||||
CharsetUtil.CHARSET_UTF_8);
|
||||
// 刷新请求跳过,直接向下执行
|
||||
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, requestBodyMap.get("grant_type"))) {
|
||||
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
|
||||
}
|
||||
|
||||
// 根据 randomStr 参数判断验证码是否正常
|
||||
String code = requestBodyMap.get("code");
|
||||
String randomStr = requestBodyMap.getOrDefault("randomStr",
|
||||
requestBodyMap.get(SecurityConstants.SMS_PARAMETER_NAME));
|
||||
checkCode(code, randomStr);
|
||||
|
||||
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查验证码,错误扔出 ValidateCodeException GlobalExceptionHandler统一处理
|
||||
* @param code 验证码
|
||||
* @param randomStr 请求参数
|
||||
* @throws ValidateCodeException 验证码异常
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void checkCode(String code, String randomStr) {
|
||||
if (CharSequenceUtil.isBlank(code)) {
|
||||
throw new ValidateCodeException("验证码不能为空");
|
||||
}
|
||||
|
||||
String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
|
||||
|
||||
Object codeObj = redisTemplate.opsForValue().get(key);
|
||||
|
||||
if (ObjectUtil.isEmpty(codeObj) || !code.equals(codeObj)) {
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pig.gateway.handler;
|
||||
|
||||
import com.pig4cloud.captcha.ArithmeticCaptcha;
|
||||
import com.pig4cloud.pig.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.FastByteArrayOutputStream;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2018/7/5 验证码生成逻辑处理类
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ImageCodeHandler implements HandlerFunction<ServerResponse> {
|
||||
|
||||
private static final Integer DEFAULT_IMAGE_WIDTH = 100;
|
||||
|
||||
private static final Integer DEFAULT_IMAGE_HEIGHT = 40;
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Override
|
||||
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
|
||||
ArithmeticCaptcha captcha = new ArithmeticCaptcha(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT);
|
||||
|
||||
String result = captcha.text();
|
||||
|
||||
// 保存验证码信息
|
||||
Optional<String> randomStr = serverRequest.queryParam("randomStr");
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
randomStr.ifPresent(s -> redisTemplate.opsForValue()
|
||||
.set(CacheConstants.DEFAULT_CODE_KEY + s, result, SecurityConstants.CODE_TIME, TimeUnit.SECONDS));
|
||||
|
||||
// 转换流信息写出
|
||||
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
|
||||
captcha.out(os);
|
||||
|
||||
return ServerResponse.status(HttpStatus.OK)
|
||||
.contentType(MediaType.IMAGE_JPEG)
|
||||
.body(BodyInserters.fromResource(new ByteArrayResource(os.toByteArray())));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user