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 @@ + + \ 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 @@ + + + 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开发平台