oauth2.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. /**
  2. * OAUTH2 授权
  3. * 登录,用户授权
  4. * <pre>
  5. * 作者:hugh zhuang
  6. * 邮箱:3378340995@qq.com
  7. * 日期:2018-10-08-下午3:29:34
  8. * 版权:广州流辰信息技术有限公司
  9. * </pre>
  10. */
  11. import { OAUTH2_URL } from "@/api/baseUrl";
  12. import request from "@/utils/request";
  13. import requestState from "@/constants/state";
  14. import { Message } from "element-ui";
  15. import qs from "qs";
  16. var AccessToken = function (data) {
  17. if (!(this instanceof AccessToken)) {
  18. return new AccessToken(data);
  19. }
  20. this.data = data;
  21. };
  22. /**
  23. * 对返回结果的一层封装,如果遇见IBPS平台返回的错误,将返回一个错误
  24. * 参见:IBPS返回码说明
  25. */
  26. var wrapper = function (callback) {
  27. return function (err, data, res) {
  28. callback = callback || function () {};
  29. if (err) {
  30. err.name = "IBPSAPI" + err.name;
  31. return callback(err, data, res);
  32. }
  33. callback(null, data, res);
  34. };
  35. };
  36. /*!
  37. * 检查AccessToken是否有效,检查规则为当前时间和过期时间进行对比
  38. *
  39. * Examples:
  40. * ```
  41. * token.isValid();
  42. * ```
  43. */
  44. AccessToken.prototype.isValid = function () {
  45. return (
  46. !!this.data.access_token &&
  47. new Date().getTime() < this.data.create_at + this.data.expires_in * 1000
  48. );
  49. };
  50. /*!
  51. * 处理token,更新过期时间
  52. */
  53. var processToken = function (that, callback) {
  54. var createAt = new Date().getTime();
  55. return function (err, data, res) {
  56. if (err) {
  57. return callback(err, data, res);
  58. }
  59. data.create_at = createAt;
  60. if (res.variables && res.variables.licJson) {
  61. data.licJson = JSON.parse(res.variables.licJson);
  62. }
  63. // 存储token
  64. that.saveToken(data.uid, data, function (err) {
  65. callback(err, new AccessToken(data));
  66. });
  67. };
  68. };
  69. /**
  70. * 根据clientId和clientSecret创建OAuth接口的构造函数
  71. * 如需跨进程跨机器进行操作,access token需要进行全局维护
  72. * 使用token的优先级是:
  73. *
  74. * 1. 使用当前缓存的token对象
  75. * 2. 调用开发传入的获取token的异步方法,获得token之后使用(并缓存它)。
  76. * Examples:
  77. * ```
  78. * import IbpsOAuth from '@/utils/oauth2'
  79. * var oauthApi = new OAuth('clientId', 'clientSecret');
  80. * ```
  81. * @param {String} clientId 在平台上申请得到的clientId(或appid)
  82. * @param {String} clientSecret 在平台上申请得到的client secret(或app secret)
  83. * @param {Function} getToken 用于获取token的方法
  84. * @param {Function} saveToken 用于保存token的方法
  85. */
  86. var OAuth = function (clientId, clientSecret, getToken, saveToken) {
  87. this.clientId = clientId;
  88. this.clientSecret = clientSecret;
  89. this.statistic = "";
  90. this.username = "";
  91. // token的获取和存储
  92. this.store = {};
  93. this.getToken =
  94. getToken ||
  95. function (uid, callback) {
  96. callback(null, this.store[uid]);
  97. };
  98. if (!saveToken && process.env.NODE_ENV === "production") {
  99. console.warn(
  100. "Please dont save oauth token into memory under production"
  101. );
  102. }
  103. this.saveToken =
  104. saveToken ||
  105. function (uid, token, callback) {
  106. this.store[uid] = token;
  107. callback(null);
  108. };
  109. this.defaults = {};
  110. };
  111. /*!
  112. * request的封装
  113. *
  114. * @param {String} url 路径
  115. * @param {Object} opts urllib选项
  116. * @param {Function} callback 回调函数
  117. */
  118. OAuth.prototype.request = function (opts, callback) {
  119. const options = {};
  120. Object.assign(options, this.defaults);
  121. if (typeof opts === "function") {
  122. callback = opts;
  123. opts = {};
  124. }
  125. for (const key in opts) {
  126. if (key !== "headers") {
  127. options[key] = opts[key];
  128. } else {
  129. if (opts.headers) {
  130. options.headers = options.headers || {};
  131. Object.assign(options.headers, opts.headers);
  132. }
  133. }
  134. }
  135. request(options)
  136. .then((response) => {
  137. const { state } = response;
  138. if (
  139. state === requestState.UNSUPORT ||
  140. state === requestState.WARNING
  141. ) {
  142. const err = new Error(response.message);
  143. err.state = state;
  144. err.cause = response.cause;
  145. callback(err);
  146. } else {
  147. callback(null, response.data, response);
  148. }
  149. })
  150. .catch((error) => {
  151. callback(error);
  152. });
  153. };
  154. /**
  155. * 获取授权页面的URL地址
  156. * @param {String} redirect 授权后要跳转的地址
  157. * @param {String} state 开发者可提供的数据
  158. * @param {String} scope 作用范围,值为snsapi_userinfo和snsapi_base,前者用于弹出,后者用于跳转
  159. */
  160. OAuth.prototype.getAuthorizeURL = function (redirect, state, scope) {
  161. const url = OAUTH2_URL() + "";
  162. const info = {
  163. clientId: this.clientId,
  164. redirect_uri: redirect,
  165. response_type: "code",
  166. scope: scope || "snsapi_base",
  167. state: state || "",
  168. };
  169. return url + "?" + qs.stringify(info) + "#ibps_redirect";
  170. };
  171. /**
  172. * 获取授权页面的URL地址
  173. * @param {String} redirect 授权后要跳转的地址
  174. * @param {String} state 开发者可提供的数据
  175. * @param {String} scope 作用范围,值为snsapi_login,前者用于弹出,后者用于跳转
  176. */
  177. OAuth.prototype.getAuthorizeURLForWebsite = function (redirect, state, scope) {
  178. const url = OAUTH2_URL() + "/qrconnect";
  179. const info = {
  180. clientId: this.clientId,
  181. redirect_uri: redirect,
  182. response_type: "code",
  183. scope: scope || "snsapi_login",
  184. state: state || "",
  185. };
  186. return url + "?" + qs.stringify(info) + "#ibps_redirect";
  187. };
  188. /**
  189. * 根据授权获取到的code,换取access_token和uid
  190. * 获取uid之后,可以调用`IBPS.API`来获取更多用户信息
  191. * Examples:
  192. * ```
  193. * api.getAccessTokenByCode(code, callback);
  194. * ```
  195. * Callback:
  196. *
  197. * - `error`, 获取access token出现异常时的异常对象
  198. * - `result`, 成功时得到的响应结果
  199. *
  200. * Result:
  201. * ```
  202. * {
  203. * data: {
  204. * "access_token": "ACCESS_TOKEN",
  205. * "refresh_token": "REFRESH_TOKEN",
  206. * "uid": "uid",
  207. * "expires_in": 7200,
  208. * "scope": "SCOPE"
  209. * }
  210. * }
  211. * ```
  212. * @param {String} code 授权获取到的code
  213. * @param {Function} callback 回调函数
  214. */
  215. OAuth.prototype.getAccessTokenByCode = function (code, callback) {
  216. localStorage.setItem("username", this.username);
  217. const args = {
  218. url: OAUTH2_URL() + "/authentication/apply",
  219. data: {
  220. client_id: this.clientId,
  221. client_secret: this.clientSecret,
  222. authorize_code: code,
  223. username: this.username,
  224. grant_type: "authorization_code",
  225. },
  226. method: "post",
  227. };
  228. this.request(args, wrapper(processToken(this, callback)));
  229. };
  230. /**
  231. * 密码授权模式
  232. * 根据用户名和密码,换取access token和uid
  233. * 获取uid之后,可以调用`IBPS.API`来获取更多用户信息
  234. * Examples:
  235. * ```
  236. * api.getAccessTokenByPassword(code, callback);
  237. * ```
  238. * Callback:
  239. *
  240. * - `error`, 获取access token出现异常时的异常对象
  241. * - `result`, 成功时得到的响应结果
  242. *
  243. * Result:
  244. * ```
  245. * {
  246. * data: {
  247. * "access_token": "ACCESS_TOKEN",
  248. * "refresh_token": "REFRESH_TOKEN",
  249. * "uid": "uid",
  250. * "expires_in": 7200,
  251. * "scope": "SCOPE"
  252. * }
  253. * }
  254. * ```
  255. * ```
  256. * @param {String} username 用户名
  257. * @param {String} password 密码
  258. * @param {Function} callback 回调函数
  259. */
  260. OAuth.prototype.getAccessTokenByPassword = function (
  261. { username, password },
  262. callback
  263. ) {
  264. const args = {
  265. url: OAUTH2_URL() + "/authentication/apply",
  266. data: {
  267. client_id: this.clientId,
  268. client_secret: this.clientSecret,
  269. username: username,
  270. password: password,
  271. grant_type: "password_credentials",
  272. },
  273. method: "post",
  274. };
  275. this.request(args, wrapper(processToken(this, callback)));
  276. };
  277. /**
  278. * 根据refresh token,刷新access token,调用getAccessTokenByCode后才有效
  279. * Examples:
  280. * ```
  281. * api.refreshAccessToken(refreshToken, callback);
  282. * ```
  283. * Callback:
  284. *
  285. * - `err`, 刷新access token出现异常时的异常对象
  286. * - `result`, 成功时得到的响应结果
  287. *
  288. * Result:
  289. * ```
  290. * {
  291. * data: {
  292. * "access_token": "ACCESS_TOKEN",
  293. * "expires_in": 7200,
  294. * "refresh_token": "REFRESH_TOKEN",
  295. * "uid": "uid",
  296. * "remind_in": 7
  297. * }
  298. * }
  299. * ```
  300. * @param {String} refreshToken 刷新tonken
  301. * @param {Function} callback 回调函数
  302. */
  303. OAuth.prototype.refreshAccessToken = function (refreshToken, callback) {
  304. const username = localStorage.getItem("username");
  305. const args = {
  306. url: OAUTH2_URL() + "/authentication/apply",
  307. data: {
  308. client_id: this.clientId,
  309. client_secret: this.clientSecret,
  310. grant_type: "refresh_token",
  311. username,
  312. refresh_token: refreshToken,
  313. },
  314. method: "post",
  315. };
  316. this.request(args, wrapper(processToken(this, callback)));
  317. };
  318. /**
  319. * 获取用户信息 【私有方法】
  320. * @param {*} options
  321. * @param {*} accessToken
  322. * @param {*} callback
  323. */
  324. OAuth.prototype._getUser = function (options, accessToken, callback) {
  325. const args = {
  326. url: OAUTH2_URL() + "/user/context",
  327. data: {
  328. access_token: accessToken,
  329. uid: options.uid,
  330. lang: options.lang || "zh_CN",
  331. },
  332. };
  333. this.request(args, wrapper(callback));
  334. };
  335. /**
  336. * 根据uid,获取用户信息。
  337. * 当access token无效时,自动通过refresh token获取新的access token。然后再获取用户信息
  338. * Examples:
  339. * ```
  340. * api.getUser(uid, callback);
  341. * api.getUser(options, callback);
  342. * ```
  343. *
  344. * Options:
  345. * ```
  346. * // 或
  347. * {
  348. * "uid": "the user Id", // 必须
  349. * "lang": "the lang code" // zh_CN 简体,zh_TW 繁体,en 英语
  350. * }
  351. * ```
  352. * Callback:
  353. *
  354. * - `err`, 获取用户信息出现异常时的异常对象
  355. * - `result`, 成功时得到的响应结果
  356. *
  357. * Result:
  358. * ```
  359. * {
  360. * "uid": "uid",
  361. * "nickname": "NICKNAME",
  362. * "sex": "1",
  363. * "province": "PROVINCE"
  364. * "city": "CITY",
  365. * "country": "COUNTRY",
  366. * "headimgurl": "http://xxxxx.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
  367. * "privilege": [
  368. * "PRIVILEGE1"
  369. * "PRIVILEGE2"
  370. * ]
  371. * }
  372. * ```
  373. * @param {Object|String} options 传入uid或者参见Options
  374. * @param {Function} callback 回调函数
  375. */
  376. OAuth.prototype.getUser = function (options, callback) {
  377. if (typeof options !== "object") {
  378. options = {
  379. uid: options,
  380. };
  381. }
  382. const that = this;
  383. this.getToken(options.uid, function (err, data) {
  384. if (err) {
  385. return callback(err);
  386. }
  387. // 没有token数据
  388. if (!data) {
  389. const message =
  390. "No token for " + options.uid + ", please authorize first.";
  391. Message({
  392. message: `${message}`,
  393. type: "error",
  394. showClose: true,
  395. dangerouslyUseHTMLString: true,
  396. duration: 5 * 1000,
  397. });
  398. const error = new Error(message);
  399. err.state = "NoOAuthTokenError";
  400. return callback(error);
  401. }
  402. const token = new AccessToken(data);
  403. if (token.isValid()) {
  404. that._getUser(options, token.data.access_token, callback);
  405. } else {
  406. that.refreshAccessToken(
  407. token.data.refresh_token,
  408. function (err, token) {
  409. if (err) {
  410. return callback(err);
  411. }
  412. that._getUser(options, token.data.access_token, callback);
  413. }
  414. );
  415. }
  416. });
  417. };
  418. /**
  419. * 检验授权凭证(access_token)是否有效。
  420. * Examples:
  421. * ```
  422. * api.verifyToken(uid, accessToken, callback);
  423. * ```
  424. * @param {String} uid 传入uid
  425. * @param {String} accessToken 待校验的access token
  426. * @param {Function} callback 回调函数
  427. */
  428. OAuth.prototype.verifyToken = function (uid, accessToken, callback) {
  429. const args = {
  430. url: OAUTH2_URL() + "/authentication/verify",
  431. params: {
  432. access_token: accessToken,
  433. uid: uid,
  434. },
  435. method: "post",
  436. };
  437. this.request(args, wrapper(callback));
  438. };
  439. /**
  440. * 用户名、密码方式登录。
  441. * Examples:
  442. * ```
  443. * api.userLogin(data, callback);
  444. * ```
  445. * @param {Objcct} data
  446. * username : 用户名
  447. * password : 密码
  448. * @param {Function} callback 回调函数
  449. */
  450. OAuth.prototype.userLogin = function (data, callback) {
  451. const args = {
  452. url: OAUTH2_URL() + "/user/login/apply",
  453. data: data,
  454. method: "post",
  455. };
  456. this.request(args, wrapper(callback));
  457. };
  458. /**
  459. *
  460. *申请授权
  461. * Examples:
  462. * ```
  463. * api.authorize(login, callback);
  464. * ```
  465. * @param {Objcct} options
  466. * @param {Function} callback 回调函数
  467. */
  468. OAuth.prototype.authorize = function (data, callback) {
  469. const url = OAUTH2_URL() + "/authorize/apply";
  470. if (typeof data !== "object") {
  471. data = {
  472. login_state: data,
  473. };
  474. }
  475. data.client_id = this.clientId;
  476. const args = {
  477. url,
  478. data: data,
  479. method: "post",
  480. };
  481. this.request(args, wrapper(callback));
  482. };
  483. /**
  484. * 根据code,获取用户信息。
  485. * Examples:
  486. * ```
  487. * api.getUserByCode(code, callback);
  488. * ```
  489. * Callback:
  490. *
  491. * - `err`, 获取用户信息出现异常时的异常对象
  492. * - `result`, 成功时得到的响应结果
  493. *
  494. * Result:
  495. * ```
  496. * {
  497. * "uid": "uid",
  498. * "nickname": "NICKNAME",
  499. * "sex": "1",
  500. * "province": "PROVINCE"
  501. * "city": "CITY",
  502. * "country": "COUNTRY",
  503. * "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
  504. * "privilege": [
  505. * "PRIVILEGE1"
  506. * "PRIVILEGE2"
  507. * ]
  508. * }
  509. * ```
  510. * @param {Object|String} options 授权获取到的code
  511. * @param {Function} callback 回调函数
  512. */
  513. OAuth.prototype.getUserByCode = function (options, callback) {
  514. const that = this;
  515. let lang;
  516. let code;
  517. if (typeof options === "string") {
  518. code = options;
  519. } else {
  520. lang = options.lang;
  521. code = options.code;
  522. }
  523. this.getAccessTokenByCode(code, function (err, result) {
  524. if (err) {
  525. return callback(err);
  526. }
  527. const uid = result.data.uid;
  528. that.getUser({ uid: uid, lang: lang }, callback);
  529. });
  530. };
  531. /**
  532. * 通过登录获取access_token
  533. * @param {*} options
  534. * @param {*} callback
  535. */
  536. OAuth.prototype.getLoginCode = function (options, callback) {
  537. this.username = options.username;
  538. const that = this;
  539. /**
  540. * 用户登录
  541. */
  542. this.userLogin(options, function (err, data, res) {
  543. if (err) {
  544. return callback(err);
  545. }
  546. that.statistic = res.variables.statistic; //判斷是否有统计权限
  547. // 没有token数据
  548. if (!data) {
  549. const message = "没有传回用户信息";
  550. Message({
  551. message: `${message}`,
  552. type: "error",
  553. showClose: true,
  554. dangerouslyUseHTMLString: true,
  555. duration: 5 * 1000,
  556. });
  557. const error = new Error(message);
  558. err.state = "NoOAuthTokenError";
  559. return callback(error);
  560. }
  561. that.authorize(data, function (err1, data1) {
  562. if (err1) {
  563. return callback(err1);
  564. }
  565. that.getAccessTokenByCode(data1, callback);
  566. });
  567. });
  568. };
  569. export default OAuth;