如果您的应用和BMOP对接后,为保证接口交互的安全性与隐私性,您的应用需要取得的授权才能访问接口。
力方帐号授权,采用国际通用的OAuth2.0标准协议,作为用户身份验证与授权协议,支持网站、手机客户端、桌面客户端。 若要了解更多关于 OAuth2.0 的技术说明文档,可参看官方网站(http://oauth.net/2/) 。
目前力方OAuth2.0服务仅支持Server-side flow获取Access Token(授权令牌)的方式。 此流程要求ISV应用有Web Server应用,能够保存应用本身的密钥以及状态,可以通过http直接访问力方的授权服务器。
此流程需要您有自己的web服务器,能够保存应用本身的密钥以及状态,可以通过http直接访问力方的授权服务器。
为了保护开发者利益,防止用户越权使用,所有用户在使用应用之前,必须进行授权编号绑定,在开放平台“控制台 > 应用管理 > 授权管理”页面进行设置,将需要授权的用户的A编号添加到应用授权用户列表,该设置将直接影响应用的安全等级。
业务方式 | 入口地址(HTTP) | 入口地址(HTTPS) |
获取授权码(code) | http://oauth.bm001.com/authorize | https://oauth.bm001.com/authorize |
获取接口访问令牌(accessToken) | http://oauth.bm001.com/token | https://oauth.bm001.com/token |
参数说明 | |||
参数名字 | 参数选项 | 参数值 | 参数释义 |
client_id | 必选 | 即应用的appKey | |
response_type | 必选 | code | |
redirect_uri | 必选 | 参见redirect_uri的定义 | |
state | 可选 | 维持应用的状态,传入值与返回值保持一致。 | |
view | 可选 | 默认为web | 授权页面样式,不传默认web,可选值:web、app |
登录成功,进入授权界面,如果用户点击"授权"按钮,力方开放平台会将授权码code返回到回调地址上面,应用接下来就可以用该code去换取accessToken。
返回值说明 | |
名称 | 说明 |
code | 授权码(只能使用一次,10分钟有效期) |
state | 与请求授权时传入的一样 |
error | 错误信息 |
换取accessToken请求参数说明 | |||
参数名称 | 参数选项 | 参数值 | 参数释义 |
client_id | 必选 | appKey | |
grant_type | 必选 | authorization_code | 用code请求token时传authorization_code |
code | 必选 | 每个授权码只能使用1次,10分钟内有效 | |
state | 可选 | 维持应用的状态,传入值与返回值保持一致。 | |
sign | 必选 | 请求签名,具体签名方法详见本文档最下方签名方法 |
{ status: 1, errorCode: 0, errorMessage: null, data: { access_token: "ca79e16d7363682d1bd41b6d99140115", expires_in: 86400, refresh_token: "84c97358d110fa81c5d89f496c49913e", re_expires_in: 86400, token_type: "Bearer", parent_id: "A00000", user_id: "A854800", user_nick: "qmopen", sub_user_id: "E183727", sub_user_nick: "maomao" } }
{ status: 0, errorCode: 104, errorMessage: "code不存在或已失效!", data: null }
换取access_token返回参数说明 | ||||
名称 | 类型 | 示例 | 说明 | |
token_type | String | Bearer | 目前accessToken类型只支持(Bearer) | |
access_token | String | ca79e16d7363682d1bd41b6d99140115 | Access Token即授权访问令牌 | |
expires_in | number | 86400(表示86400秒后过期) | Access Token过期时间(单位:秒) | |
refresh_token | String | 84c97358d110fa81c5d89f496c49913e | Refresh Token 即刷新令牌 | |
re_expires_in | number | 86400(表示86400秒后过期) | Refresh Token过期时间(单位:秒) | |
parent_id | String | A00000 | 授权用户的上级编号 | |
user_id | String | A854800 | 力方账号对应的ID | |
user_nick | String | qmopen | 力方登录账号 | |
sub_user_id | String | E183727 | 商家员工账号对应的ID | |
sub_user_nick | String | maomao | 商家员工登录账号 |
access_token是有一定的有效期的,请在access_token过期之前刷新,请求方法使用POST请求。通过授权获取的refresh_token,可以用来刷新access_token的时长,操作方法类似换取access_token,仅仅请求参数有区别,说明如下:
刷新access_token参数说明 | |||
参数名称 | 参数选项 | 参数值 | 参数释义 |
client_id | 必选 | 即应用的appKey | |
grant_type | 必选 | refresh_token | 刷新token时传refresh_token |
state | 可选 | 维持应用的状态,传入值与返回值保持一致。 | |
sign | 必选 | 请求签名,签名方法具体见下方签名方法 | |
refresh_token | 必选 | refreshToken值 | 刷新令牌,请在refreshToken有效期内刷新accesstoken,每次刷新后,accessToken和refreshToken都会变更 |
为了更加灵活的对力方开放平台开放的接口和数据进行安全有效的管控,从用户的安全角度出发,减少用户数据泄露或者被恶意更改的可能,力方开放平台对开发者的应用进行分级处理。
如何提高自己应用的安全等级,点击安全等级说明,查看应用的安全等级说明文档。
为了减少数据泄露或者被恶意更改的可能,授权时长根据应用等级、应用类型分别给予不同的有效时长。
授权时长与应用安全等级关系 | ||
应用安全等级 | accessToken有效时长 | refreshToken失效时长 |
2级 | 固定时长90天 | 同accessToken时长 |
1级 | 固定时长30天 | 同accessToken时长 |
0级 | 固定时长1天 | 同accessToken时长 |
最近有一些开发者反馈accessToken的授权和获取太过于麻烦,并且开发者承诺能保证自身的accessToken安全,希望能够得到一个长久的accessToken。 为了解决满足这个需求,现在有获取长期accessToken的方法, 登录“直销商系统 > 数据中心 > 开发工具”会看到下面有一个accessToken,点击更新,将会获取新的accessToken,有效期30年。
授权常见错误码说明 | |||
errorCode | errorMessage | ||
100 | 系统繁忙,请稍后再试! | ||
101 | client_id不存在或已删除! | ||
102 | 此应用尚未审核通过或者已被暂时关闭! | ||
103 | 签名不正确! | ||
104 | code不存在或已失效! | ||
105 | code和client_id不匹配! | ||
106 | refresh_token不能为空! | ||
107 | refresh_token不存在或已过期! | ||
108 | code不能为空! | ||
109 | redirect_uri和callback不在同一根域名 | ||
110 | 您的用户编号还未被绑定到该应用,请联系应用所有者设置 | ||
111 | 刷新次数超过上限,每个accessToken一天最多可刷新60次 | ||
112 | 您还没有签订合同或者合同已到期 | ||
113 | 您还没有订购该应用或者订购的应用已到期 | ||
114 | 您不是应用所属用户的下级,不能授权该应用 | ||
115 | 该用户已被冻结,不能登录授权或刷新 |
redirect_uri指的是应用发起请求时所传的回调地址参数,在用户登录授权后,力方授权服务器会回调redirect_uri地址,将code查询码一并返回,可在接受到code以后发起post请求,换取最终的accessToken令牌。
举例:假如您的应用回调地址为redirect_uri=http://www.XXXXX.com/qianmi/auth/callBack,授权成功后,力方授权服务器会回调该地址,将code返回,您的应用在接受到code以后就可以进行下一步换取accessToken操作。
view参数可选值:web、app。web对应普通pc端的浏览器页面样式,app对应手机等无线客户端的浏览器页面样式。没有任何特殊说明的情况下,默认为web样式,如果通过手机端访问授权,必须指定为app。
client端的状态值。用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回。请务必严格按照流程检查用户与state参数状态的绑定。
Access Token是用户授权后颁发的session key,即接口访问令牌,同一应用、同一授权用户的Access Token是唯一的。
Refresh Token是随accessToken一起颁发的刷新令牌,RefreshToken用来在accessToken过期之前重新刷新accessToken
E生活应用不能直接用BOSS账号登录授权,必须使用一个直销商账号来登录授权。
这里仅展示如何利用SDK获取accessToken的示例代码(需要使用SDK),请求成功以后,将responsejson串转化成对象,然后从里面可以解析各相关字段
import com.qianmi.open.api.ApiException; import com.qianmi.open.api.response.TokenResponse; import com.qianmi.open.api.tool.util.OAuthUtils; import com.qianmi.open.api.tool.util.QianmiContext; public class AuthorizeDemo { /** * 根据授权码获取token * @param appKey * @param appSecret * @param code * @return * @throws ApiException */ public String getToken(String appKey, String appSecret, String code) throws ApiException { QianmiContext context = OAuthUtils.getToken(appKey, appSecret, code); TokenResponse response = context.getTokenResponse(); String accessToken = response.getAccessToken(); return accessToken; } /** * 根据refreshToken刷新token * @param appKey * @param appSecret * @param refreshToken * @return * @throws ApiException */ public String refreshToken(String appKey, String appSecret, String refreshToken) throws ApiException { QianmiContext context = OAuthUtils.refreshToken(appKey, appSecret, refreshToken); TokenResponse response = context.getTokenResponse(); String accessToken = response.getAccessToken(); return accessToken; } }
<?php $url = "http://oauth.bm001.com/token"; $client_id = "100000"; $appSecret = "pi8YzLlPtBTJjEiwS4f4G74EfmSBIV2m"; $grant_type = "authorization_code"; $code = "2918e3cae67108d3151eb6fad6888b1a"; $data = Array ( 'client_id' => $client_id, 'code' => $code, 'grant_type' => $grant_type ); ksort($data); $plain_text=""; foreach($data as $key => $value) { $plain_text .= $key.$value; } $plain_text = $appSecret.$plain_text.$appSecret; $sign = strtoupper(sha1($plain_text)); $data['sign'] = $sign; ksort($data); $url_params = ""; foreach ($data as $key => $value) { $url_params .= "&".$key."=".$value; } $url_params = ltrim($url_params,"&"); //curl初始化 $ch = curl_init(); curl_setopt ( $ch, CURLOPT_URL, $url ); curl_setopt ( $ch, CURLOPT_POST, 1 ); curl_setopt ( $ch, CURLOPT_HEADER, 0 ); curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt ( $ch, CURLOPT_POSTFIELDS, $url_params ); $return = curl_exec ( $ch ); //出错检测 if(curl_errno($ch)){ echo "curl error:".curl_errno($ch); }else{ print_r($return); } curl_close ( $ch ); ?>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Api; using System.Api.Util; namespace ConsoleApplication { class OAuthDemo { /// <summary> /// 根据授权码获取token /// <summary> /// <param name="appKey">应用appkey</param> /// <param name="appSecret">应用密钥</param> /// <param name="code">授权码</param> /// <returns>AccessToken</returns> string getToken(string appKey, string appSecret, string code) { QianmiContext context = OAuthUtils.getToken(appKey, appSecret, code); return context.Token.AccessToken; } /// <summary> /// 根据授权码获取token /// <summary> /// <param name="appKey">应用appkey</param> /// <param name="appSecret">应用密钥</param> /// <param name="refreshToken">RefreshToken</param> /// <returns>AccessToken</returns> string refreshToken(string appKey, string appSecret, string refreshToken) { QianmiContext context = OAuthUtils.refreshToken(appKey, appSecret, refreshToken); return context.Token.AccessToken; } } }
为了防止在获取accessToken或者refreshToken过程中被黑客恶意篡改,需要对请求参数进行签名验证,签名不合法的请求将会被拒绝,力方开放平台目前支持的签名算法为SHA1,签名的大致过程如下:
说明:SHA1为安全哈希算法,主要用于数字签名标准(DSS)中的签名算法(DSA),SHA1会产生一个160位的消息摘要,用16进制表示,一个16进制的字符能表示4个位,所以签名后的长度固定为40个16进制字符。
JAVA签名示例代码package com.qianmi.util; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.util.*; /** * Created by QianMi */ public class SignUtil { public static String sha1(String str) throws IOException { return byte2hex(getSHA1Digest(str)); } private static byte[] getSHA1Digest(String data) throws IOException { byte[] bytes; try { MessageDigest md = MessageDigest.getInstance("SHA-1"); bytes = md.digest(data.getBytes("utf-8")); } catch (GeneralSecurityException gse) { throw new IOException(gse); } return bytes; } private static String byte2hex(byte[] bytes) { StringBuilder sign = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i] & 0xFF); if (hex.length() == 1) { sign.append("0"); } sign.append(hex.toUpperCase()); } return sign.toString(); } public static String sign(Map<String, String> param, String secret) throws IOException { if (param == null) { return null; } StringBuilder sb = new StringBuilder(); List<String> paramNames = new ArrayList<>(param.size()); paramNames.addAll(param.keySet()); Collections.sort(paramNames); sb.append(secret); for (String paramName : paramNames) { sb.append(paramName).append(param.get(paramName)); } sb.append(secret); return sha1(sb.toString()); } public static void main(String[] args) throws IOException { Map<String, String> param = new HashMap<>(); param.put("access_token", "7466bdfc5f79a7fe1defd9a5880a4b84"); param.put("v", "1.1"); param.put("method", "bm.elife.recharge.mobile.getItemInfo"); param.put("timestamp", "1428488009985"); param.put("mobileNo", "13888888888"); param.put("rechargeAmount", "100"); System.out.println(sign(param, "test")); } }
1、为什么一定要通过浏览器授权,不能提供登录接口?
之所以不提供登录接口,是从安全角度考虑的,防止可能的第三方系统获取您的用户名、密码等资料。力方开放平台采用业内普遍使用的oAuth2.0协议进行登录授权认证,统一跳转力方授权服务器进行账号登录授权,授权完毕回调code至各ISV应用以便完成接口访问令牌(access_token)的换取。
2、我注册的是E生活商家后台应用,我们的会员需要进行授权吗?
E生活商家后台应用仅需要绑定的下级直销商账号授权即可,无需第三方会员账号在力方授权。此问题更多描述请参考“接入指南”中的常见问题回答。