Showing
9 changed files
with
358 additions
and
22 deletions
@@ -137,6 +137,10 @@ app.use((req, res, next) => { | @@ -137,6 +137,10 @@ app.use((req, res, next) => { | ||
137 | next(); | 137 | next(); |
138 | }); | 138 | }); |
139 | 139 | ||
140 | +function isOpenApmRisk(req) { | ||
141 | + return _.get(req.app.locals, 'wap.open.apmrisk', false); | ||
142 | +} | ||
143 | + | ||
140 | // dispatcher | 144 | // dispatcher |
141 | try { | 145 | try { |
142 | const tdkUrl = require('./doraemon/middleware/redis-url'); | 146 | const tdkUrl = require('./doraemon/middleware/redis-url'); |
@@ -154,6 +158,8 @@ try { | @@ -154,6 +158,8 @@ try { | ||
154 | const pageCache = require('./doraemon/middleware/page-cache'); | 158 | const pageCache = require('./doraemon/middleware/page-cache'); |
155 | const downloadBar = require('./doraemon/middleware/download-bar'); | 159 | const downloadBar = require('./doraemon/middleware/download-bar'); |
156 | const routeEncode = require('./doraemon/middleware/route-encode'); | 160 | const routeEncode = require('./doraemon/middleware/route-encode'); |
161 | + const ifElseMd = require('./doraemon/middleware/ifElseMd'); | ||
162 | + const riskManagementApm = require('./doraemon/middleware/risk-management2'); | ||
157 | const riskManagement = require('./doraemon/middleware/risk-management'); | 163 | const riskManagement = require('./doraemon/middleware/risk-management'); |
158 | const statics = require('./doraemon/middleware/statics'); | 164 | const statics = require('./doraemon/middleware/statics'); |
159 | 165 | ||
@@ -168,7 +174,10 @@ try { | @@ -168,7 +174,10 @@ try { | ||
168 | 174 | ||
169 | // 请求限制中间件 | 175 | // 请求限制中间件 |
170 | if (!app.locals.devEnv) { | 176 | if (!app.locals.devEnv) { |
171 | - app.use(require('./doraemon/middleware/limiter')); | 177 | + const limiter = require('./doraemon/middleware/limiter'); |
178 | + const limiterApm = require('./doraemon/middleware/limiter/index2'); | ||
179 | + | ||
180 | + app.use(ifElseMd(isOpenApmRisk, limiterApm, limiter)); | ||
172 | } | 181 | } |
173 | 182 | ||
174 | if (app.locals.devEnv) { | 183 | if (app.locals.devEnv) { |
@@ -181,7 +190,7 @@ try { | @@ -181,7 +190,7 @@ try { | ||
181 | app.use(pageCache()); | 190 | app.use(pageCache()); |
182 | app.use(routeEncode.md); | 191 | app.use(routeEncode.md); |
183 | app.use(downloadBar()); | 192 | app.use(downloadBar()); |
184 | - app.use(riskManagement()); | 193 | + app.use(ifElseMd(isOpenApmRisk, riskManagementApm(), riskManagement())); |
185 | app.use(statics(app)); | 194 | app.use(statics(app)); |
186 | 195 | ||
187 | require('./dispatch')(app); | 196 | require('./dispatch')(app); |
@@ -44,6 +44,7 @@ exports.index = (req, res) => { | @@ -44,6 +44,7 @@ exports.index = (req, res) => { | ||
44 | }); | 44 | }); |
45 | }; | 45 | }; |
46 | 46 | ||
47 | +const limitKey = 'limit2'; | ||
47 | 48 | ||
48 | const submitValidate = { | 49 | const submitValidate = { |
49 | errRes: { | 50 | errRes: { |
@@ -54,6 +55,7 @@ const submitValidate = { | @@ -54,6 +55,7 @@ const submitValidate = { | ||
54 | }, | 55 | }, |
55 | clearLimitIp(req) { | 56 | clearLimitIp(req) { |
56 | let remoteIp = req.yoho.clientIp; | 57 | let remoteIp = req.yoho.clientIp; |
58 | + let operations = []; | ||
57 | 59 | ||
58 | if (remoteIp.indexOf(',') > 0) { | 60 | if (remoteIp.indexOf(',') > 0) { |
59 | let arr = remoteIp.split(','); | 61 | let arr = remoteIp.split(','); |
@@ -61,21 +63,51 @@ const submitValidate = { | @@ -61,21 +63,51 @@ const submitValidate = { | ||
61 | remoteIp = arr[0]; | 63 | remoteIp = arr[0]; |
62 | } | 64 | } |
63 | 65 | ||
64 | - let operations = [cache.delAsync(`${config.app}:limiter:${remoteIp}`)]; | 66 | + const isOpenApmrisk = _.get(req.app.locals, 'wap.open.apmrisk', false); |
65 | 67 | ||
66 | - // 验证码之后一小时之内不再限制qps | ||
67 | - if (req.session.apiLimitValidate || req.session.apiRiskValidate) { | ||
68 | - operations.push(cache.setAsync( | ||
69 | - `${config.app}:limiter:api:ishuman:${remoteIp}`, | ||
70 | - 1, | ||
71 | - config.LIMITER_IP_TIME | ||
72 | - )); | 68 | + // 新的计数 |
69 | + if (isOpenApmrisk) { | ||
70 | + operations.push(cache.delAsync(`${config.app}:${limitKey}:${remoteIp}`)); | ||
71 | + | ||
72 | + if (req.session.apiLimitValidate || req.session.apiRiskValidate) { | ||
73 | + operations.push(cache.setAsync( | ||
74 | + `${config.app}:limiter:api:ishuman:${remoteIp}`, | ||
75 | + 1, | ||
76 | + config.LIMITER_IP_TIME | ||
77 | + )); | ||
78 | + } else { | ||
79 | + operations.push(cache.setAsync( | ||
80 | + `${config.app}:${limitKey}:ishuman:${remoteIp}`, | ||
81 | + 1, | ||
82 | + config.LIMITER_IP_TIME | ||
83 | + )); | ||
84 | + | ||
85 | + } | ||
86 | + | ||
87 | + _.forEach(config.REQUEST_LIMIT, (val, key) => { | ||
88 | + operations.push(cache.delAsync(`${config.app}:${limitKey}:${key}:max:${remoteIp}`)); | ||
89 | + }); | ||
73 | } else { | 90 | } else { |
74 | - operations.push(cache.setAsync( | ||
75 | - `${config.app}:limiter:ishuman:${remoteIp}`, | ||
76 | - 1, | ||
77 | - config.LIMITER_IP_TIME | ||
78 | - )); | 91 | + operations.push(cache.delAsync(`${config.app}:limiter:${remoteIp}`)); |
92 | + | ||
93 | + // 验证码之后一小时之内不再限制qps | ||
94 | + if (req.session.apiLimitValidate || req.session.apiRiskValidate) { | ||
95 | + operations.push(cache.setAsync( | ||
96 | + `${config.app}:limiter:api:ishuman:${remoteIp}`, | ||
97 | + 1, | ||
98 | + config.LIMITER_IP_TIME | ||
99 | + )); | ||
100 | + } else { | ||
101 | + operations.push(cache.setAsync( | ||
102 | + `${config.app}:limiter:ishuman:${remoteIp}`, | ||
103 | + 1, | ||
104 | + config.LIMITER_IP_TIME | ||
105 | + )); | ||
106 | + } | ||
107 | + | ||
108 | + _.forEach(config.REQUEST_LIMIT, (val, key) => { | ||
109 | + operations.push(cache.delAsync(`${config.app}:limiter:${key}:max:${remoteIp}`)); | ||
110 | + }); | ||
79 | } | 111 | } |
80 | 112 | ||
81 | delete req.session.apiLimitValidate; | 113 | delete req.session.apiLimitValidate; |
@@ -87,10 +119,6 @@ const submitValidate = { | @@ -87,10 +119,6 @@ const submitValidate = { | ||
87 | operations.push(cache.delAsync(riskPid)); | 119 | operations.push(cache.delAsync(riskPid)); |
88 | } | 120 | } |
89 | 121 | ||
90 | - _.forEach(config.REQUEST_LIMIT, (val, key) => { | ||
91 | - operations.push(cache.delAsync(`${config.app}:limiter:${key}:max:${remoteIp}`)); | ||
92 | - }); | ||
93 | - | ||
94 | return Promise.all(operations); | 122 | return Promise.all(operations); |
95 | }, | 123 | }, |
96 | geetest(req, res) { | 124 | geetest(req, res) { |
@@ -135,7 +163,7 @@ const submitValidate = { | @@ -135,7 +163,7 @@ const submitValidate = { | ||
135 | imgCheckRisk(req, res) { | 163 | imgCheckRisk(req, res) { |
136 | const self = this; | 164 | const self = this; |
137 | 165 | ||
138 | - co(function * () { | 166 | + co(function*() { |
139 | let result = yield req.ctx(checkModel).verifyImgCheckRisk(req.cookies.udid, req.body.captcha); | 167 | let result = yield req.ctx(checkModel).verifyImgCheckRisk(req.cookies.udid, req.body.captcha); |
140 | 168 | ||
141 | if (result.code === 200) { | 169 | if (result.code === 200) { |
@@ -200,7 +200,11 @@ exports.serverError = () => { | @@ -200,7 +200,11 @@ exports.serverError = () => { | ||
200 | } | 200 | } |
201 | 201 | ||
202 | if (!isHuman) { | 202 | if (!isHuman) { |
203 | - cache.setAsync(`${config.app}:limiter:${remoteIp}`, 1, config.LIMITER_IP_TIME); | 203 | + if (_.get(req.app.locals, 'wap.open.apmrisk', false)) { |
204 | + cache.setAsync(`${config.app}:limit2:${remoteIp}`, 1, config.LIMITER_IP_TIME); | ||
205 | + } else { | ||
206 | + cache.setAsync(`${config.app}:limiter:${remoteIp}`, 1, config.LIMITER_IP_TIME); | ||
207 | + } | ||
204 | 208 | ||
205 | req.session[sessionLimitKey] = true; | 209 | req.session[sessionLimitKey] = true; |
206 | 210 |
doraemon/middleware/ifElseMd.js
0 → 100644
1 | + | ||
2 | + | ||
3 | +module.exports = (predict, ifTrueFn, elseFn) => { | ||
4 | + if (!ifTrueFn) { | ||
5 | + ifTrueFn = (req, res, next) => next(); | ||
6 | + } | ||
7 | + | ||
8 | + if (!elseFn) { | ||
9 | + elseFn = (req, res, next) => next(); | ||
10 | + } | ||
11 | + | ||
12 | + return (req, res, next) => { | ||
13 | + if (predict(req, res)) { | ||
14 | + ifTrueFn(req, res, next); | ||
15 | + } else { | ||
16 | + elseFn(req, res, next); | ||
17 | + } | ||
18 | + }; | ||
19 | +}; |
doraemon/middleware/limiter/index2.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const _ = require('lodash'); | ||
4 | +const logger = global.yoho.logger; | ||
5 | +const ip = require('./rules/ip-list2'); | ||
6 | +const userAgent = require('./rules/useragent'); | ||
7 | +const ipWhiteList = require('./rules/ip-white-list'); | ||
8 | +const pathWhiteList = require('./rules/path-white-list'); | ||
9 | +const qpsLimiter = require('./rules/qps-limit2'); | ||
10 | +const co = Promise.coroutine; | ||
11 | + | ||
12 | +// const asynchronous = require('./rules/asynchronous'); | ||
13 | +// const fakerLimiter = require('./rules/faker-limit'); | ||
14 | +const captchaPolicy = require('./policies/captcha'); | ||
15 | + | ||
16 | +// const reporterPolicy = require('./policies/reporter'); | ||
17 | + | ||
18 | +const limiter = (rule, policy, context) => { | ||
19 | + return rule(context, policy); | ||
20 | +}; | ||
21 | + | ||
22 | +// 排除条件:ip白名单/路径白名单/异步请求/登录用户 | ||
23 | +const _excluded = (req) => { | ||
24 | + let remoteIp = req.yoho.clientIp || ''; | ||
25 | + | ||
26 | + return co(function* () { | ||
27 | + let atIPWhiteList = yield ipWhiteList(remoteIp); | ||
28 | + | ||
29 | + return Boolean( | ||
30 | + atIPWhiteList || | ||
31 | + _.includes(pathWhiteList(), req.path) || | ||
32 | + req.xhr || | ||
33 | + !_.isEmpty(_.get(req, 'user.uid')) | ||
34 | + ); | ||
35 | + })(); | ||
36 | +}; | ||
37 | + | ||
38 | +module.exports = (req, res, next) => { | ||
39 | + const remoteIp = req.yoho.clientIp || ''; | ||
40 | + const enabled = !_.get(req.app.locals, 'wap.sys.noLimiter'); | ||
41 | + | ||
42 | + // 开关为关或者未获取到remoteIp,放行 | ||
43 | + if (!enabled || !remoteIp) { | ||
44 | + logger.debug(`request remote ip: ${remoteIp}; enabled: ${enabled}`); | ||
45 | + return next(); | ||
46 | + } | ||
47 | + | ||
48 | + co(function* () { | ||
49 | + let excluded = yield _excluded(req); | ||
50 | + | ||
51 | + logger.debug(`request remote ip: ${remoteIp}; excluded: ${excluded}; enabled: ${enabled}`); | ||
52 | + | ||
53 | + // 白名单,放行 | ||
54 | + if (excluded) { | ||
55 | + return next(); | ||
56 | + } | ||
57 | + const context = { | ||
58 | + req: req, | ||
59 | + res: res, | ||
60 | + next: next, | ||
61 | + remoteIp: remoteIp | ||
62 | + }; | ||
63 | + | ||
64 | + let results = yield Promise.all([ | ||
65 | + limiter(userAgent, captchaPolicy, context), | ||
66 | + limiter(ip, captchaPolicy, context), | ||
67 | + limiter(qpsLimiter, captchaPolicy, context) | ||
68 | + | ||
69 | + // limiter(asynchronous, captchaPolicy, context) | ||
70 | + // limiter(fakerLimiter, reporterPolicy, context) | ||
71 | + ]); | ||
72 | + | ||
73 | + let allPass = true, exclusion = false, policy = null; | ||
74 | + | ||
75 | + logger.debug('limiter result: ' + JSON.stringify(results)); | ||
76 | + | ||
77 | + _.forEach(results, (result) => { | ||
78 | + if (typeof result === 'object' && !exclusion) { | ||
79 | + exclusion = result.exclusion; | ||
80 | + } | ||
81 | + | ||
82 | + if (typeof result === 'function') { | ||
83 | + allPass = false; | ||
84 | + policy = result; | ||
85 | + } | ||
86 | + }); | ||
87 | + | ||
88 | + if (exclusion) { | ||
89 | + return next(); | ||
90 | + } else if (!allPass && policy) { | ||
91 | + policy(req, res, next); | ||
92 | + } else { | ||
93 | + return next(); | ||
94 | + } | ||
95 | + })().catch(err => { | ||
96 | + logger.error(err); | ||
97 | + return next(); | ||
98 | + }); | ||
99 | +}; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const cache = global.yoho.cache.master; | ||
4 | +const logger = global.yoho.logger; | ||
5 | +const config = global.yoho.config; | ||
6 | + | ||
7 | +const limitKey = 'limit2'; | ||
8 | + | ||
9 | +module.exports = (limiter, policy) => { | ||
10 | + const ipBlackKey = `pc:limiter:${limiter.remoteIp}`; // ci ip黑名单 | ||
11 | + const ipLimitKey = `${config.app}:${limitKey}:${limiter.remoteIp}`; // 业务黑名单 | ||
12 | + | ||
13 | + return Promise.all([ | ||
14 | + cache.getAsync(ipBlackKey), | ||
15 | + cache.getAsync(ipLimitKey) | ||
16 | + ]).then(result => { | ||
17 | + let ipBlackRes = result[0]; | ||
18 | + let ipLimitRes = result[1]; | ||
19 | + | ||
20 | + logger.debug(ipBlackKey, ipBlackRes); | ||
21 | + logger.debug(ipLimitKey, ipLimitRes); | ||
22 | + | ||
23 | + if ((ipBlackRes && +ipBlackRes > 0) || (ipLimitRes && +ipLimitRes > 0)) { | ||
24 | + return Promise.resolve(policy); | ||
25 | + } else { | ||
26 | + return Promise.resolve(true); | ||
27 | + } | ||
28 | + }); | ||
29 | +}; |
1 | +/** | ||
2 | + * 限制页面访问次数,如超过限制次数,返回相应策略(目前是ip加入黑名单,跳转图形验证码页面,解除限制) | ||
3 | + * 当前规则只针对未登录用户 | ||
4 | + */ | ||
5 | + | ||
6 | +const logger = global.yoho.logger; | ||
7 | +const cache = global.yoho.cache.master; | ||
8 | +const config = global.yoho.config; | ||
9 | + | ||
10 | +const limitKey = 'limit2'; | ||
11 | + | ||
12 | +module.exports = (limiter, policy) => { | ||
13 | + let getOp = {}; | ||
14 | + | ||
15 | + getOp.human = cache.getAsync(`${config.app}:${limitKey}:ishuman:${limiter.remoteIp}`); | ||
16 | + getOp.limit = cache.getAsync(`${config.app}:${limitKey}:${limiter.remoteIp}`); | ||
17 | + | ||
18 | + return Promise.props(getOp).then((results) => { | ||
19 | + console.log('enable ===>', results); | ||
20 | + if (results.human) { | ||
21 | + return Promise.resolve(true); | ||
22 | + } | ||
23 | + | ||
24 | + if (results.limit) { | ||
25 | + return Promise.resolve(policy); | ||
26 | + } | ||
27 | + | ||
28 | + return Promise.resolve(true); | ||
29 | + }).catch(err=>{ | ||
30 | + logger.error(err); | ||
31 | + | ||
32 | + return Promise.resolve(true); | ||
33 | + }); | ||
34 | +}; |
doraemon/middleware/risk-management2.js
0 → 100644
1 | +/** | ||
2 | + * 控制路由请求次数 | ||
3 | + * @date: 2018/03/05 | ||
4 | + */ | ||
5 | +'use strict'; | ||
6 | + | ||
7 | +const _ = require('lodash'); | ||
8 | +const cache = global.yoho.cache.master; | ||
9 | +const helpers = global.yoho.helpers; | ||
10 | +const pathToRegexp = require('path-to-regexp'); | ||
11 | +const logger = global.yoho.logger; | ||
12 | +const md5 = require('yoho-md5'); | ||
13 | + | ||
14 | +const statusCode = { | ||
15 | + code: 4403, | ||
16 | + data: {}, | ||
17 | + message: '亲,您的访问次数过多,请稍后再试哦...' | ||
18 | +}; | ||
19 | + | ||
20 | +const IP_WHITE_LIST = [ | ||
21 | + '106.38.38.146', | ||
22 | + '106.38.38.147', | ||
23 | + '106.39.86.227', | ||
24 | + '218.94.75.58', | ||
25 | + '218.94.75.50', | ||
26 | + '218.94.77.166' | ||
27 | +]; | ||
28 | + | ||
29 | +const _jumpUrl = (req, res, next, result) => { | ||
30 | + if (result.code === 4403) { | ||
31 | + if (req.xhr) { | ||
32 | + res.set({ | ||
33 | + 'Cache-Control': 'no-cache', | ||
34 | + Pragma: 'no-cache', | ||
35 | + Expires: (new Date(1900, 0, 1, 0, 0, 0, 0)).toUTCString() | ||
36 | + }); | ||
37 | + return res.status(403).json(result); | ||
38 | + } | ||
39 | + return res.redirect(`${result.data.url}&refer=${req.originalUrl}`); | ||
40 | + } | ||
41 | + | ||
42 | + return next(); | ||
43 | +}; | ||
44 | + | ||
45 | +const limitKey = 'limit2'; | ||
46 | + | ||
47 | +module.exports = () => { | ||
48 | + return (req, res, next) => { | ||
49 | + // default open | ||
50 | + if (_.get(req.app.locals.wap, 'close.risk', false)) { | ||
51 | + return next(); | ||
52 | + } | ||
53 | + | ||
54 | + let ip = _.get(req.yoho, 'clientIp', ''); | ||
55 | + let path = req.path || ''; | ||
56 | + let risks = _.get(req.app.locals.wap, 'json.risk', []); | ||
57 | + let router = {}; | ||
58 | + | ||
59 | + logger.debug(`risk => risks: ${JSON.stringify(risks)}, path: ${path}, ip: ${ip}`); // eslint-disable-line | ||
60 | + if (_.isEmpty(path) || _.isEmpty(risks) || IP_WHITE_LIST.indexOf(ip) > -1) { | ||
61 | + return next(); | ||
62 | + } | ||
63 | + | ||
64 | + _.isArray(risks) && risks.some(item => { | ||
65 | + if (item.state === 'off') { | ||
66 | + return false; | ||
67 | + } | ||
68 | + | ||
69 | + if (!item.regRoute) { | ||
70 | + item.regRoute = pathToRegexp(item.route); | ||
71 | + } | ||
72 | + | ||
73 | + if (item.regRoute.test(path)) { | ||
74 | + router = item; | ||
75 | + return true; | ||
76 | + } | ||
77 | + | ||
78 | + return false; | ||
79 | + }); | ||
80 | + | ||
81 | + logger.debug(`risk => router: ${JSON.stringify(router)}, path: ${path}`); // eslint-disable-line | ||
82 | + if (_.isEmpty(router)) { | ||
83 | + return next(); | ||
84 | + } | ||
85 | + | ||
86 | + let keyPath = md5(`${router.regRoute}`); | ||
87 | + let limitEnable = `wap:risk:${limitKey}:${keyPath}:${ip}`; | ||
88 | + let checkUrl = helpers.urlFormat('/3party/check', { | ||
89 | + pid: `wap:risk:${limitKey}:${keyPath}` | ||
90 | + }); | ||
91 | + | ||
92 | + cache.getAsync(limitEnable) | ||
93 | + .then(result => { | ||
94 | + logger.debug(`risk => getCache: ${JSON.stringify(result)}, path: ${path}`); // eslint-disable-line | ||
95 | + if (result) { | ||
96 | + return Object.assign({}, statusCode, { | ||
97 | + data: { | ||
98 | + url: checkUrl | ||
99 | + } | ||
100 | + }); | ||
101 | + } else { | ||
102 | + return { | ||
103 | + code: 200 | ||
104 | + }; | ||
105 | + } | ||
106 | + }).then(result => { | ||
107 | + logger.debug(`risk => result: ${JSON.stringify(result)}, path: ${path}`); // eslint-disable-line | ||
108 | + return _jumpUrl(req, res, next, result); | ||
109 | + }).catch(e => { | ||
110 | + console.log(`risk => path: ${path}, err: ${e.message}`); | ||
111 | + return next(); | ||
112 | + }); | ||
113 | + }; | ||
114 | +}; |
@@ -87,7 +87,7 @@ | @@ -87,7 +87,7 @@ | ||
87 | "xml2js": "^0.4.19", | 87 | "xml2js": "^0.4.19", |
88 | "yoho-express-session": "^2.0.0", | 88 | "yoho-express-session": "^2.0.0", |
89 | "yoho-md5": "^2.0.0", | 89 | "yoho-md5": "^2.0.0", |
90 | - "yoho-node-lib": "=0.6.26", | 90 | + "yoho-node-lib": "=0.6.28", |
91 | "yoho-zookeeper": "^1.0.10" | 91 | "yoho-zookeeper": "^1.0.10" |
92 | }, | 92 | }, |
93 | "devDependencies": { | 93 | "devDependencies": { |
-
Please register or login to post a comment