♻️ Refactoring code. 重构 PasswordDecoderFilter ValidateCodeFilter 代码至 pig-auth 模块简化网关逻辑

This commit is contained in:
冷冷 2024-04-04 21:12:29 +08:00
parent 4b03d61d18
commit baa890379b
10 changed files with 371 additions and 420 deletions

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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())));
}
}