diff --git a/_web/.eslintrc.js b/_web/.eslintrc.js
index 5bece06c..e4f4dae9 100644
--- a/_web/.eslintrc.js
+++ b/_web/.eslintrc.js
@@ -56,7 +56,9 @@ module.exports = {
}
],
'template-curly-spacing': 'off',
- 'indent': 'off'
+ 'indent': 'off',
+ "space-before-function-paren": 0,
+ 'no-multi-spaces': 2, //不能用多余的空格
},
parserOptions: {
parser: 'babel-eslint'
diff --git a/_web/package.json b/_web/package.json
index e6b8ba26..9f92061c 100644
--- a/_web/package.json
+++ b/_web/package.json
@@ -16,6 +16,7 @@
"babel-polyfill": "^6.26.0",
"clipboard": "^2.0.6",
"core-js": "^3.1.2",
+ "crypto-js": "^4.0.0",
"default-passive-events": "^1.0.10",
"enquire.js": "^2.1.6",
"font-awesome": "^4.7.0",
diff --git a/_web/src/api/modular/system/loginManage.js b/_web/src/api/modular/system/loginManage.js
index 69fababc..df575074 100644
--- a/_web/src/api/modular/system/loginManage.js
+++ b/_web/src/api/modular/system/loginManage.js
@@ -75,3 +75,17 @@ export function getSmsCaptcha (parameter) {
params: parameter
})
}
+
+
+/**
+ * 获取验证码开关
+ * @author Jax
+ * @param parameter
+ */
+export function getCaptchaOpen (parameter) {
+ return axios({
+ url: '/getCaptchaOpen',
+ method: 'get',
+ params: parameter
+ })
+}
\ No newline at end of file
diff --git a/_web/src/components/verifition/Verify.vue b/_web/src/components/verifition/Verify.vue
new file mode 100644
index 00000000..9c6f32bd
--- /dev/null
+++ b/_web/src/components/verifition/Verify.vue
@@ -0,0 +1,465 @@
+
+
+
+
+ 请完成安全验证
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_web/src/components/verifition/Verify/VerifyPoints.vue b/_web/src/components/verifition/Verify/VerifyPoints.vue
new file mode 100644
index 00000000..5fd915c4
--- /dev/null
+++ b/_web/src/components/verifition/Verify/VerifyPoints.vue
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+
+
![]()
+
+
+ {{index + 1}}
+
+
+
+
+
+ {{text}}
+
+
+
+
\ No newline at end of file
diff --git a/_web/src/components/verifition/Verify/VerifySlide.vue b/_web/src/components/verifition/Verify/VerifySlide.vue
new file mode 100644
index 00000000..e6b706c3
--- /dev/null
+++ b/_web/src/components/verifition/Verify/VerifySlide.vue
@@ -0,0 +1,347 @@
+
+
+
+
+
![]()
+
+
+
+ {{tipWords}}
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
diff --git a/_web/src/components/verifition/api/index.js b/_web/src/components/verifition/api/index.js
new file mode 100644
index 00000000..c0eb28e7
--- /dev/null
+++ b/_web/src/components/verifition/api/index.js
@@ -0,0 +1,22 @@
+/**
+ * 此处可直接引用自己项目封装好的 axios 配合后端联调
+ */
+import { axios } from '@/utils/request'
+
+// 获取验证图片 以及token
+export function reqGet(data) {
+ return axios({
+ url: '/captcha/code',
+ method: 'get',
+ data
+ })
+}
+
+// 滑动或者点选验证
+export function reqCheck(data) {
+ return axios({
+ url: '/captcha/code/check',
+ method: 'post',
+ data
+ })
+}
diff --git a/_web/src/components/verifition/utils/ase.js b/_web/src/components/verifition/utils/ase.js
new file mode 100644
index 00000000..4c1c5594
--- /dev/null
+++ b/_web/src/components/verifition/utils/ase.js
@@ -0,0 +1,11 @@
+import CryptoJS from 'crypto-js'
+/**
+ * @word 要加密的内容
+ * @keyWord String 服务器随机返回的关键字
+ * */
+export function aesEncrypt(word,keyWord="XwKsGlMcdPMEhR1B"){
+ var key = CryptoJS.enc.Utf8.parse(keyWord);
+ var srcs = CryptoJS.enc.Utf8.parse(word);
+ var encrypted = CryptoJS.AES.encrypt(srcs, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
+ return encrypted.toString();
+}
diff --git a/_web/src/components/verifition/utils/axios.js b/_web/src/components/verifition/utils/axios.js
new file mode 100644
index 00000000..610d4032
--- /dev/null
+++ b/_web/src/components/verifition/utils/axios.js
@@ -0,0 +1,30 @@
+import axios from 'axios';
+
+axios.defaults.baseURL = process.env.BASE_API;
+
+const service = axios.create({
+ timeout: 40000,
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Content-Type': 'application/json; charset=UTF-8'
+ },
+})
+service.interceptors.request.use(
+ config => {
+ return config
+ },
+ error => {
+ Promise.reject(error)
+ }
+)
+
+// response interceptor
+service.interceptors.response.use(
+ response => {
+ const res = response.data;
+ return res
+ },
+ error => {
+ }
+)
+export default service
diff --git a/_web/src/components/verifition/utils/util.js b/_web/src/components/verifition/utils/util.js
new file mode 100644
index 00000000..55f65d42
--- /dev/null
+++ b/_web/src/components/verifition/utils/util.js
@@ -0,0 +1,36 @@
+export function resetSize(vm) {
+ var img_width, img_height, bar_width, bar_height; //图片的宽度、高度,移动条的宽度、高度
+
+ var parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth
+ var parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight
+
+ if (vm.imgSize.width.indexOf('%') != -1) {
+ img_width = parseInt(this.imgSize.width) / 100 * parentWidth + 'px'
+ } else {
+ img_width = this.imgSize.width;
+ }
+
+ if (vm.imgSize.height.indexOf('%') != -1) {
+ img_height = parseInt(this.imgSize.height) / 100 * parentHeight + 'px'
+ } else {
+ img_height = this.imgSize.height
+ }
+
+ if (vm.barSize.width.indexOf('%') != -1) {
+ bar_width = parseInt(this.barSize.width) / 100 * parentWidth + 'px'
+ } else {
+ bar_width = this.barSize.width
+ }
+
+ if (vm.barSize.height.indexOf('%') != -1) {
+ bar_height = parseInt(this.barSize.height) / 100 * parentHeight + 'px'
+ } else {
+ bar_height = this.barSize.height
+ }
+
+ return {imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height}
+}
+
+export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
+export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']
\ No newline at end of file
diff --git a/_web/src/views/userLoginReg/Login.vue b/_web/src/views/userLoginReg/Login.vue
index 1ceec988..ffba2345 100644
--- a/_web/src/views/userLoginReg/Login.vue
+++ b/_web/src/views/userLoginReg/Login.vue
@@ -82,6 +82,16 @@
>忘记密码
+
+
+
+
import TwoStepCaptcha from '@/components/tools/TwoStepCaptcha'
import { mapActions } from 'vuex'
-import { getSmsCaptcha } from '@/api/modular/system/loginManage'
+import { getSmsCaptcha, getCaptchaOpen } from '@/api/modular/system/loginManage'
+import Verify from '@/components/verifition/Verify'
export default {
components: {
- TwoStepCaptcha
+ TwoStepCaptcha,
+ Verify
},
data () {
return {
@@ -145,13 +157,27 @@ export default {
},
accountLoginErrMsg: '',
tenantOpen: false,
- tenantsList: []
+ captchaOpen: false, // 是否开启验证码
+ tenantsList: [],
+ loginParams: [] // 登录参数
+
}
},
created () {
+ this.getCaptchaOpen()
},
methods: {
...mapActions(['Login', 'Logout', 'dictTypeData']),
+ /**
+ * 获取验证码开关
+ */
+ getCaptchaOpen () {
+ getCaptchaOpen().then((res) => {
+ if (res.success) {
+ this.captchaOpen = res.data
+ }
+ })
+ },
// handler
handleUsernameOrEmail (rule, value, callback) {
const { state } = this
@@ -185,6 +211,12 @@ export default {
}
validateFields(validateFieldsKey, { force: true }, (err, values) => {
if (!err) {
+ this.loginParams = values
+ // 是否开启验证码
+ if (this.captchaOpen) {
+ this.$refs.verify.show()
+ return
+ }
const loginParams = { ...values }
delete loginParams.account
loginParams[!state.loginType ? 'email' : 'account'] = values.account
@@ -205,6 +237,17 @@ export default {
}
})
},
+ /**
+ * 获取验证码
+ */
+ verifySuccess(params) {
+ this.loginParams.code = params.captchaVerification
+ this.Login(this.loginParams).then((res) => this.loginSuccess(res))
+ .catch(err => this.requestFailed(err))
+ .finally(() => {
+ this.state.loginBtn = false
+ })
+ },
getCaptcha (e) {
e.preventDefault()
const { form: { validateFields }, state } = this
diff --git a/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/consts/CommonConstant.java b/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/consts/CommonConstant.java
index 73768302..0835516b 100644
--- a/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/consts/CommonConstant.java
+++ b/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/consts/CommonConstant.java
@@ -111,4 +111,9 @@ public interface CommonConstant {
* 数据库链接URL标识
*/
String DATABASE_URL_NAME = "DATABASE_URL_NAME";
+
+ /**
+ * 滑块验证码
+ */
+ String IMAGE_CODE_TYPE = "blockPuzzle";
}
diff --git a/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/consts/SpringSecurityConstant.java b/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/consts/SpringSecurityConstant.java
index 3034b5a2..de171a98 100644
--- a/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/consts/SpringSecurityConstant.java
+++ b/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/consts/SpringSecurityConstant.java
@@ -63,6 +63,10 @@ public interface SpringSecurityConstant {
//druid的
"/druid/**",
+ //获取验证码
+ "/captcha/**",
+ "/getCaptchaOpen",
+
};
}
diff --git a/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/context/constant/ConstantContextHolder.java b/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/context/constant/ConstantContextHolder.java
index bbf9b145..2213f95e 100644
--- a/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/context/constant/ConstantContextHolder.java
+++ b/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/context/constant/ConstantContextHolder.java
@@ -32,6 +32,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import com.cn.xiaonuo.core.consts.CommonConstant;
import com.cn.xiaonuo.core.consts.SymbolConstant;
+import com.cn.xiaonuo.core.enums.YesOrNotEnum;
import com.cn.xiaonuo.core.exception.ServiceException;
import com.cn.xiaonuo.core.pojo.email.EmailConfigs;
import com.cn.xiaonuo.core.pojo.oauth.OauthConfigs;
@@ -390,4 +391,13 @@ public class ConstantContextHolder {
}
}
+ /**
+ * @Description 获取验证码 开关标识
+ * @Date 2021/1/21 15:22
+ * @author Jax
+ * @return Boolean
+ **/
+ public static Boolean getCaptchaOpenFlag() {
+ return getSysConfigWithDefault("XIAONUO_CAPTCHA_OPEN", Boolean.class, true);
+ }
}
diff --git a/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/exception/enums/AuthExceptionEnum.java b/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/exception/enums/AuthExceptionEnum.java
index 2f31c7c3..d3ecac23 100644
--- a/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/exception/enums/AuthExceptionEnum.java
+++ b/xiaonuo-base/xiaonuo-core/src/main/java/com/cn/xiaonuo/core/exception/enums/AuthExceptionEnum.java
@@ -86,7 +86,14 @@ public enum AuthExceptionEnum implements AbstractBaseExceptionEnum {
/**
* 无登录用户
*/
- NO_LOGIN_USER(9, "无登录用户");
+ NO_LOGIN_USER(9, "无登录用户"),
+
+ /**
+ * 验证码错误
+ */
+ CONSTANT_EMPTY_ERROR(10, "验证码错误"),
+
+ ;
private final Integer code;
diff --git a/xiaonuo-base/xiaonuo-system/pom.xml b/xiaonuo-base/xiaonuo-system/pom.xml
index 4ad775e8..6945e897 100644
--- a/xiaonuo-base/xiaonuo-system/pom.xml
+++ b/xiaonuo-base/xiaonuo-system/pom.xml
@@ -85,6 +85,14 @@
com.github.xiaoymin
swagger-bootstrap-ui
+
+
+
+ com.github.anji-plus
+ captcha-spring-boot-starter
+ 1.2.6
+
+
diff --git a/xiaonuo-base/xiaonuo-system/src/main/java/com/cn/xiaonuo/sys/modular/auth/controller/SysLoginController.java b/xiaonuo-base/xiaonuo-system/src/main/java/com/cn/xiaonuo/sys/modular/auth/controller/SysLoginController.java
index 57d8ae06..ef590296 100644
--- a/xiaonuo-base/xiaonuo-system/src/main/java/com/cn/xiaonuo/sys/modular/auth/controller/SysLoginController.java
+++ b/xiaonuo-base/xiaonuo-system/src/main/java/com/cn/xiaonuo/sys/modular/auth/controller/SysLoginController.java
@@ -25,8 +25,14 @@ XiaoNuo采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注
package com.cn.xiaonuo.sys.modular.auth.controller;
import cn.hutool.core.lang.Dict;
+import com.anji.captcha.model.common.ResponseModel;
+import com.anji.captcha.model.vo.CaptchaVO;
+import com.anji.captcha.service.CaptchaService;
+import com.cn.xiaonuo.core.consts.CommonConstant;
import com.cn.xiaonuo.core.context.constant.ConstantContextHolder;
import com.cn.xiaonuo.core.context.login.LoginContextHolder;
+import com.cn.xiaonuo.core.exception.AuthException;
+import com.cn.xiaonuo.core.exception.enums.AuthExceptionEnum;
import com.cn.xiaonuo.core.pojo.response.ResponseData;
import com.cn.xiaonuo.core.pojo.response.SuccessResponseData;
import com.cn.xiaonuo.sys.modular.auth.service.AuthService;
@@ -49,6 +55,9 @@ public class SysLoginController {
@Resource
private AuthService authService;
+ @Resource
+ private CaptchaService captchaService;
+
/**
* 获取是否开启租户的标识
*
@@ -72,6 +81,11 @@ public class SysLoginController {
String password = dict.getStr("password");
String tenantCode = dict.getStr("tenantCode");
+ //检测是否开启验证码
+ if (ConstantContextHolder.getCaptchaOpenFlag()) {
+ verificationCode(dict.getStr("code"));
+ }
+
//如果系统开启了多租户开关,则添加租户的临时缓存
if (ConstantContextHolder.getTenantOpenFlag()) {
authService.cacheTenantInfo(tenantCode);
@@ -103,4 +117,56 @@ public class SysLoginController {
return new SuccessResponseData(LoginContextHolder.me().getSysLoginUserUpToDate());
}
+ /**
+ * @Description 获取验证码开关
+ * @author Jax
+ * @Date 2021/1/21 15:19
+ * @return ResponseData
+ **/
+ @GetMapping("/getCaptchaOpen")
+ public ResponseData getCaptchaOpen() {
+ return new SuccessResponseData(ConstantContextHolder.getCaptchaOpenFlag());
+ }
+
+ /**
+ * @Description 获取验证码
+ * @Date 2021/1/21 15:25
+ * @author Jax
+ * @return ResponseModel
+ **/
+ @GetMapping("/captcha/code")
+ public ResponseModel getCode() {
+ CaptchaVO vo = new CaptchaVO();
+ vo.setCaptchaType(CommonConstant.IMAGE_CODE_TYPE);
+ return captchaService.get(vo);
+ }
+
+ /**
+ * @Description 校验前端验证码
+ * @Date 2021/1/21 15:26
+ * @author Jax
+ * @param captcha
+ * @return ResponseModel
+ **/
+ @PostMapping("/captcha/code/check")
+ public ResponseModel check(@RequestBody CaptchaVO captcha) {
+ return captchaService.check(captcha);
+ }
+
+ /**
+ * @Description 校验验证码
+ * @Date 2021/1/21 15:27
+ * @author Jax
+ * @param code
+ * @return boolean
+ **/
+ private boolean verificationCode(String code) {
+ CaptchaVO vo = new CaptchaVO();
+ vo.setCaptchaVerification(code);
+ if (!captchaService.verification(vo).isSuccess()) {
+ throw new AuthException(AuthExceptionEnum.CONSTANT_EMPTY_ERROR);
+ }
+ return true;
+ }
+
}
diff --git a/xiaonuo-base/xiaonuo-system/src/main/java/com/cn/xiaonuo/sys/provider/CaptchaCacheServiceProvider.java b/xiaonuo-base/xiaonuo-system/src/main/java/com/cn/xiaonuo/sys/provider/CaptchaCacheServiceProvider.java
new file mode 100644
index 00000000..07237a61
--- /dev/null
+++ b/xiaonuo-base/xiaonuo-system/src/main/java/com/cn/xiaonuo/sys/provider/CaptchaCacheServiceProvider.java
@@ -0,0 +1,47 @@
+package com.cn.xiaonuo.sys.provider;
+
+import com.anji.captcha.service.CaptchaCacheService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @ClassName CaptchaCacheServiceProvider
+ * @Description 验证码 分布式部署 需要使用redis
+ * @Author Jax
+ * @Date 2021/1/21 16:27
+ **/
+//public class CaptchaCacheServiceProvider implements CaptchaCacheService {
+//
+// private static final String REDIS = "redis";
+//
+// @Autowired
+// private StringRedisTemplate stringRedisTemplate;
+//
+// @Override
+// public void set(String key, String value, long expiresInSeconds) {
+// stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);
+// }
+//
+// @Override
+// public boolean exists(String key) {
+// return stringRedisTemplate.hasKey(key);
+// }
+//
+// @Override
+// public void delete(String key) {
+// stringRedisTemplate.delete(key);
+// }
+//
+// @Override
+// public String get(String key) {
+// return stringRedisTemplate.opsForValue().get(key);
+// }
+//
+// @Override
+// public String type() {
+// return REDIS;
+// }
+//
+//}
diff --git a/xiaonuo-main/src/main/resources/application.yml b/xiaonuo-main/src/main/resources/application.yml
index 366c8251..8ca8b59e 100644
--- a/xiaonuo-main/src/main/resources/application.yml
+++ b/xiaonuo-main/src/main/resources/application.yml
@@ -55,3 +55,9 @@ jodconverter:
port-numbers: 8100
#libreoffice进程重启前的最大进程数
max-tasks-per-process: 100
+
+#验证码相关配置
+aj:
+ captcha:
+ water-font: 宋体
+ water-mark: XiaoNuo开发平台