聚美智数


闪验一键登录H5

<p>[TOC]</p> <h3>概述</h3> <h4>产品说明</h4> <p>本产品以H5的方式接入运营商闪验功能,适用于移动端网页一键登录功能</p> <h4>注意点</h4> <ul> <li> <p>同一号码有输入错误次数限制 移动:输入错误三次就会被锁定24小时 电信:连续输错三次锁定24小时,不连续输错不锁定 联通:没有限制</p> </li> <li>闪验功能需要识别运营商,因此<strong>不能在Wifi环境下</strong>进行,需要<strong>关闭Wifi</strong></li> </ul> <h4>准备工作</h4> <p>开通闪验功能需要创建应用,运营商审核通过后方能对接。请联系客服提供以下资料:</p> <ol> <li>应用名称</li> <li>应用类型</li> <li>应用介绍</li> <li>所属行业</li> <li>应用ICON(最大:128 X 128 )</li> <li>登录页地址和域名。地址和域名必须按照实际填写,否则会报referer校验不通过的错误 &gt; 登录页地址和域名务必填写正确,审核通过后不能修改。建议直接提供生产环境的地址,开发测试可通过配置hosts解析到本地或测试环境</li> </ol> <p>运营商审核通过后,接入者可获取到应用的<strong>flashValidateAppId</strong>和<strong>flashValidateKey</strong>,后续集成对接中会使用</p> <h4>业务流程</h4> <p>本产品接入需要<strong>客户端(浏览器)</strong>和<strong>服务端</strong>均接入才能实现</p> <p><img src="https://file.jumdata.com/api-document/flash-validate/flash-validate-h5.jpg" alt="" /></p> <p>演示:<a href="https://api-h5.jumdata.com/flash-validate/demo">https://api-h5.jumdata.com/flash-validate/demo</a></p> <hr /> <h3>客户端接入</h3> <h4>准备工作</h4> <p>Web 页面需引入js和css</p> <pre><code class="language-html">&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;https://api-h5.jumdata.com/flash-validate/flash-validate.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt; &amp;lt;link type=&amp;quot;text/css&amp;quot; href=&amp;quot;https://www.cmpassport.com/h5/js/jssdk_auth/css/ydrz-layer.css&amp;quot;/&amp;gt;</code></pre> <p>创建JumeiFlashValidate实例</p> <pre><code class="language-javascript">// flashValidateAppId开通后服务商提供 // 实例一个页面只需要创建一次,建议在页面加载完成后创建 var jumeiFlashValidate = new JumeiFlashValidate(flashValidateAppId) </code></pre> <h4>界面配置(可选)</h4> <ul> <li>该方法为可选方法,本产品内置有默认界面配置,可以使用默认配置,降低开发及适配难度。</li> <li>如确需修改界面配置,须在初始化之前通过setUIConfig方法配置。</li> <li>支持授权页面弹窗/全屏模式、标题、应用logo,隐私协议等属性修改,开发者可选择修改其中一项或多项进行配置。</li> <li>授权页面弹窗/全屏模式三网均可通过api修改。</li> <li>标题、应用logo,隐私协议仅移动、联通支持通过api修改,电信需要发对接人员报备,运营商审核通过后方可生效。</li> </ul> <h5>全屏模式</h5> <p><img src="https://file.jumdata.com/api-document/flash-validate/%E5%85%A8%E5%B1%8F.png" alt="" /></p> <h5>弹窗模式</h5> <p><img src="https://file.jumdata.com/api-document/flash-validate/%E5%BC%B9%E7%AA%97.png" alt="" /></p> <h5>配置说明</h5> <ul> <li>页面标题:支持配置文案,限制10字以内,默认显示“本机号码登录”,默认水平居中显示。(仅支持全屏模式下)</li> <li>应用logo:支持配置应用logo,默认水平居中显示</li> <li>服务协议:在运营商协议后,支持增加配置协议。最多新增2个,总限制20字以内,默认水平居中显示</li> </ul> <p>&gt; 注意:该配置仅对移动、联通卡立即生效,电信配置需要发对接人员报备,运营商审核通过后方可生效</p> <h5>方法说明</h5> <pre><code class="language-javascript">var config={ setPageType, setLoginTitle, setLoginLogo, setPrivacyOne, setPrivacyTwo, } // jumeiFlashValidate为页面加载后,创建的JumeiFlashValidate实例 jumeiFlashValidate.setUIConfig(config, function (result) { });</code></pre> <p>config参数说明</p> <table> <thead> <tr> <th>参数</th> <th>类型</th> <th>&lt;div style=&quot;width:420px;&quot;&gt;说明&lt;/div&gt;</th> </tr> </thead> <tbody> <tr> <td>setPageType</td> <td>Boolean</td> <td>设置页面登录模式,全屏:false,弹窗:true,不填默认全屏</td> </tr> <tr> <td>setLoginTitle</td> <td>string</td> <td>页面标题</td> </tr> <tr> <td>setLoginLogo</td> <td>string</td> <td>平台logo的url,尺寸建议:80x80</td> </tr> <tr> <td>setPrivacyOne</td> <td>[]</td> <td>格式:[&#039;协议名称&#039;,&#039;链接地址&#039;]协议 1 名称,最多20个字符</td> </tr> <tr> <td>setPrivacyTwo</td> <td>[]</td> <td>格式:[&#039;协议名称&#039;,&#039;链接地址&#039;]协议 2 名称,最多 20个字符</td> </tr> </tbody> </table> <h4>初始化</h4> <p>需要在启动授权页前调用,一般在H5页面加载完成,并实例化JumeiFlashValidate后调用</p> <h5>方法说明</h5> <pre><code class="language-javascript">// jumeiFlashValidate为页面加载后,创建的JumeiFlashValidate实例 jumeiFlashValidate.init(function(result){ })</code></pre> <p>回调函数参数说明</p> <table> <thead> <tr> <th>参数</th> <th>&lt;div style=&quot;width:420px;&quot;&gt;说明&lt;/div&gt;</th> </tr> </thead> <tbody> <tr> <td>code</td> <td>&quot;000000&quot;代表成功,其他代表失败,详见客户端返回码说明</td> </tr> <tr> <td>message</td> <td>code对应的说明</td> </tr> </tbody> </table> <h4>启动授权页</h4> <p>在需要启动一键登录的页面,调用此方法会启动闪验H5内部的授权页面</p> <h5>方法说明</h5> <pre><code class="language-javascript">// jumeiFlashValidate为页面加载后,创建的JumeiFlashValidate实例 jumeiFlashValidate.open(function(result){ })</code></pre> <p>回调函数参数说明 </p> <table> <thead> <tr> <th>参数</th> <th>说明</th> </tr> </thead> <tbody> <tr> <td>code</td> <td>&quot;200000&quot;代表成功,其他状态码为失败,详见客户端返回码说明</td> </tr> <tr> <td>message</td> <td>code对应的说明</td> </tr> <tr> <td>token</td> <td>可获取置换手机号所需的token,调用服务端接口时需要用到此参数</td> </tr> </tbody> </table> <h5>注意事项</h5> <p>触发启动授权页函数的按钮必须设置<strong>id=&quot;j-get-code&quot;</strong></p> <pre><code class="language-html">&amp;lt;button id=&amp;quot;j-get-code&amp;quot;&amp;gt;本机号码登录&amp;lt;/button&amp;gt; </code></pre> <p>通过监听的方式添加事件,直接在按钮加点击事件,如果是电信号,会被电信的sdk所吞掉事件</p> <pre><code class="language-javascript">var button = document.getElementById(&amp;#039;j-get-code&amp;#039;) button.addEventListener(&amp;#039;click&amp;#039;, function () { })</code></pre> <p>需要与初始化在同一界面调用,否则电信卡可能会启动不了授权页</p> <p>如果采用Vue开发,只需把触发启动授权页函数设置<strong>id=&quot;j-get-code&quot;</strong>即可,无需执行button.addEventListener</p> <pre><code class="language-html">&amp;lt;button id=&amp;quot;j-get-code&amp;quot; @click=&amp;quot;open()&amp;quot;&amp;gt;本机号码登录&amp;lt;/button&amp;gt;</code></pre> <h4>客户端返回码说明</h4> <table> <thead> <tr> <th>返回码</th> <th>&lt;div style=&quot;width:420px;&quot;&gt;说明&lt;/div&gt;</th> </tr> </thead> <tbody> <tr> <td>000000</td> <td>初始化成功</td> </tr> <tr> <td>000400</td> <td>初始化失败</td> </tr> <tr> <td>000500</td> <td>自定义配置失败,协议长度不能超过二十字符</td> </tr> <tr> <td>000510</td> <td>参数错误</td> </tr> <tr> <td>000600</td> <td>Sdk加载失败</td> </tr> <tr> <td>000700</td> <td>自定义配置成功</td> </tr> <tr> <td>100001</td> <td>未知运营商,可能的原因:未关闭wifi、没有信号、移动校验号码错误超限</td> </tr> <tr> <td>100101</td> <td>用户拒绝授权</td> </tr> <tr> <td>100102</td> <td>电信号码不匹配(仅电信号返回)</td> </tr> <tr> <td>100103</td> <td>电信校验错误次数超限(仅电信号返回)</td> </tr> <tr> <td>200000</td> <td>获取token成功</td> </tr> </tbody> </table> <hr /> <h3>服务端接入</h3> <p>客户端授权成功之后,需要由接入者的服务端再调用服务商的获取手机号接口获取手机号,本接口即完成该功能 &gt; 服务端接口需要采用appId、appSecret生成并且传入sign,请不要在客户端直接调用服务商的服务端接口,以免暴露appSecret。应该由接入者的客户端调用接入者的服务端,再由接入者的服务端调用服务商的服务端接口</p> <h4>请求地址</h4> <p><code>https://api.jumdata.com/flash-validate/mobile/get</code></p> <h4>请求方式</h4> <ul> <li>POST</li> </ul> <h4>请求格式</h4> <ul> <li>application/x-www-form-urlencoded</li> </ul> <h4>请求参数</h4> <table> <thead> <tr> <th>名称</th> <th>类型</th> <th>必须</th> <th>&lt;div style=&quot;width:420px;&quot;&gt;说明&lt;/div&gt;</th> </tr> </thead> <tbody> <tr> <td>appId</td> <td>String</td> <td>是</td> <td>服务商分配的唯一标识</td> </tr> <tr> <td>timestamp</td> <td>Long</td> <td>是</td> <td>当前时间戳(毫秒)</td> </tr> <tr> <td>sign</td> <td>String</td> <td>是</td> <td>签名,详见签名算法说明</td> </tr> <tr> <td>flashValidateAppId</td> <td>String</td> <td>是</td> <td>开通后服务商提供,和客户端一致</td> </tr> <tr> <td>token</td> <td>String</td> <td>是</td> <td>闪验客户端启动授权页返回的token</td> </tr> <tr> <td>flashValidateSign</td> <td>String</td> <td>是</td> <td>闪验签名,详见闪验签名说明</td> </tr> <tr> <td>clientIp</td> <td>String</td> <td>否</td> <td>客户端IP,由客户服务端获取的前端APP的IP,如需要使用反欺诈核验功能则传入,否则可以不传</td> </tr> </tbody> </table> <h4>接口签名算法说明</h4> <pre><code>sign = sha256(appId + appSecret + timestamp)</code></pre> <p>用服务商分配的 <strong>appId</strong>、服务商分配的 <strong>appSecret</strong>,当前时间戳(毫秒) <strong>timestamp</strong>,按上述顺序拼接成字符串,再进行 <strong>sha256</strong> 哈希得到。如下:</p> <pre><code class="language-java">String appId = &amp;quot;xyzxy2121zxyz&amp;quot;; String timestamp = &amp;quot;1555378976238&amp;quot;; String appSecret = &amp;quot;efcefcef1121cefcefc1212121&amp;quot;; String str = appId + appSecret + timestamp; String sign = sha256(str);</code></pre> <h4>闪验签名说明</h4> <pre><code class="language-java">sign = sha256(flashValidateAppId + flashValidateAppKey + token)</code></pre> <p>闪验应用的 <strong>flashValidateAppId</strong>、<strong>flashValidateAppKey</strong>,闪验授权页返回的 <strong>token</strong>,按上述顺序拼接成字符串,再进行 <strong>sha256</strong> 哈希得到。如下:</p> <pre><code class="language-java">String flashValidateAppId = &amp;quot;124324343&amp;quot;; String flashValidateAppKey = &amp;quot;xyzxy21121rewrer12yuyu1zxyz&amp;quot;; String token = &amp;quot;xyzxy21121rewrer12yuyu1zxyz23123xyzxy21121rewrer12yuyu1zx321321&amp;quot;; String str = flashValidateAppId + flashValidateAppKey + token; String sign = sha256(str);</code></pre> <h4>获取号码成功返回样例</h4> <pre><code class="language-json">{ &amp;quot;code&amp;quot;: 200, &amp;quot;msg&amp;quot;: &amp;quot;成功&amp;quot;, &amp;quot;taskNo&amp;quot;: &amp;quot;987272522132093927999832&amp;quot;, &amp;quot;charge&amp;quot;: true, &amp;quot;data&amp;quot;: { &amp;quot;mobile&amp;quot;: &amp;quot;BB9736648935DDD2F67C784126AEB7E2&amp;quot; // 手机号码,AES加密,详见手机号解密说明 } }</code></pre> <h4>获取号码失败返回样例</h4> <pre><code class="language-json">{ &amp;quot;code&amp;quot;: 301, &amp;quot;msg&amp;quot;: &amp;quot;取号失败,号码补填不正确&amp;quot;, &amp;quot;taskNo&amp;quot;: &amp;quot;987272522132093927999832&amp;quot;, &amp;quot;charge&amp;quot;: true }</code></pre> <h4>返回字段说明</h4> <table> <thead> <tr> <th>字段名</th> <th>&lt;div style=&quot;width:420px&quot;&gt;说明&lt;/div&gt;</th> </tr> </thead> <tbody> <tr> <td>code</td> <td>返回码,详见:服务端返回码说明</td> </tr> <tr> <td>msg</td> <td>code对应的描述</td> </tr> <tr> <td>charge</td> <td>计费标志 true为计费,false为不计费</td> </tr> <tr> <td>taskNo</td> <td>本次请求号</td> </tr> <tr> <td>data</td> <td>返回具体结果,object类型,详见data返回字段描述</td> </tr> </tbody> </table> <h4>服务端返回码说明</h4> <table> <thead> <tr> <th>code</th> <th>&lt;div style=&quot;width:420px;&quot;&gt;说明&lt;/div&gt;</th> </tr> </thead> <tbody> <tr> <td>200</td> <td>成功(计费)</td> </tr> <tr> <td>301</td> <td>取号失败,号码补填不正确(计费)</td> </tr> <tr> <td>400</td> <td>参数错误</td> </tr> <tr> <td>404</td> <td>接口地址不正确</td> </tr> <tr> <td>500</td> <td>系统维护,请稍候再试</td> </tr> <tr> <td>601</td> <td>接口未开通</td> </tr> <tr> <td>602</td> <td>账号停用</td> </tr> <tr> <td>604</td> <td>接口停用</td> </tr> <tr> <td>606</td> <td>调用超限,请稍候再试</td> </tr> <tr> <td>607</td> <td>ip不在白名单</td> </tr> <tr> <td>609</td> <td>请求过于频繁,请稍候再试</td> </tr> <tr> <td>610</td> <td>请求超时</td> </tr> <tr> <td>999</td> <td>其他,以实际返回为准</td> </tr> </tbody> </table> <h4>手机号解密说明</h4> <p>mobile字段采用加密返回,加密算法/分组加密模式/分组填充方式为<strong>AES/CBC/PKCS7Padding</strong>,密文为<strong>16进制</strong>字符串,解密时需要做对应转换。秘钥为<strong>md5(flashValidateKey)前16位字符串</strong>,初始化向量为<strong>md5(flashValidateKey)后16位字符密串</strong></p> <p>示例代码</p> <ul> <li>解密</li> </ul> <pre><code class="language-java">String tmp = DigestUtils.md5Hex(flashValidateKey); String aesKey = StringUtil.substring(tmp, 0, 16); // 密钥 String aesIv = StringUtil.substring(tmp, 16); // 初始化向量 Striing mobile = AES256Util.decrypt(mobileCipher, aesKey, aesIv); // mobileCipher为接口返回的手机号密文</code></pre> <ul> <li>AES256Util.java</li> </ul> <pre><code class="language-java">package com.anq.jumeidata.openapi.util; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.encoders.Hex; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.security.Security; /** * AES加密工具 */ @Slf4j public class AES256Util { private static final String ENCODING = &amp;quot;UTF-8&amp;quot;; private static final String ALGORITHM = &amp;quot;AES&amp;quot;; /** * 加密算法/分组加密模式/分组填充方式 */ private static final String ALGORITHM_CBC_PADDING = &amp;quot;AES/CBC/PKCS7Padding&amp;quot;; /** * 使用PKCS7Padding填充必须添加一个支持PKCS7Padding的Provider * 类加载的时候就判断是否已经有支持256位的Provider,如果没有则添加进去 */ static { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } } /** * 加密 * * @param content 需要加密的内容 * @param secret 密钥 * @param iv 向量 * @return */ public static String encrypt(String content, String secret, String iv) throws AES256Exception { byte[] plainByte; try { plainByte = content.getBytes(ENCODING); } catch (UnsupportedEncodingException e) { throw new AES256Exception(e); } byte[] encryptByte = encrypt(plainByte, secret, iv); return Hex.toHexString(encryptByte); } /** * 加密 * * @param content 需要加密的内容 * @param secret 密钥 * @param iv 向量 * @return */ public static byte[] encrypt(byte[] content, String secret, String iv) throws AES256Exception { try { byte[] secretByte = secret.getBytes(); SecretKeySpec skey = new SecretKeySpec(secretByte, ALGORITHM); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes()); Cipher cipher = Cipher.getInstance(ALGORITHM_CBC_PADDING);// &amp;quot;算法/加密/填充&amp;quot; cipher.init(Cipher.ENCRYPT_MODE, skey, ivParameterSpec); //初始化加密器 return cipher.doFinal(content); // 加密 } catch (Exception e) { throw new AES256Exception(e); } } /** * @param content 需要解密的内容 * @param secret 密钥 * @param iv 向量 * @return */ public static String decrypt(String content, String secret, String iv) throws AES256Exception { byte[] encryptByte; try { encryptByte = Hex.decodeStrict(content); } catch (Exception e) { throw new AES256Exception(e); } byte[] plaintextByte = decrypt(encryptByte, secret, iv); if (plaintextByte == null) { return null; } try { return new String(plaintextByte, ENCODING); } catch (UnsupportedEncodingException e) { log.warn(e.getMessage(), e); return null; } } /** * @param content 需要解密的内容 * @param secret 密钥 * @param iv 向量 */ public static byte[] decrypt(byte[] content, String secret, String iv) throws AES256Exception { try { SecretKeySpec skey = new SecretKeySpec(secret.getBytes(), ALGORITHM); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(ENCODING)); Cipher cipher = Cipher.getInstance(ALGORITHM_CBC_PADDING);// 创建密码器 cipher.init(Cipher.DECRYPT_MODE, skey, ivParameterSpec);// 初始化解密器 return cipher.doFinal(content); } catch (Exception e) { throw new AES256Exception(e); } } public static class AES256Exception extends Exception { public AES256Exception(String message, Throwable cause) { super(message, cause); } public AES256Exception(Throwable cause) { super(cause); } } } </code></pre> <p>需要依赖</p> <pre><code class="language-xml">&amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.bouncycastle&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;bcprov-jdk15to18&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;1.77&amp;lt;/version&amp;gt; &amp;lt;/dependency&amp;gt;</code></pre>

页面列表

ITEM_HTML