基础篇请看:设备指纹系列--基础篇
我们接着前文继续写关于设备指纹前端接入方面的内容。话不多说,直接步入正题。
我们会在下文展示5种前端接入的方式,包括web接入、安卓接入、ios接入、微信小程序接入以及支付宝小程序接入。
在页面 HTML 中引入 const-id.js,代码形如:
1 | <script src="511K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0k6r3&6Q4x3X3g2V1K9h3&6Y4P5r3W2S2L8X3N6Q4x3X3c8A6L8X3y4Q4x3X3g2U0L8$3#2Q4x3V1k6U0N6s2g2Q4x3X3c8Y4M7X3!0#2M7q4)9J5c8X3y4G2L8Y4y4@1K9h3c8Q4x3X3c8B7M7#2)9J5c8X3W2F1k6r3g2^5i4K6u0W2K9Y4x3`."></script> |
因js文件会定期更新,为避免js失效影响您的使用,请不要将js下载到本地服务器上引入
页面加载后,初始化设备指纹,需要在 JavaScript 中调用 _dx.ConstID(options, callback) 方法获取设备指纹token,代码形如:
1 2 3 4 5 6 7 8 9 10 11 12 13 | var options = { appId: '【这里填写 AppID】', // 唯一标识,必填 server: 'fa1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0L8$3&6K6N6r3W2V1i4K6u0W2k6r3W2F1k6%4S2A6j5h3&6Y4i4K6u0V1K9h3&6U0i4K6u0W2j5$3!0E0i4K6u0r3N6h3c8A6k6q4)9J5c8X3x3I4i4K6t1%4, // constId 服务接口,可选 userId: '【这里填写 userID】' // 用户标识,可选};_dx.ConstID(options, function (err, token) { if (err) { // console.log('error: ' + err); return; } // console.log('const-id token is ' + token);}); |
同时也支持Promise的用法
1 2 3 4 5 | _dx.ConstID(options).then(function(token) { console.log(token)}).catch(function(err) { console.log(err)}) |
字段 | 类型 | 是否必填 | 说明 |
| :------ | :------ | :--- | :----------------------------------- |
| appId | String | 是 | 当前应用的标识 |
| server | String | 否 | constId 服务接口,可选,如不填,则默认会用云服务接口 |
| scene | String | 否 | 场景标识,例如 login、survey 等 |
| userId | String | 否 | 业务方的用户唯一标识,例如用户名、用户ID、手机号、Email等 |
| timeout | number | 否 | 超时失败时间,单位为毫秒 |
| cache | boolean | 否 | 默认为 true,表示会缓存采集结果;改为 false 则每次会重新采集
| 浏览器 | 最低版本 |
|---|---|
| IE | 8 |
| Edge | 20 |
| Chrome | 60 |
| Safari | 11 |
| Firefox | 60 |
| 360 | 10 |
| Sougou | 8 |
| 4 |
| 浏览器 | 最低版本 |
|---|---|
| Chrome | 60 |
| UC | 12 |
| 8 | |
| Safari | 11 |
| 原生 | 安卓4.0及以上 |
| 条目 | 说明 |
|---|---|
| 开发目标 | Android 4.0+ |
| 开发环境 | Android Studio 3.0.1 或者 Eclipse + ADT |
| CPU架构 | ARM 或者 x86 |
| SDK三方依赖 | 无 |
点击下载demo(仅做代码配置演示使用)
SDK包集成内容:
Demo工程结构如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | .├── app│ ├── build.gradle│ ├── libs│ │ └── dx-risk-vx.x.xxr.xxxxxxxx.aar│ ├── proguard-rules.pro│ └── src│ └── main├── build.gradle├── gradle├── gradle.properties├── gradlew├── gradlew.bat└── settings.gradle |
1 2 3 4 5 6 7 8 9 10 | android { packagingOptions { doNotStrip "**/lib*Risk*.so" }}dependencies { implementation fileTree(dir: 'libs', include: ['*.aar'])} |
1 2 3 4 5 6 7 8 9 10 | <uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_PHONE_STATE"/><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/><uses-permission android:name="android.permission.BLUETOOTH"/> |
1 2 3 4 5 6 7 8 9 10 | -dontskipnonpubliclibraryclassmembers-keepattributes *Annotation*,EnclosingMethod-dontwarn com.dx.mobile.**-dontwarn *.com.dx.mobile.**-dontwarn *.com.mobile.strenc.**-keep class com.dx.mobile.risk.**{*;}-keep class com.security.inner.**{*;}-keep class *.com.dx.mobile.**{*;}-keep class *.com.mobile.strenc.**{*;} |
需要动态申请权限如下:
1 2 3 4 5 | android.permission.WRITE_EXTERNAL_STORAGEandroid.permission.READ_EXTERNAL_STORAGEandroid.permission.READ_PHONE_STATEandroid.permission.ACCESS_COARSE_LOCATIONandroid.permission.ACCESS_FINE_LOCATION |
动态申请代码实例(Activity下):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); // API 23或以上的动态申请权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { String[] permissionArray = { "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_BACKGROUND_LOCATION", "android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.READ_PHONE_STATE", }; this.requestPermissions(permissionArray, 1); }}@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // start getToken new Thread(new Runnable() { @Override public void run() { HashMap<String, String> params = new HashMap<String, String>(); String token = DXRisk.getToken("appid", params); } }).start();} |
采集端的设备指纹信息,上传至风控后台,再由风控后台返回token。 该API为耗时操作,因此必须在非主线程上调用,否则会抛异常。
DXRisk.java 该类是DxRisk SDK的风控组件接口,负责采集本地信息并返回用户前端token。
自v6.1.18r开始,SDKsetup前,可以调用setAllowPrivacyList进行隐私采集白名单的设置,默认关闭所有涉及隐私规范红线的采集项
1 | public static void setAllowPrivacyList(long privacyFlag) |
使用示例
1 | setAllowPrivacyList(PrivacyFlag.ANDROID_ID); //只采集ANDROID_ID |
1 2 | //自定义列表模式setAllowPrivacyList(PrivacyFlag.X | PrivacyFlag.Y | ...); //表示既采集X又采集Y,X、Y表示具体某项采集的FLAG值,如下表 |
以下为提供的白名单采集项
| FLAG | 描述 |
|---|---|
| DEFAULT | 默认值,关闭所有隐私采集项 |
| ALL | 打开所有隐私采集项 |
| IMEI | 加入该FLAG则采集手机IMEI值,否则不采集 |
| IMSI | 加入该FLAG则采集手机IMSI值,否则不采集 |
| MEID | 加入该FLAG则采集手机MEID值,否则不采集 |
| SERIAL_NUMBER | 加入该FLAG则采集手机SERIAL_NUMBER值,否则不采集 |
| MAC_ADDRESS | 加入该FLAG则采集手机MAC_ADDRESS值,否则不采集 |
| ICCID | 加入该FLAG则采集手机ICCID值,否则不采集 |
| ANDROID_ID | 加入该FLAG则采集手机ANDROID_ID值,否则不采集 |
| DEVICE_ID | 加入该FLAG则采集手机DEVICE_ID值,否则不采集 |
| GET_INSTALLED_PACKAGES | 加入该FLAG则采集手机的应用安装列表,否则不采集 |
SDK使用前必须调用先setup,setup主要用于数据/环境初始化,一般在Application的onCreate下调用:
1 2 3 4 5 6 | /*** 初始化参数,环境* @param context* @return*/public static boolean setup(Context context) |
PS:下列两种方式获取token在网络通畅的情况下没有任何的不同。
1 2 3 4 5 | /** * @return token 通常返回长度为40的字符串。在网络卡顿或不通的情况下,返回4-5k的字符串。 * @throws DXRiskErrorException 如在主线程调用本API,或者appId为空等等,则会抛出该异常 */public static String getToken(String appId, HashMap<String, String> paramsMap) throws DXRiskErrorException |
获取轻量级Token可获取的设备信息信息远少于getToken(),可能会造成在判断设备是否有风险时出现较大误差,请谨慎使用。
1 2 3 4 5 | /** `* @return token 通常返回长度为40的字符串。在网络卡顿或不通的情况下,返回1k的字符串。 `* @throws DXRiskErrorException 如在主线程调用本API,或者appId为空等等,则会抛出该异常 */public static String getLightToken(String appId, HashMap<String, String> paramsMap) throws DXRiskErrorException |
建议Application.onCreate下调用
1 2 3 4 5 6 | @Overridepublic void onCreate() { super.onCreate(); // 环境初始化 DXRisk.setup(this);} |
整个过程由于是耗时操作,必须要在非主线程上执行,否则会crash
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | new Thread(){ @Override public void run() { /* 私有化配置 */ HashMap<String, String> params = new HashMap<String, String>(); // 这里请填写对应的服务端url,注意SAAS和私有化的区别 params.put(DXRisk.KEY_URL, "47cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0L8$3&6K6N6r3W2V1i4K6u0W2k6r3W2F1k6%4S2A6j5h3&6Y4i4K6u0V1K9h3&6U0i4K6u0W2j5$3!0E0"); // 开启线上数据备份 // params.put(DXRisk.KEY_BACKUP, DXRisk.VALUE_ENABLE_BACKUP); // 设置请求token超时时长ms,不设置默认为500ms params.put(DXRisk.KEY_DELAY_MS_TIME, "2000"); //此配置关闭SDK缓存,表示每次调用接口都是强制采集信息 // params.put("PRIVATE_CLEAR_TOKEN", "clear"); // 开通服务后可在应用管理菜单中获取;私有化版本请填写私有化appId String appId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // 获取设备指纹token final String token = DXRisk.getToken(appId, params); // TODO 把token通过Post请求,传到用户后端 }}.start(); |
在获取token过程中,如果因为网络超时或者加解密失败,该接口有可能会返回为null,同时会输出tag为DXRISK的错误信息,具体描述如下:
1 2 3 4 5 6 7 | DXRISK_REQUEST_NETWORK_ERR -1001 //网络链接失败DXRISK_REQUEST_DECRYPT_ERR -1002 //数据解密错误DXRISK_REQUEST_UNCOMPRESS_ERR -1003 //解压错误DXRISK_REQUEST_RESPONSE_EMPTY_ERR -1004 //返回为空DXRISK_REQUEST_DATA_PARSE_ERR -1005 //数据解析失败DXRISK_REQUEST_DIRTY_DATA_ERR -1006 //脏数据DXRISK_CONST_ID_EMPTY -1007 //constid为空 |
| 条目 | 说明 |
|---|---|
| 兼容平台 | iOS 8.0+ |
| 开发环境 | XCode 4.0 + |
| CPU架构 | armv7, arm64, i386, x86_64 |
| SDK依赖 | libz, libresolv, libc++ , SystemConfiguration.framework , CoreLocation.framework , CoreTelephony.framework |
点击下载指纹SDK,SDK的目录结构如下:

dx-risk-iOS-x.x.x-xxxxxxx目录 DXRisk sdk
DXRisk.framework,DXRiskWithIDFA.framework,DXRiskStatic.framework,DXRiskStaticWithIDFA.framework`其中之一直接拖入工程目录中,或者右击总文件夹添加文件。
DXRiskWithIDFA.framework 或者 DXRiskStaticWithIDFA.framework,该版本可以提供更精准的tokenDXRisk.framework 或者 DXRiskStatic.framework若在项目中添加DXRisk.framework或者DXRiskWithIDFA.framework其中之一,选择Target -> General,在Frameworks,Libraries,and Embedded Content中,将DXRisk.framework或者DXRiskWithIDFA.framework 对应的 Embed 切换到Embed & Sign。如下图:

若在项目中添加DXRiskStatic.framework或者DXRiskStaticWithIDFA.framework其中之一,需要在Build Settings -> Other Linker Flags 设置 -ObjC 如下图:

以下的操作仅限导入DXRisk.framework ,DXRiskWithIDFA.framework动态库
此步骤主要是解决上传Store架构不符合的问题,如项目中已配置过Carthage或有其他相关的打包Framework调整脚本,可略过此步自行调整 选择Target -> Build Phases,点击+按钮,添加如下脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"# This script loops through the frameworks embedded in the application and# removes unused architectures.find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORKdoFRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"EXTRACTED_ARCHS=()for ARCH in $ARCHSdoecho "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")doneecho "Merging extracted architectures: ${ARCHS}"lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"rm "${EXTRACTED_ARCHS[@]}"echo "Replacing original executable with thinned version"rm "$FRAMEWORK_EXECUTABLE_PATH"mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"done |
在iOS 12的环境最好在Capabilities->Access WiFi Information 进行开启操作,这样方便设备指纹数据采集。 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | // 风控组件:DXRiskManager类@interface DXRiskManager : NSObject// 字符串常量extern NSString* const DXRiskManagerKeyUserId;extern NSString* const DXRiskManagerKeyEmail;extern NSString* const DXRiskManagerKeyPhone;extern NSString* const DXRiskManagerKeyUserExtend1;extern NSString* const DXRiskManagerKeyUserExtend2;extern NSString* const DXRiskManagerKeyURL;//私有化服务器地址extern NSString* const DXRiskManagerKeyBackup;//私有化下使用,将数据备份到顶象服务器(开启为DXRiskManagerKeyBackupEnable 值)extern NSString* const DXRiskManagerKeyBackupAppId; // 私有化下使用,指定数据备份到顶象服务器的AppId extern NSString* const DXRiskManagerKeyDegradeNotify;//数据降级通知,若打开,服务端会有响应的降级统计extern NSString* const DXRiskManagerKeyCountry;//国家地区设置,默认中国extern NSString* const DXRiskManagerKeyDelayMsTime; //可填设置请求超时毫秒时间(默认值为 500 ,范围是:【100 : 3000】)// NoticeDegrade参数/* The NoticeDegrade Value. This value only be used pair with key:DXRiskManagerKeyDegradeNotify to notify token degrade. */extern NSString* const DXRiskManagerKeyDegradeNotifyEnable;// Backup参数/* The Backup Value. This value only be used pair with key:DXRiskManagerKeyBackup to set data backup. */extern NSString* const DXRiskManagerKeyBackupEnable;// Country参数/* The Country Value. This value only be used pair with key:DXRiskManagerKeyCountry to set country. */extern NSString* const DXRiskManagerCountryChina;/* The Country Value. This value only be used pair with key:DXRiskManagerKeyCountry to set country. */extern NSString* const DXRiskManagerCountryIndonesia;/** 采集端的设备指纹信息,上传至风控后台,再由风控后台返回token。 该API为耗时操作,因此必须在非主线程上调用。 @param appId appId 开通服务后可在二级菜单“应用管理”中获取 @param extendsParams 配置项 @return token */+ (NSString *)getToken:(NSString *)appId extendParams:(NSDictionary *)extendsParams;/** DXRiskManager -- 初始化方法 */+ (BOOL)setup; @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // 整个过程由于是耗时操作,必须要在非主线程上执行,否则会阻塞UI。如果本身已经在非UI线程上执行,则不需要另开线程 dispatch_queue_t dxrisk_queue = dispatch_queue_create("com.dingxiang.dxrisk", DISPATCH_QUEUE_CONCURRENT); dispatch_async(dxrisk_queue, ^{ NSMutableDictionary *dic = [NSMutableDictionary dictionary]; // 根据业务逻辑,填充自定义字段 [dic setObject:@"123456" forKey:DXRiskManagerKeyUserId]; // 如需设置海外服务器, 可选值请参考头文件字段 [dic setObject:DXRiskManagerServiceAreaSoutheastAsia forKey:DXRiskManagerKeyServiceArea]; // 如需自定义服务端URL,填充DXRiskManagerKeyURL字段(私有化部署的情况) // 如有需要则添加DXRiskManagerKeyBackup参数,Value固定为DXRiskManagerKeyBackupEnable // DXRiskManagerKeyBackupAppId参数可不填,参数为顶象备份数据库提供的备份Appid [dic setObject:@"http://xxxxxxx" forKey:DXRiskManagerKeyURL]; // [dic setObject:DXRiskManagerKeyBackupEnable forKey:DXRiskManagerKeyBackup]; [dic setObject:@"xxxxxxxxxxxxxxxxxxxx" forKey:DXRiskManagerKeyBackupAppId]; // 此配置关闭SDK缓存,表示每次调用接口都强制采集信息 // [dic setObject:@"clear" forKey:@"PRIVATE_CLEAR_TOKEN"]; // 获取token // 注意:token最好不要保存在某个局部变量或者字段,每次使用时,都通过API获取。 BOOL isSuccess = [DXRiskManager setup]; NSLog(@"setup success: %@" , isSuccess ? @"YES":@"NO"); NSString *constID = [DXRiskManager getToken:@"xxxxxxxxxxxxxxxxxxxx" extendsParams:dic]; NSLog(@"constID: %@", constID); // TODO 把constid通过Post请求,传到业务后台。 // 下面是模拟频繁调用的过程 while(TRUE) { NSLog(@"constID: %@", [DXRiskManager getToken:@"xxxxxxxxxxxxxxxxxxxx" extendsParams:dic]); [NSThread sleepForTimeInterval:.5]; } }); |
在获取token过程中,如果因为网络超时或者加解密失败,该接口有可能会返回为null,同时会输出tag为DXRISK的错误信息,具体描述如下:
1 2 3 4 5 6 7 | DXRISK_REQUEST_NETWORK_ERR -1001 //网络链接失败DXRISK_REQUEST_DECRYPT_ERR -1002 //数据解密错误DXRISK_REQUEST_UNCOMPRESS_ERR -1003 //解压错误DXRISK_REQUEST_RESPONSE_EMPTY_ERR -1004 //返回为空DXRISK_REQUEST_DATA_PARSE_ERR -1005 //数据解析失败DXRISK_REQUEST_DIRTY_DATA_ERR -1006 //脏数据DXRISK_CONST_ID_EMPTY -1007 //constid为空 |
请注意选择对应的版本,下载完成后放到本地项目中。
未注册用户可在顶象官网进行账号注册,创建应用获取应用密钥AppID和AppSecret。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | const ConstId = require('本地设备指纹js存放路径')Page({ onLoad: function () { new ConstId({ appId: '【这里填写在顶象官网申请到的 AppID】', // 唯一标识,必填 server: 'https://host/udid/w1' // 服务接口地址,请注意私有化客户填写自有域名 }, (e, id) => { if (e) { console.log(e) return } console.log('constId:', id) }) }}) |
c6dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0L8$3&6K6N6r3W2V1i4K6u0W2k6r3W2F1k6%4S2A6j5h3&6Y4i4K6u0V1K9h3&6U0i4K6u0W2j5$3!0E0,私有化用户配置本地部署域名,测试阶段可以开启微信开发者工具右上角详情->本地设置->“不校验域名”。接入方式和微信小程序类似,但是使用的SDK不一样。如有需要,请联系客服获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | const ConstId = require('本地设备指纹js存放路径')Page({ onLoad: function () { new ConstId({ appId: '【这里填写在顶象官网申请到的 AppID】', // 唯一标识,必填 server: 'https://host/udid/w1' // 服务接口地址,请注意私有化客户填写自有域名 }, (e, id) => { if (e) { console.log(e) return } console.log('constId:', id) }) }}) |
以上。
如果有设备指纹需求的话,可以前往顶象:免费试用