Browse Source

feat: 支持验证码

linlinjava 5 years ago
parent
commit
679117d53b

+ 4 - 0
litemall-admin-api/pom.xml

@@ -41,6 +41,10 @@
             <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>
             <version>1.9.6</version>

+ 31 - 0
litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/KaptchaConfig.java

@@ -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;
+    }
+
+}

+ 1 - 0
litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/config/ShiroConfig.java

@@ -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");

+ 1 - 1
litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/shiro/AdminWebSessionManager.java

@@ -18,7 +18,7 @@ public class AdminWebSessionManager extends DefaultWebSessionManager {
     public AdminWebSessionManager() {
         super();
         setGlobalSessionTimeout(MILLIS_PER_HOUR * 6);
-        setSessionIdCookieEnabled(false);
+//        setSessionIdCookieEnabled(false);
         setSessionIdUrlRewritingEnabled(false);
     }
 

+ 2 - 0
litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/AdminResponseCode.java

@@ -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;

+ 49 - 1
litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAuthController.java

@@ -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 {

+ 9 - 2
litemall-admin/src/api/login.js

@@ -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'
+  })
+}

+ 1 - 1
litemall-admin/src/store/modules/user.js

@@ -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)

+ 2 - 0
litemall-admin/src/utils/request.js

@@ -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

+ 32 - 2
litemall-admin/src/views/login/index.vue

@@ -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;

+ 1 - 0
litemall-core/src/main/java/org/linlinjava/litemall/core/config/CorsConfig.java

@@ -17,6 +17,7 @@ public class CorsConfig {
         corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
         corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
         corsConfiguration.setMaxAge(maxAge);
+        corsConfiguration.setAllowCredentials(true);
         return corsConfiguration;
     }
 

+ 8 - 0
litemall-core/src/main/java/org/linlinjava/litemall/core/util/ResponseUtil.java

@@ -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, "参数不对");
     }

+ 7 - 1
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>