【7.0.4】【captcha】整理验证码

pull/22/head
fengshuonan 2021-07-05 15:45:30 +08:00
parent 25fab57768
commit 9ec129809b
13 changed files with 533 additions and 25 deletions

View File

@ -87,4 +87,9 @@ public interface RuleConstants {
*/
String TENANT_DB_PREFIX = "sys_tenant_db_";
/**
* base64<img src=""/>使
*/
String BASE64_IMG_PREFIX = "data:image/png;base64,";
}

View File

@ -55,7 +55,7 @@ import cn.stylefeng.roses.kernel.jwt.api.pojo.payload.DefaultJwtPayload;
import cn.stylefeng.roses.kernel.log.api.LoginLogServiceApi;
import cn.stylefeng.roses.kernel.message.api.expander.WebSocketConfigExpander;
import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil;
import cn.stylefeng.roses.kernel.security.api.CaptchaApi;
import cn.stylefeng.roses.kernel.security.api.ImageCaptchaApi;
import cn.stylefeng.roses.kernel.system.api.UserServiceApi;
import cn.stylefeng.roses.kernel.system.api.enums.UserStatusEnum;
import cn.stylefeng.roses.kernel.system.api.expander.SystemConfigExpander;
@ -102,7 +102,7 @@ public class AuthServiceImpl implements AuthServiceApi {
private LoginLogServiceApi loginLogServiceApi;
@Resource
private CaptchaApi captchaApi;
private ImageCaptchaApi captchaApi;
@Resource
private SsoProperties ssoProperties;

View File

@ -0,0 +1,53 @@
/*
* Copyright [2020-2030] [https://www.stylefeng.cn]
*
* 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.
*
* GunsAPACHE LICENSE 2.0使
*
* 1.LICENSE
* 2.Guns
* 3.
* 4. https://gitee.com/stylefeng/guns
* 5. https://gitee.com/stylefeng/guns
* 6.
*/
package cn.stylefeng.roses.kernel.security.api;
import cn.stylefeng.roses.kernel.security.api.pojo.DragCaptchaImageDTO;
/**
*
*
* @author fengshuonan
* @date 2021/7/5 12:05
*/
public interface DragCaptchaApi {
/**
*
*
* @author fengshuonan
* @date 2021/7/5 11:55
*/
DragCaptchaImageDTO createCaptcha();
/**
*
*
* @author fengshuonan
* @date 2021/7/5 11:55
*/
boolean validateCaptcha(String verKey, Integer verCode);
}

View File

@ -24,7 +24,7 @@
*/
package cn.stylefeng.roses.kernel.security.api;
import cn.stylefeng.roses.kernel.security.api.pojo.EasyCaptcha;
import cn.stylefeng.roses.kernel.security.api.pojo.ImageCaptcha;
/**
* Api
@ -34,7 +34,7 @@ import cn.stylefeng.roses.kernel.security.api.pojo.EasyCaptcha;
* @author chenjinlong
* @date 2021/1/15 13:46
*/
public interface CaptchaApi {
public interface ImageCaptchaApi {
/**
*
@ -42,7 +42,7 @@ public interface CaptchaApi {
* @author chenjinlong
* @date 2021/1/15 12:38
*/
EasyCaptcha captcha();
ImageCaptcha captcha();
/**
*

View File

@ -39,9 +39,9 @@ import lombok.Getter;
public enum SecurityExceptionEnum implements AbstractExceptionEnum {
/**
* xxx
*
*/
SECURITY_EXPIRED_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + SecurityConstants.SECURITY_EXCEPTION_STEP_CODE + "01", "安全模块异常");
CAPTCHA_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + SecurityConstants.SECURITY_EXCEPTION_STEP_CODE + "01", "生成验证码错误");
/**
*

View File

@ -0,0 +1,48 @@
package cn.stylefeng.roses.kernel.security.api.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* dto
*
* @author fengshuonan
* @date 2021/7/5 14:10
*/
@Data
@AllArgsConstructor
public class DragCaptchaImageDTO {
/**
* key
*/
private String key;
/**
* base64
*/
private String srcImage;
/**
* base64
*/
private String cutImage;
/**
* x
*/
private Integer locationX;
/**
* y
*/
private Integer locationY;
public DragCaptchaImageDTO(String srcImage, String cutImage, int locationX, int locationY) {
this.srcImage = srcImage;
this.cutImage = cutImage;
this.locationX = locationX;
this.locationY = locationY;
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright [2020-2030] [https://www.stylefeng.cn]
*
* 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.
*
* GunsAPACHE LICENSE 2.0使
*
* 1.LICENSE
* 2.Guns
* 3.
* 4. https://gitee.com/stylefeng/guns
* 5. https://gitee.com/stylefeng/guns
* 6.
*/
package cn.stylefeng.roses.kernel.security.captcha;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi;
import cn.stylefeng.roses.kernel.security.api.DragCaptchaApi;
import cn.stylefeng.roses.kernel.security.api.exception.SecurityException;
import cn.stylefeng.roses.kernel.security.api.exception.enums.SecurityExceptionEnum;
import cn.stylefeng.roses.kernel.security.api.pojo.DragCaptchaImageDTO;
import cn.stylefeng.roses.kernel.security.captcha.util.DragCaptchaImageUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
*
*
* @author fengshuonan
* @date 2021/7/5 11:34
*/
public class DragCaptchaService implements DragCaptchaApi {
private final CacheOperatorApi<String> cacheOperatorApi;
public DragCaptchaService(CacheOperatorApi<String> cacheOperatorApi) {
this.cacheOperatorApi = cacheOperatorApi;
}
public DragCaptchaImageDTO createCaptcha() {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.decode(DragCaptchaImageUtil.IMAGE_BASE64));
try {
DragCaptchaImageDTO dragCaptchaImageDTO = DragCaptchaImageUtil.getVerifyImage(byteArrayInputStream);
// 缓存x轴坐标
String verKey = IdUtil.simpleUUID();
Integer verValue = dragCaptchaImageDTO.getLocationX();
cacheOperatorApi.put(verKey, verValue.toString());
// 清空x轴坐标
dragCaptchaImageDTO.setKey(verKey);
dragCaptchaImageDTO.setLocationX(null);
return dragCaptchaImageDTO;
} catch (IOException e) {
throw new SecurityException(SecurityExceptionEnum.CAPTCHA_ERROR);
} finally {
IoUtil.close(byteArrayInputStream);
}
}
public boolean validateCaptcha(String verKey, Integer verScope) {
if (StrUtil.isEmpty(verKey)) {
return false;
}
if (verScope == null) {
return false;
}
// 获取缓存中存储的范围
Integer locationX = Convert.toInt(cacheOperatorApi.get(verKey));
int beginScope = locationX - 5;
int endScope = locationX + 5;
// 每次验证不管成功和失败都剔除掉key
cacheOperatorApi.remove(verKey);
// 验证缓存中的范围值
return verScope >= beginScope && verScope <= endScope;
}
}

View File

@ -27,8 +27,8 @@ package cn.stylefeng.roses.kernel.security.captcha;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.stylefeng.roses.kernel.cache.api.CacheOperatorApi;
import cn.stylefeng.roses.kernel.security.api.CaptchaApi;
import cn.stylefeng.roses.kernel.security.api.pojo.EasyCaptcha;
import cn.stylefeng.roses.kernel.security.api.ImageCaptchaApi;
import cn.stylefeng.roses.kernel.security.api.pojo.ImageCaptcha;
import com.wf.captcha.SpecCaptcha;
/**
@ -37,21 +37,21 @@ import com.wf.captcha.SpecCaptcha;
* @author chenjinlong
* @date 2021/1/15 13:44
*/
public class CaptchaService implements CaptchaApi {
public class ImageCaptchaService implements ImageCaptchaApi {
private final CacheOperatorApi<String> cacheOperatorApi;
public CaptchaService(CacheOperatorApi<String> cacheOperatorApi) {
public ImageCaptchaService(CacheOperatorApi<String> cacheOperatorApi) {
this.cacheOperatorApi = cacheOperatorApi;
}
@Override
public EasyCaptcha captcha() {
public ImageCaptcha captcha() {
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
String verCode = specCaptcha.text().toLowerCase();
String verKey = IdUtil.simpleUUID();
cacheOperatorApi.put(verKey, verCode);
return EasyCaptcha.builder().verImage(specCaptcha.toBase64()).verKey(verKey).build();
return ImageCaptcha.builder().verImage(specCaptcha.toBase64()).verKey(verKey).build();
}
@Override

View File

@ -26,8 +26,10 @@ package cn.stylefeng.roses.kernel.security.starter;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.stylefeng.roses.kernel.security.api.CaptchaApi;
import cn.stylefeng.roses.kernel.security.captcha.CaptchaService;
import cn.stylefeng.roses.kernel.security.api.DragCaptchaApi;
import cn.stylefeng.roses.kernel.security.api.ImageCaptchaApi;
import cn.stylefeng.roses.kernel.security.captcha.DragCaptchaService;
import cn.stylefeng.roses.kernel.security.captcha.ImageCaptchaService;
import cn.stylefeng.roses.kernel.security.captcha.cache.CaptchaMemoryCache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@ -49,12 +51,27 @@ public class CaptchaAutoConfiguration {
* @date 2021/1/15 11:25
*/
@Bean
@ConditionalOnMissingBean(CaptchaApi.class)
public CaptchaApi captchaApi() {
@ConditionalOnMissingBean(ImageCaptchaApi.class)
public ImageCaptchaApi captchaApi() {
// 验证码过期时间 120秒
TimedCache<String, String> timedCache = CacheUtil.newTimedCache(1000 * 120);
CaptchaMemoryCache captchaMemoryCache = new CaptchaMemoryCache(timedCache);
return new CaptchaService(captchaMemoryCache);
return new ImageCaptchaService(captchaMemoryCache);
}
/**
*
*
* @author fengshuonan
* @date 2021/7/5 11:57
*/
@Bean
@ConditionalOnMissingBean(DragCaptchaApi.class)
public DragCaptchaApi dragCaptchaService() {
// 验证码过期时间 120秒
TimedCache<String, String> timedCache = CacheUtil.newTimedCache(1000 * 120);
CaptchaMemoryCache captchaMemoryCache = new CaptchaMemoryCache(timedCache);
return new DragCaptchaService(captchaMemoryCache);
}
}

View File

@ -31,7 +31,7 @@ import cn.hutool.core.util.StrUtil;
import cn.stylefeng.roses.kernel.db.api.factory.PageFactory;
import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory;
import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult;
import cn.stylefeng.roses.kernel.security.api.CaptchaApi;
import cn.stylefeng.roses.kernel.security.api.ImageCaptchaApi;
import cn.stylefeng.roses.kernel.sms.api.SmsSenderApi;
import cn.stylefeng.roses.kernel.sms.api.exception.SmsException;
import cn.stylefeng.roses.kernel.sms.api.expander.SmsConfigExpander;
@ -75,7 +75,7 @@ public class SysSmsInfoServiceImpl extends ServiceImpl<SysSmsMapper, SysSms> imp
private SmsSenderApi smsSenderApi;
@Resource
private CaptchaApi captchaApi;
private ImageCaptchaApi captchaApi;
@Transactional(rollbackFor = Exception.class)
@Override

View File

@ -28,12 +28,16 @@ import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData;
import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData;
import cn.stylefeng.roses.kernel.scanner.api.annotation.ApiResource;
import cn.stylefeng.roses.kernel.scanner.api.annotation.GetResource;
import cn.stylefeng.roses.kernel.security.api.CaptchaApi;
import cn.stylefeng.roses.kernel.security.api.pojo.EasyCaptcha;
import cn.stylefeng.roses.kernel.security.api.DragCaptchaApi;
import cn.stylefeng.roses.kernel.security.api.ImageCaptchaApi;
import cn.stylefeng.roses.kernel.security.api.pojo.DragCaptchaImageDTO;
import cn.stylefeng.roses.kernel.security.api.pojo.ImageCaptcha;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import static cn.stylefeng.roses.kernel.rule.constants.RuleConstants.BASE64_IMG_PREFIX;
/**
*
*
@ -45,11 +49,34 @@ import javax.annotation.Resource;
public class KaptchaController {
@Resource
private CaptchaApi captchaApi;
private ImageCaptchaApi captchaApi;
@GetResource(name = "获取图形验证码", path = "/captcha", requiredPermission = false, requiredLogin = false, responseClass = EasyCaptcha.class)
@Resource
private DragCaptchaApi dragCaptchaApi;
/**
*
*
* @author fengshuonan
* @date 2021/7/5 12:00
*/
@GetResource(name = "获取图形验证码", path = "/captcha", requiredPermission = false, requiredLogin = false, responseClass = ImageCaptcha.class)
public ResponseData captcha() {
return new SuccessResponseData(captchaApi.captcha());
}
/**
*
*
* @author fengshuonan
* @date 2021/7/5 12:00
*/
@GetResource(name = "获取图形验证码", path = "/dragCaptcha", requiredPermission = false, requiredLogin = false, responseClass = DragCaptchaImageDTO.class)
public ResponseData dragCaptcha() {
DragCaptchaImageDTO captcha = dragCaptchaApi.createCaptcha();
captcha.setSrcImage(BASE64_IMG_PREFIX + captcha.getSrcImage());
captcha.setCutImage(BASE64_IMG_PREFIX + captcha.getCutImage());
return new SuccessResponseData(captcha);
}
}