Authored by 陈峰

login captcha

  1 +<template>
  2 +<div class="captcha-box">
  3 + <div class="captcha-box-header">
  4 + <span>请将下列图片点击翻转至正确方向</span>
  5 + <a href="javascript:;" class="img-check-refresh" @click="captchaRefresh">换一批</a>
  6 + </div>
  7 + <div class="captcha-box-content">
  8 + <div class="item"
  9 + v-for="block in blocks"
  10 + :key="block.posX"
  11 + :style="{
  12 + 'background-image': `url(${imgSrc})`,
  13 + 'background-position': `${block.posX}px ${block.posY}px`
  14 + }"
  15 + @click="captchaClick(block)"></div>
  16 + </div>
  17 +</div>
  18 +</template>
  19 +<script>
  20 +import _ from 'lodash';
  21 +
  22 +export default {
  23 + name: 'captcha-box',
  24 + data() {
  25 + return {
  26 + captchaSrc: '/Api/captcha.jpg',
  27 + random: Math.random(),
  28 + blocks: []
  29 + };
  30 + },
  31 + created() {
  32 + this.blocks = [{
  33 + val: 0,
  34 + posX: 0,
  35 + posY: 0
  36 + }, {
  37 + val: 0,
  38 + posX: -60,
  39 + posY: 0
  40 + }, {
  41 + val: 0,
  42 + posX: -120,
  43 + posY: 0
  44 + }, {
  45 + val: 0,
  46 + posX: -180,
  47 + posY: 0
  48 + }];
  49 + },
  50 + computed: {
  51 + imgSrc() {
  52 + return `${this.captchaSrc}?r=${this.random}`;
  53 + }
  54 + },
  55 + methods: {
  56 + captchaClick(block) {
  57 + block.val = (block.val + 1) % 4;
  58 + block.posY = (block.posY - 60) % 240;
  59 + let vals = _.map(this.blocks, b => {
  60 + return b.val;
  61 + });
  62 +
  63 + this.$emit('change', vals);
  64 + },
  65 + captchaRefresh() {
  66 + this.random = Math.random();
  67 + }
  68 + }
  69 +};
  70 +</script>
  71 +<style lang="scss">
  72 +.captcha-box {
  73 + margin: 0 auto;
  74 + width: 270px;
  75 +}
  76 +
  77 +.captcha-box-header {
  78 + color: #b0b0b0;
  79 +
  80 + a {
  81 + color: #ff1901;
  82 + float: right;
  83 + }
  84 +}
  85 +
  86 +.captcha-box-content {
  87 + display: flex;
  88 +
  89 + .item {
  90 + flex: 1;
  91 + cursor: pointer;
  92 + margin-right: 10px;
  93 + width: 60px;
  94 + height: 60px;
  95 + overflow: hidden;
  96 + border: solid 1px #ccc;
  97 + background-size: 240px;
  98 +
  99 + &:last-child {
  100 + margin-right: 0;
  101 + }
  102 + }
  103 +}
  104 +</style>
  105 +
  1 +import Captcha from './captcha';
  2 +
  3 +export {
  4 + Captcha
  5 +};
@@ -16,6 +16,9 @@ @@ -16,6 +16,9 @@
16 <Icon type="ios-locked-outline" slot="prepend"></Icon> 16 <Icon type="ios-locked-outline" slot="prepend"></Icon>
17 </Input> 17 </Input>
18 </Form-item> 18 </Form-item>
  19 + <Form-item>
  20 + <captcha v-if="isCaptcha" @change="captchaChange"></captcha>
  21 + </Form-item>
19 <Form-item class="login-btn"> 22 <Form-item class="login-btn">
20 <Button type="primary" :loading="loading" @click="handleSubmit('formInline')"> 23 <Button type="primary" :loading="loading" @click="handleSubmit('formInline')">
21 登录 24 登录
@@ -29,47 +32,65 @@ @@ -29,47 +32,65 @@
29 32
30 <script> 33 <script>
31 import Vue from 'vue'; 34 import Vue from 'vue';
  35 +import _ from 'lodash';
  36 +import {Captcha} from './components';
