阿里云的服务 数据风控下面有项滑动验证, 适合有人机验证场景的应用接入.javascript
所谓滑动验证, 是在须要进行人机识别时, 前端出现验证滑块, 经过拖动滑块到末尾, 实现验证. 若是阿里云认为这次验证风险稍高, 可能出现点击汉字的认证等, 若是风险过高, 验证会直接拒绝.css
集成过程基本是按照 阿里云官方文档 来进行的.html
开通滑动验证服务以后, 阿里云控制台会给出先后端的示例代码, 其中appkey是根据用户不一样分配的不通的key.前端
前端代码以下所示:java
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>示例-WEB版</title> <!-- 此段必需要引入 t为小时级别的时间戳 --> <link type="text/css" href="http://g.alicdn.com/sd/ncpc/nc.css?t=1502182314627" rel="stylesheet"/> <script type="text/javascript" src="http://g.alicdn.com/sd/ncpc/nc.js?t=1502182314627"></script> <!-- 引入结束 --> </head> <body> <!-- 此段必需要引入 --> <div id="_umfp" style="display:inline;width:1px;height:1px;overflow:hidden"></div> <!-- 引入结束 --> <!-- 表单示例,请替换成您的业务表单 --> <div class="container"> <form action="您提交的服务端地址" method="post"> <div class="ln"> <span class="h">用户名:</span> <input type="text" name="username"> </div> <div class="ln"> <span class="h">密码:</span> <input type="password" name="password"> </div> <div class="ln"> <div id="dom_id"></div> </div> <input type='hidden' id='csessionid' name='csessionid'/> <input type='hidden' id='sig' name='sig'/> <input type='hidden' id='token' name='token'/> <input type='hidden' id='scene' name='scene'/> <div class="ln"> <input type="submit" value="提交"> </div> </form> </div> <!-- 表单示例结束 --> <!-- 此段必需要引入 --> <script> var nc = new noCaptcha(); var nc_appkey = '你的appkey'; // 应用标识,不可更改 var nc_scene = 'other'; //场景,不可更改 var nc_token = [nc_appkey, (new Date()).getTime(), Math.random()].join(':'); var nc_option = { renderTo: '#dom_id',//渲染到该DOM ID指定的Div位置 appkey: nc_appkey, scene: nc_scene, token: nc_token, trans: '{"name1":"code0"}',//测试用,特殊nc_appkey时才生效,正式上线时请务必要删除;code0:经过;code100:点击验证码;code200:图形验证码;code300:恶意请求拦截处理 callback: function (data) {// 校验成功回调 console.log(data.csessionid); console.log(data.sig); console.log(nc_token); document.getElementById('csessionid').value = data.csessionid; document.getElementById('sig').value = data.sig; document.getElementById('token').value = nc_token; document.getElementById('scene').value = nc_scene; } }; nc.init(nc_option); </script> <!-- 引入结束 --> </body> <!-- 样式示例,请替换成本身的样式 --> <style> body { background: #f5f5f5; font-size: 14px; line-height: 20px; margin: 0; padding: 0; } .container { background: #fff; padding: 20px; margin: 20px; width: 400px; } .ln { padding: 5px 0; } .ln .h { display: inline-block; width: 4em; } .ln input { border: solid 1px #999; padding: 5px 8px; } </style> <!-- 样式示例结束 --> </html>
其中标记不可更改的地方就不要更改. 能够看到, 在验证经过以后, console会打印出session, sig, token等几个验证返回变量, 连同scene一块儿, 经过表单返回业务后台, 由后台调用阿里云滑动验证服务进行校验.python
python后台方面, 首先须要算法
pip install aliyun-python-sdk-jaq
安装阿里云滑动验证的python sdk
python的后台代码大体以下:apache
# coding: utf-8 import requests from aliyunsdkcore import client from aliyunsdkjaq.request.v20161123 import AfsCheckRequest from aliyunsdkcore.profile import region_provider from app.libs.configure import config region_provider.modify_point('Jaq', 'cn-hangzhou', 'jaq.aliyuncs.com') clt = client.AcsClient(config.ALIYUN_OSS_ACCESS_KEY, config.ALIYUN_OSS_ACCESS_SECRET, 'cn-hangzhou') def check_aliyun_captcha(session, sig, token, scene): request = AfsCheckRequest.AfsCheckRequest() # 必填参数:请求来源: 1:Android端; 2:iOS端; 3:PC端及其余 request.set_Platform(3) request.set_Session(session) request.set_Sig(sig) request.set_Token(token) request.set_Scene(scene) result = clt.do_action_with_exception(request) print result
这只是个简单示例, print出了返回结果. 这个返回结果是json序列化的string, 须要将其反序列化进行结果断定. 若是Data
元素是true
的话, 证实校验经过, 能够进行下面的业务逻辑.
一样的, 这里还有一点没有处理, 就是do_action_with_exception
的抛出ServerException
的问题. 应当catch处理.json
若是验证经过的话, 阿里云返回:后端
{"Data":true,"ErrorMsg":"success.","ErrorCode":0}
但只能验证一次, 第二次一样参数执行的话, 会返回:
{"Data":false,"ErrorMsg":"invalid sig parameter.","ErrorCode":400}
是为了防止暴力尝试等.
同时必须说明, 阿里云的这项服务我认为并不稳定, 集成中间出现了一直invalid sig
的报错. 后来经与技术支持沟通才ok. 而且这块的文档友好程度等还比较欠缺.
另: 我还按照阿里云api文档(滑动验证api, 公共参数, 签名机制)的说明写了段代码, 不使用aliyun-python-sdk, 手动拼接请求.一样发现了不少问题.
好比文档中有一段
这个签名值应当不是随便写的. 使用文档中要求的HMAC-SHA1签名算法, 不管是用python像以下这样书写:
from hashlib import sha1 import hmac from base64 import b64encode hashed = hmac.new('testsecret&', string_to_sign, sha1) signature = b64encode(hashed.digest()) print signature
仍是像以下用Java算签名:
import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; public class HMACSHA1 { private static final String MAC_NAME = "HmacSHA1"; private static final String ENCODING = "UTF-8"; public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { byte[] data=encryptKey.getBytes(ENCODING); SecretKey secretKey = new SecretKeySpec(data, MAC_NAME); Mac mac = Mac.getInstance(MAC_NAME); mac.init(secretKey); byte[] text = encryptText.getBytes(ENCODING); return mac.doFinal(text); } public static void main(String[] args) throws Exception{ byte[] bytes = HMACSHA1.HmacSHA1Encrypt("string_to_sign", "testsecret&"); System.out.println(Base64.encodeBase64String(bytes)); } }
string_to_sign
用文档中给出的替换, 个人两段程序算出的是一致的, 但都跟文档中给出的不一致. 不清楚为什么基础算法会不一致, 难道是文档不够新.
若是我用python以下根据api文档实现本身的后台请求方法的话:
import random from urllib import quote from hashlib import sha1 import hmac import requests from base64 import b64encode import datetime def utcnow_isostr(): dt = datetime.datetime.utcnow() return datetime.datetime.strftime(dt, '%Y-%m-%dT%H:%M:%SZ') def quote_ali(element): return quote(str(element)).replace('+', '20%').replace('*', '2A%').replace('%7E', '~') def build_afs_check_request(session, sig, token, scene): m = dict() m['Action'] = 'AfsCheck' m['Format'] = 'JSON' m['Version'] = '2016-11-23' m['AccessKeyId'] = config.ALIYUN_OSS_ACCESS_KEY m['SignatureMethod'] = 'HMAC-SHA1' m['Timestamp'] = utcnow_isostr() m['SignatureVersion'] = '1.0' m['SignatureNonce'] = str(int(random.random()*1000000)) m['token'] = token m['sig'] = sig m['session'] = session m['scene'] = scene m['platform'] = 3 ks = m.keys() ks.sort() query_list = list() for k in ks: query_list.append(k + '=' + quote_ali(m[k])) string_to_sign = 'GET&%2F&' + '&'.join(query_list) print string_to_sign base_query_string = '&'.join(query_list) hashed = hmac.new(config.ALIYUN_OSS_ACCESS_SECRET + '&', string_to_sign, sha1) signature = b64encode(hashed.digest()) print signature url = 'http://jaq.aliyuncs.com/?{}&Signature={}'.format(base_query_string, quote_ali(signature)) print url return url def custom_check_aliyun_captcha(session, sig, token, scene): url = build_afs_check_request(session, sig, token, scene) resp = requests.get(url) print resp.content
这么去请求的话会报Signature不一致, 果不出所料.
基于以上诡异的表现, 我以为阿里云滑动验证做为外部服务并不友好或成熟. 更倾向于用本身作的一些验证码服务, 虽然会简单些, 更容易被攻破, 至少稳定, 提供的qps更高.