feat: 支持验证码
This commit is contained in:
@@ -40,6 +40,10 @@
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring-boot-web-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.penggle</groupId>
|
||||
<artifactId>kaptcha</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<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.setSecurityManager(securityManager);
|
||||
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
|
||||
filterChainDefinitionMap.put("/admin/auth/kaptcha", "anon");
|
||||
filterChainDefinitionMap.put("/admin/auth/login", "anon");
|
||||
filterChainDefinitionMap.put("/admin/auth/401", "anon");
|
||||
filterChainDefinitionMap.put("/admin/auth/index", "anon");
|
||||
|
||||
@@ -18,7 +18,7 @@ public class AdminWebSessionManager extends DefaultWebSessionManager {
|
||||
public AdminWebSessionManager() {
|
||||
super();
|
||||
setGlobalSessionTimeout(MILLIS_PER_HOUR * 6);
|
||||
setSessionIdCookieEnabled(false);
|
||||
// setSessionIdCookieEnabled(false);
|
||||
setSessionIdUrlRewritingEnabled(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ public class AdminResponseCode {
|
||||
public static final Integer ADMIN_ALTER_NOT_ALLOWED = 603;
|
||||
public static final Integer ADMIN_DELETE_NOT_ALLOWED = 604;
|
||||
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_NAME_EXIST = 611;
|
||||
public static final Integer ORDER_CONFIRM_NOT_ALLOWED = 620;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.linlinjava.litemall.admin.web;
|
||||
|
||||
import com.google.code.kaptcha.Producer;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@@ -24,12 +25,18 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import sun.misc.BASE64Encoder;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
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.util.*;
|
||||
|
||||
import static org.linlinjava.litemall.admin.util.AdminResponseCode.ADMIN_INVALID_ACCOUNT;
|
||||
import static org.linlinjava.litemall.admin.util.AdminResponseCode.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/auth")
|
||||
@@ -46,6 +53,37 @@ public class AdminAuthController {
|
||||
@Autowired
|
||||
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 }
|
||||
*/
|
||||
@@ -53,10 +91,20 @@ public class AdminAuthController {
|
||||
public Object login(@RequestBody String body, HttpServletRequest request) {
|
||||
String username = JacksonUtil.parseString(body, "username");
|
||||
String password = JacksonUtil.parseString(body, "password");
|
||||
String code = JacksonUtil.parseString(body, "code");
|
||||
|
||||
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
|
||||
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();
|
||||
try {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function loginByUsername(username, password) {
|
||||
export function loginByUsername(username, password, code) {
|
||||
const data = {
|
||||
username,
|
||||
password
|
||||
password,
|
||||
code
|
||||
}
|
||||
return request({
|
||||
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) {
|
||||
const username = userInfo.username.trim()
|
||||
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
|
||||
commit('SET_TOKEN', token)
|
||||
setToken(token)
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Message, MessageBox } from 'element-ui'
|
||||
import store from '@/store'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
axios.defaults.withCredentials = true
|
||||
|
||||
// create an axios instance
|
||||
const service = axios.create({
|
||||
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-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>
|
||||
|
||||
<div style="position:relative">
|
||||
@@ -43,6 +53,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getKaptcha } from '@/api/login'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
data() {
|
||||
@@ -56,8 +68,10 @@ export default {
|
||||
return {
|
||||
loginForm: {
|
||||
username: 'admin123',
|
||||
password: 'admin123'
|
||||
password: 'admin123',
|
||||
code: ''
|
||||
},
|
||||
codeImg: '',
|
||||
loginRules: {
|
||||
username: [{ required: true, message: '管理员账户不允许为空', trigger: 'blur' }],
|
||||
password: [
|
||||
@@ -79,12 +93,18 @@ export default {
|
||||
|
||||
},
|
||||
created() {
|
||||
this.getCode()
|
||||
// window.addEventListener('hashchange', this.afterQRScan)
|
||||
},
|
||||
destroyed() {
|
||||
// window.removeEventListener('hashchange', this.afterQRScan)
|
||||
},
|
||||
methods: {
|
||||
getCode() {
|
||||
getKaptcha().then(response => {
|
||||
this.codeImg = response.data.data
|
||||
})
|
||||
},
|
||||
handleLogin() {
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
if (valid && !this.loading) {
|
||||
@@ -93,6 +113,9 @@ export default {
|
||||
this.loading = false
|
||||
this.$router.push({ path: this.redirect || '/' })
|
||||
}).catch(response => {
|
||||
if (response.data.errno === 606 || response.data.errno === 605) {
|
||||
this.codeImg = response.data.data
|
||||
}
|
||||
this.$notify.error({
|
||||
title: '失败',
|
||||
message: response.data.errmsg
|
||||
@@ -174,7 +197,14 @@ $light_gray:#eee;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-code {
|
||||
padding-top: 5px;
|
||||
float: right;
|
||||
img{
|
||||
cursor: pointer;
|
||||
vertical-align:middle
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
|
||||
@@ -17,6 +17,7 @@ public class CorsConfig {
|
||||
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
|
||||
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
|
||||
corsConfiguration.setMaxAge(maxAge);
|
||||
corsConfiguration.setAllowCredentials(true);
|
||||
return corsConfiguration;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,14 @@ public class ResponseUtil {
|
||||
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() {
|
||||
return fail(401, "参数不对");
|
||||
}
|
||||
|
||||
8
pom.xml
8
pom.xml
@@ -154,7 +154,13 @@
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.penggle</groupId>
|
||||
<artifactId>kaptcha</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<!--引入ui包-->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
|
||||
Reference in New Issue
Block a user