feat: 支持验证码
This commit is contained in:
@@ -40,6 +40,10 @@
|
|||||||
<groupId>org.apache.shiro</groupId>
|
<groupId>org.apache.shiro</groupId>
|
||||||
<artifactId>shiro-spring-boot-web-starter</artifactId>
|
<artifactId>shiro-spring-boot-web-starter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.penggle</groupId>
|
||||||
|
<artifactId>kaptcha</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
<artifactId>swagger-bootstrap-ui</artifactId>
|
<artifactId>swagger-bootstrap-ui</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.linlinjava.litemall.admin.config;
|
||||||
|
|
||||||
|
import com.google.code.kaptcha.Producer;
|
||||||
|
import com.google.code.kaptcha.impl.DefaultKaptcha;
|
||||||
|
import com.google.code.kaptcha.util.Config;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class KaptchaConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Producer kaptchaProducer() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty("kaptcha.image.width", "100");
|
||||||
|
properties.setProperty("kaptcha.image.height", "40");
|
||||||
|
properties.setProperty("kaptcha.textproducer.font.size", "32");
|
||||||
|
properties.setProperty("kaptcha.textproducer.font.color", "0,0,0");
|
||||||
|
properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ");
|
||||||
|
properties.setProperty("kaptcha.textproducer.char.length", "4");
|
||||||
|
properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
|
||||||
|
|
||||||
|
DefaultKaptcha kaptcha = new DefaultKaptcha();
|
||||||
|
Config config = new Config(properties);
|
||||||
|
kaptcha.setConfig(config);
|
||||||
|
return kaptcha;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ public class ShiroConfig {
|
|||||||
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
|
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
|
||||||
shiroFilterFactoryBean.setSecurityManager(securityManager);
|
shiroFilterFactoryBean.setSecurityManager(securityManager);
|
||||||
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
|
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
|
||||||
|
filterChainDefinitionMap.put("/admin/auth/kaptcha", "anon");
|
||||||
filterChainDefinitionMap.put("/admin/auth/login", "anon");
|
filterChainDefinitionMap.put("/admin/auth/login", "anon");
|
||||||
filterChainDefinitionMap.put("/admin/auth/401", "anon");
|
filterChainDefinitionMap.put("/admin/auth/401", "anon");
|
||||||
filterChainDefinitionMap.put("/admin/auth/index", "anon");
|
filterChainDefinitionMap.put("/admin/auth/index", "anon");
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class AdminWebSessionManager extends DefaultWebSessionManager {
|
|||||||
public AdminWebSessionManager() {
|
public AdminWebSessionManager() {
|
||||||
super();
|
super();
|
||||||
setGlobalSessionTimeout(MILLIS_PER_HOUR * 6);
|
setGlobalSessionTimeout(MILLIS_PER_HOUR * 6);
|
||||||
setSessionIdCookieEnabled(false);
|
// setSessionIdCookieEnabled(false);
|
||||||
setSessionIdUrlRewritingEnabled(false);
|
setSessionIdUrlRewritingEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ public class AdminResponseCode {
|
|||||||
public static final Integer ADMIN_ALTER_NOT_ALLOWED = 603;
|
public static final Integer ADMIN_ALTER_NOT_ALLOWED = 603;
|
||||||
public static final Integer ADMIN_DELETE_NOT_ALLOWED = 604;
|
public static final Integer ADMIN_DELETE_NOT_ALLOWED = 604;
|
||||||
public static final Integer ADMIN_INVALID_ACCOUNT = 605;
|
public static final Integer ADMIN_INVALID_ACCOUNT = 605;
|
||||||
|
public static final Integer ADMIN_INVALID_KAPTCHA = 606;
|
||||||
|
public static final Integer ADMIN_INVALID_KAPTCHA_REQUIRED = 607;
|
||||||
public static final Integer GOODS_UPDATE_NOT_ALLOWED = 610;
|
public static final Integer GOODS_UPDATE_NOT_ALLOWED = 610;
|
||||||
public static final Integer GOODS_NAME_EXIST = 611;
|
public static final Integer GOODS_NAME_EXIST = 611;
|
||||||
public static final Integer ORDER_CONFIRM_NOT_ALLOWED = 620;
|
public static final Integer ORDER_CONFIRM_NOT_ALLOWED = 620;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.linlinjava.litemall.admin.web;
|
package org.linlinjava.litemall.admin.web;
|
||||||
|
|
||||||
|
import com.google.code.kaptcha.Producer;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
@@ -24,12 +25,18 @@ import org.springframework.context.ApplicationContext;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import sun.misc.BASE64Encoder;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static org.linlinjava.litemall.admin.util.AdminResponseCode.ADMIN_INVALID_ACCOUNT;
|
import static org.linlinjava.litemall.admin.util.AdminResponseCode.*;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/admin/auth")
|
@RequestMapping("/admin/auth")
|
||||||
@@ -46,6 +53,37 @@ public class AdminAuthController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private LogHelper logHelper;
|
private LogHelper logHelper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Producer kaptchaProducer;
|
||||||
|
|
||||||
|
@GetMapping("/kaptcha")
|
||||||
|
public Object kaptcha(HttpServletRequest request) {
|
||||||
|
String kaptcha = doKaptcha(request);
|
||||||
|
if (kaptcha != null) {
|
||||||
|
return ResponseUtil.ok(kaptcha);
|
||||||
|
}
|
||||||
|
return ResponseUtil.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String doKaptcha(HttpServletRequest request) {
|
||||||
|
// 生成验证码
|
||||||
|
String text = kaptchaProducer.createText();
|
||||||
|
BufferedImage image = kaptchaProducer.createImage(text);
|
||||||
|
HttpSession session = request.getSession();
|
||||||
|
session.setAttribute("kaptcha", text);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(image, "jpeg", outputStream);
|
||||||
|
BASE64Encoder encoder = new BASE64Encoder();
|
||||||
|
String base64 = encoder.encode(outputStream.toByteArray());
|
||||||
|
String captchaBase64 = "data:image/jpeg;base64," + base64.replaceAll("\r\n", "");
|
||||||
|
return captchaBase64;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* { username : value, password : value }
|
* { username : value, password : value }
|
||||||
*/
|
*/
|
||||||
@@ -53,10 +91,20 @@ public class AdminAuthController {
|
|||||||
public Object login(@RequestBody String body, HttpServletRequest request) {
|
public Object login(@RequestBody String body, HttpServletRequest request) {
|
||||||
String username = JacksonUtil.parseString(body, "username");
|
String username = JacksonUtil.parseString(body, "username");
|
||||||
String password = JacksonUtil.parseString(body, "password");
|
String password = JacksonUtil.parseString(body, "password");
|
||||||
|
String code = JacksonUtil.parseString(body, "code");
|
||||||
|
|
||||||
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
|
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
|
||||||
return ResponseUtil.badArgument();
|
return ResponseUtil.badArgument();
|
||||||
}
|
}
|
||||||
|
if (StringUtils.isEmpty(code)) {
|
||||||
|
return ResponseUtil.fail(ADMIN_INVALID_KAPTCHA_REQUIRED, "验证码不能空");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpSession session = request.getSession();
|
||||||
|
String kaptcha = (String)session.getAttribute("kaptcha");
|
||||||
|
if (Objects.requireNonNull(code).compareToIgnoreCase(kaptcha) != 0) {
|
||||||
|
return ResponseUtil.fail(ADMIN_INVALID_KAPTCHA, "验证码不正确", doKaptcha(request));
|
||||||
|
}
|
||||||
|
|
||||||
Subject currentUser = SecurityUtils.getSubject();
|
Subject currentUser = SecurityUtils.getSubject();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
|
||||||
export function loginByUsername(username, password) {
|
export function loginByUsername(username, password, code) {
|
||||||
const data = {
|
const data = {
|
||||||
username,
|
username,
|
||||||
password
|
password,
|
||||||
|
code
|
||||||
}
|
}
|
||||||
return request({
|
return request({
|
||||||
url: '/auth/login',
|
url: '/auth/login',
|
||||||
@@ -27,3 +28,9 @@ export function getUserInfo(token) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getKaptcha() {
|
||||||
|
return request({
|
||||||
|
url: '/auth/kaptcha',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const user = {
|
|||||||
LoginByUsername({ commit }, userInfo) {
|
LoginByUsername({ commit }, userInfo) {
|
||||||
const username = userInfo.username.trim()
|
const username = userInfo.username.trim()
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
loginByUsername(username, userInfo.password).then(response => {
|
loginByUsername(username, userInfo.password, userInfo.code).then(response => {
|
||||||
const token = response.data.data.token
|
const token = response.data.data.token
|
||||||
commit('SET_TOKEN', token)
|
commit('SET_TOKEN', token)
|
||||||
setToken(token)
|
setToken(token)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Message, MessageBox } from 'element-ui'
|
|||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
|
|
||||||
|
axios.defaults.withCredentials = true
|
||||||
|
|
||||||
// create an axios instance
|
// create an axios instance
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url
|
baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url
|
||||||
|
|||||||
@@ -18,6 +18,16 @@
|
|||||||
<el-input v-model="loginForm.password" :type="passwordType" name="password" auto-complete="on" tabindex="2" show-password placeholder="管理员密码" @keyup.enter.native="handleLogin" />
|
<el-input v-model="loginForm.password" :type="passwordType" name="password" auto-complete="on" tabindex="2" show-password placeholder="管理员密码" @keyup.enter.native="handleLogin" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item prop="code">
|
||||||
|
<span class="svg-container">
|
||||||
|
<svg-icon icon-class="lock" />
|
||||||
|
</span>
|
||||||
|
<el-input v-model="loginForm.code" auto-complete="off" name="code" tabindex="2" placeholder="验证码" style="width: 60%" @keyup.enter.native="handleLogin" />
|
||||||
|
<div class="login-code">
|
||||||
|
<img :src="codeImg" @click="getCode">
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
|
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
|
||||||
|
|
||||||
<div style="position:relative">
|
<div style="position:relative">
|
||||||
@@ -43,6 +53,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { getKaptcha } from '@/api/login'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
data() {
|
data() {
|
||||||
@@ -56,8 +68,10 @@ export default {
|
|||||||
return {
|
return {
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: 'admin123',
|
username: 'admin123',
|
||||||
password: 'admin123'
|
password: 'admin123',
|
||||||
|
code: ''
|
||||||
},
|
},
|
||||||
|
codeImg: '',
|
||||||
loginRules: {
|
loginRules: {
|
||||||
username: [{ required: true, message: '管理员账户不允许为空', trigger: 'blur' }],
|
username: [{ required: true, message: '管理员账户不允许为空', trigger: 'blur' }],
|
||||||
password: [
|
password: [
|
||||||
@@ -79,12 +93,18 @@ export default {
|
|||||||
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
this.getCode()
|
||||||
// window.addEventListener('hashchange', this.afterQRScan)
|
// window.addEventListener('hashchange', this.afterQRScan)
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
// window.removeEventListener('hashchange', this.afterQRScan)
|
// window.removeEventListener('hashchange', this.afterQRScan)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getCode() {
|
||||||
|
getKaptcha().then(response => {
|
||||||
|
this.codeImg = response.data.data
|
||||||
|
})
|
||||||
|
},
|
||||||
handleLogin() {
|
handleLogin() {
|
||||||
this.$refs.loginForm.validate(valid => {
|
this.$refs.loginForm.validate(valid => {
|
||||||
if (valid && !this.loading) {
|
if (valid && !this.loading) {
|
||||||
@@ -93,6 +113,9 @@ export default {
|
|||||||
this.loading = false
|
this.loading = false
|
||||||
this.$router.push({ path: this.redirect || '/' })
|
this.$router.push({ path: this.redirect || '/' })
|
||||||
}).catch(response => {
|
}).catch(response => {
|
||||||
|
if (response.data.errno === 606 || response.data.errno === 605) {
|
||||||
|
this.codeImg = response.data.data
|
||||||
|
}
|
||||||
this.$notify.error({
|
this.$notify.error({
|
||||||
title: '失败',
|
title: '失败',
|
||||||
message: response.data.errmsg
|
message: response.data.errmsg
|
||||||
@@ -174,7 +197,14 @@ $light_gray:#eee;
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.login-code {
|
||||||
|
padding-top: 5px;
|
||||||
|
float: right;
|
||||||
|
img{
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align:middle
|
||||||
|
}
|
||||||
|
}
|
||||||
.tips {
|
.tips {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public class CorsConfig {
|
|||||||
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
|
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
|
||||||
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
|
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
|
||||||
corsConfiguration.setMaxAge(maxAge);
|
corsConfiguration.setMaxAge(maxAge);
|
||||||
|
corsConfiguration.setAllowCredentials(true);
|
||||||
return corsConfiguration;
|
return corsConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,14 @@ public class ResponseUtil {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Object fail(int errno, String errmsg, String data) {
|
||||||
|
Map<String, Object> obj = new HashMap<String, Object>(3);
|
||||||
|
obj.put("errno", errno);
|
||||||
|
obj.put("errmsg", errmsg);
|
||||||
|
obj.put("data", data);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
public static Object badArgument() {
|
public static Object badArgument() {
|
||||||
return fail(401, "参数不对");
|
return fail(401, "参数不对");
|
||||||
}
|
}
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@@ -155,6 +155,12 @@
|
|||||||
<version>2.9.2</version>
|
<version>2.9.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.penggle</groupId>
|
||||||
|
<artifactId>kaptcha</artifactId>
|
||||||
|
<version>2.3.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!--引入ui包-->
|
<!--引入ui包-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
|
|||||||
Reference in New Issue
Block a user