32 37
33 export default { 38 export default {
34 name: 'login', 39 name: 'login',
  40 + data() {
  41 + return {
  42 + loading: false,
  43 + isCaptcha: false,
  44 + captcha: '',
  45 + formInline: {
  46 + user: '',
  47 + password: ''
  48 + },
  49 + ruleInline: {
  50 + user: [
  51 + { required: true, message: '请填写用户名', trigger: 'blur' }
  52 + ],
  53 + password: [
  54 + { required: true, message: '请填写密码', trigger: 'blur' },
  55 + { type: 'string', min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
  56 + ]
  57 + }
  58 + };
  59 + },
  60 + created() {
  61 + this.isCaptcha = this.$cookie.get('_captcha');
  62 + },
35 methods: { 63 methods: {
36 handleSubmit(name) { 64 handleSubmit(name) {
37 this.$refs[name].validate((valid) => { 65 this.$refs[name].validate((valid) => {
38 if (valid) { 66 if (valid) {
39 - this.login(this.formInline.user, this.formInline.password); 67 + if (this.isCaptcha && !this.captcha) {
  68 + this.$Message.error('请将图形验证码翻转至正确方向');
  69 + return;
  70 + }
  71 + this.login(this.formInline.user, this.formInline.password, this.captcha);
40 } else { 72 } else {
41 this.$Message.error('表单验证失败!'); 73 this.$Message.error('表单验证失败!');
42 } 74 }
43 }); 75 });
44 }, 76 },
45 - login(username, password) { 77 + captchaChange(vals) {
  78 + this.captcha = _.join(vals, '');
  79 + },
  80 + login(username, password, captcha) {
46 this.loading = true; 81 this.loading = true;
47 - Vue.passport.local(username, password).then(() => { 82 + Vue.passport.local(username, password, captcha).then(() => {
48 this.loading = false; 83 this.loading = false;
49 this.$router.push('/'); 84 this.$router.push('/');
50 }, (error) => { 85 }, (error) => {
  86 + this.isCaptcha = error.captcha;
51 this.loading = false; 87 this.loading = false;
52 this.$Message.error(error.message); 88 this.$Message.error(error.message);
53 }); 89 });
54 } 90 }
55 }, 91 },
56 - data() {  
57 - return {  
58 - loading: false,  
59 - formInline: {  
60 - user: '',  
61 - password: ''  
62 - },  
63 - ruleInline: {  
64 - user: [  
65 - { required: true, message: '请填写用户名', trigger: 'blur' }  
66 - ],  
67 - password: [  
68 - { required: true, message: '请填写密码', trigger: 'blur' },  
69 - { type: 'string', min: 6, message: '密码长度不能小于6位', trigger: 'blur' }  
70 - ]  
71 - }  
72 - }; 92 + components: {
  93 + Captcha
73 } 94 }
74 }; 95 };
75 </script> 96 </script>
@@ -104,10 +125,16 @@ export default { @@ -104,10 +125,16 @@ export default {
104 125
105 .login-btn { 126 .login-btn {
106 text-align: right; 127 text-align: right;
  128 +
  129 + button {
  130 + width: 100%;
  131 + height: 36px;
  132 + font-size: 14px;
  133 + }
107 } 134 }
108 135
109 .login-card { 136 .login-card {
110 - height: 250px; 137 + min-height: 250px;
111 } 138 }
112 } 139 }
113 </style> 140 </style>
@@ -88,8 +88,8 @@ export default { @@ -88,8 +88,8 @@ export default {
88 }); 88 });
89 89
90 Vue.passport = { 90 Vue.passport = {
91 - local: (username, password) => {  
92 - return this.userService.login(username, password).then((res) => { 91 + local: (username, password, captcha) => {
  92 + return this.userService.login(username, password, captcha).then((res) => {
93 if (res.code === 200) { 93 if (res.code === 200) {
94 return Promise.all([ 94 return Promise.all([
95 this.initPurview(Vue, res.data), 95 this.initPurview(Vue, res.data),
@@ -2,8 +2,12 @@ import _ from 'lodash'; @@ -2,8 +2,12 @@ import _ from 'lodash';
2 import Service from '../service'; 2 import Service from '../service';
3 3
4 class UserService extends Service { 4 class UserService extends Service {
5 - login(username, password) {  
6 - return this.post('/login', {username, password}); 5 + login(username, password, captcha) {
  6 + return this.post('/login', {
  7 + username,
  8 + password,
  9 + captcha
  10 + });
7 } 11 }
8 purviews() { 12 purviews() {
9 return this.post('/erp/getPurview', { 13 return this.post('/erp/getPurview', {
@@ -63,7 +63,7 @@ @@ -63,7 +63,7 @@
63 "request": "^2.81.0", 63 "request": "^2.81.0",
64 "request-promise": "^4.2.0", 64 "request-promise": "^4.2.0",
65 "serve-favicon": "^2.4.2", 65 "serve-favicon": "^2.4.2",
66 - "uuid": "^3.0.1", 66 + "uuid": "^3.1.0",
67 "vue": "^2.3.4", 67 "vue": "^2.3.4",
68 "vue-cookie": "^1.1.4", 68 "vue-cookie": "^1.1.4",
69 "vue-html5-editor": "^1.1.1", 69 "vue-html5-editor": "^1.1.1",
@@ -116,7 +116,7 @@ @@ -116,7 +116,7 @@
116 "html-webpack-plugin": "^2.28.0", 116 "html-webpack-plugin": "^2.28.0",
117 "husky": "^0.13.3", 117 "husky": "^0.13.3",
118 "ignore-file-loader": "^1.0.0", 118 "ignore-file-loader": "^1.0.0",
119 - "node-sass": "^4.5.2", 119 + "node-sass": "^4.5.3",
120 "nodemon": "^1.11.0", 120 "nodemon": "^1.11.0",
121 "optimize-css-assets-webpack-plugin": "^1.3.0", 121 "optimize-css-assets-webpack-plugin": "^1.3.0",
122 "ora": "^1.2.0", 122 "ora": "^1.2.0",
This diff could not be displayed because it is too large.
  1 +/**
  2 + * 用户controller
  3 + * @author: feng.chen<feng.chen@yoho.cn>
  4 + * @date: 2017/04/13
  5 + */
  6 +const _ = require('lodash');
  7 +const uuid = require('uuid');
  8 +const request = require('request');
  9 +const Context = require('../framework/context');
  10 +const captchaData = require('../common/captcha');
  11 +
  12 +class CaptchaController extends Context {
  13 + constructor() {
  14 + super();
  15 + }
  16 + captcha(req, res) {
  17 + let random = _.random(0, captchaData.length);
  18 + let captcha = captchaData[random];
  19 +
  20 + let codeStr = captcha.degrees.reduce((str, rotate) => {
  21 + return str.concat((4 - rotate / 90 % 4) % 4);
  22 + }, '');
  23 +
  24 + req.session.captcha = codeStr;
  25 + req.session.captchaTimeout = new Date().getTime() + 1000 * 60;
  26 + req.session.captchaSrc = captcha.verifiedGraphicCode;
  27 + return request(`${captcha.verifiedGraphicCode}?imageView2/0/format/jpg/q/70|watermark/2/text/${uuid.v4()}/fontsize/120/dissolve/10`).pipe(res); // eslint-disable-line
  28 + }
  29 + check(req, res, next) {
  30 + let isCaptcha = req.session.isCaptcha;
  31 +
  32 + if (isCaptcha) {
  33 + if (req.body.captcha === req.session.captcha) {
  34 + if (new Date().getTime() > req.session.captchaTimeout) {
  35 + return res.json({
  36 + code: 400,
  37 + captcha: true,
  38 + expired: true,
  39 + message: '验证码过期'
  40 + });
  41 + }
  42 + return next();
  43 + } else {
  44 + return res.json({
  45 + code: 400,
  46 + captcha: true,
  47 + message: '验证码错误'
  48 + });
  49 + }
  50 + }
  51 + return next();
  52 + }
  53 +}
  54 +
  55 +module.exports = CaptchaController;
@@ -7,20 +7,22 @@ @@ -7,20 +7,22 @@
7 'use strict'; 7 'use strict';
8 8
9 const Express = require('express'); 9 const Express = require('express');
10 -const UserController = require('./user-controller');  
11 -const FileController = require('./file-controller');  
12 -const ImportController = require('./import-controller');  
13 const middleware = require('../framework/middleware'); 10 const middleware = require('../framework/middleware');
14 const before = require('../middleware/before'); 11 const before = require('../middleware/before');
15 const auth = require('../middleware/auth'); 12 const auth = require('../middleware/auth');
  13 +const UserController = require('./user-controller');
  14 +const FileController = require('./file-controller');
  15 +const ImportController = require('./import-controller');
  16 +const CaptchaController = require('./captcha-controller');
16 17
17 let router = Express.Router(); // eslint-disable-line 18 let router = Express.Router(); // eslint-disable-line
18 19
19 -router.post('/login', middleware(UserController, 'login')); 20 +router.post('/login', middleware(CaptchaController, 'check'), middleware(UserController, 'login'));
20 router.post('/logout', middleware(UserController, 'logout')); 21 router.post('/logout', middleware(UserController, 'logout'));
21 router.post('/switchShop', before, auth, middleware(UserController, 'switchShop')); 22 router.post('/switchShop', before, auth, middleware(UserController, 'switchShop'));
22 router.post('/upload/image', before, auth, middleware(FileController, 'uploadImage')); 23 router.post('/upload/image', before, auth, middleware(FileController, 'uploadImage'));
23 router.post('/import', before, auth, middleware(ImportController, 'import')); 24 router.post('/import', before, auth, middleware(ImportController, 'import'));
24 router.post('/config', middleware(UserController, 'config')); 25 router.post('/config', middleware(UserController, 'config'));
  26 +router.get('/captcha.jpg', middleware(CaptchaController, 'captcha'));
25 27
26 module.exports = router; 28 module.exports = router;
@@ -49,7 +49,13 @@ class UserController extends Context { @@ -49,7 +49,13 @@ class UserController extends Context {
49 } 49 }
50 }); 50 });
51 }, err => { 51 }, err => {
52 - return res.json(err); 52 + req.session.isCaptcha = true;
  53 + res.cookie('_captcha', true, {
  54 + path: '/'
  55 + });
  56 + return res.json(Object.assign(err, {
  57 + captcha: true
  58 + }));
53 }).catch(next); 59 }).catch(next);
54 } 60 }
55 61
@@ -123,6 +129,8 @@ class UserController extends Context { @@ -123,6 +129,8 @@ class UserController extends Context {
123 } 129 }
124 130
125 syncSession(context, user, sess, currentShop) { 131 syncSession(context, user, sess, currentShop) {
  132 + delete context.req.session.isCaptcha;
  133 + context.res.clearCookie('_captcha');
126 context.req.session.USER = user; 134 context.req.session.USER = user;
127 context.req.session.LOGIN_UID = user.pid; // pid 为用户名 135 context.req.session.LOGIN_UID = user.pid; // pid 为用户名
128 136
@@ -4165,7 +4165,7 @@ node-pre-gyp@^0.6.36: @@ -4165,7 +4165,7 @@ node-pre-gyp@^0.6.36:
4165 tar "^2.2.1" 4165 tar "^2.2.1"
4166 tar-pack "^3.4.0" 4166 tar-pack "^3.4.0"
4167 4167
4168 -node-sass@^4.5.2: 4168 +node-sass@^4.5.3:
4169 version "4.5.3" 4169 version "4.5.3"
4170 resolved "http://npm.yoho.cn/node-sass/-/node-sass-4.5.3.tgz#d09c9d1179641239d1b97ffc6231fdcec53e1568" 4170 resolved "http://npm.yoho.cn/node-sass/-/node-sass-4.5.3.tgz#d09c9d1179641239d1b97ffc6231fdcec53e1568"
4171 dependencies: 4171 dependencies:
@@ -6343,7 +6343,7 @@ uuid@^2.0.1, uuid@^2.0.2: @@ -6343,7 +6343,7 @@ uuid@^2.0.1, uuid@^2.0.2:
6343 version "2.0.3" 6343 version "2.0.3"
6344 resolved "http://npm.yoho.cn/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" 6344 resolved "http://npm.yoho.cn/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
6345 6345
6346 -uuid@^3.0.0, uuid@^3.0.1: 6346 +uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
6347 version "3.1.0" 6347 version "3.1.0"
6348 resolved "http://npm.yoho.cn/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" 6348 resolved "http://npm.yoho.cn/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
6349 6349