oauth.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. # -*- coding: utf-8 -*-
  2. """
  3. wechatpy.oauth
  4. ~~~~~~~~~~~~~~~
  5. This module provides OAuth2 library for WeChat
  6. :copyright: (c) 2014 by messense.
  7. :license: MIT, see LICENSE for more details.
  8. """
  9. from __future__ import absolute_import, unicode_literals
  10. import requests
  11. import six
  12. from six.moves.urllib.parse import quote
  13. from library import to_binary, to_text
  14. from library.wechatbase.exceptions import WeChatOAuthException
  15. from library.wechatpy.utils import json
  16. class WeChatOAuth(object):
  17. """ 微信公众平台 OAuth 网页授权
  18. 详情请参考
  19. https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505
  20. """
  21. API_BASE_URL = 'https://api.weixin.qq.com/'
  22. OAUTH_BASE_URL = 'https://open.weixin.qq.com/connect/'
  23. def __str__(self):
  24. _repr = '{kclass}(appid: {appid})'.format(
  25. kclass = self.__class__.__name__,
  26. appid = self.app_id)
  27. if six.PY2:
  28. return to_binary(_repr)
  29. else:
  30. return to_text(_repr)
  31. def __repr__(self):
  32. return str(self)
  33. def __init__(self, app_id, secret, scope = 'snsapi_base', state = ''):
  34. """
  35. :param app_id: 微信公众号 app_id
  36. :param secret: 微信公众号 secret
  37. :param scope: 可选,微信公众号 OAuth2 scope,默认为 ``snsapi_base``
  38. :param state: 可选,微信公众号 OAuth2 state
  39. """
  40. self.app_id = app_id
  41. self.secret = secret
  42. self.scope = scope
  43. self.state = state
  44. def _request(self, method, url_or_endpoint, **kwargs):
  45. if not url_or_endpoint.startswith(('http://', 'https://')):
  46. url = '{base}{endpoint}'.format(
  47. base = self.API_BASE_URL,
  48. endpoint = url_or_endpoint
  49. )
  50. else:
  51. url = url_or_endpoint
  52. if isinstance(kwargs.get('data', ''), dict):
  53. body = json.dumps(kwargs['data'], ensure_ascii = False)
  54. body = body.encode('utf-8')
  55. kwargs['data'] = body
  56. kwargs['timeout'] = kwargs.get('timeout', 15)
  57. with requests.sessions.Session() as session:
  58. res = session.request(
  59. method = method,
  60. url = url,
  61. **kwargs
  62. )
  63. try:
  64. res.raise_for_status()
  65. except requests.RequestException as reqe:
  66. raise WeChatOAuthException(
  67. errCode = 'HTTP{}'.format(res.status_code),
  68. errMsg = reqe.message,
  69. client = self,
  70. request = reqe.request,
  71. response = reqe.response
  72. )
  73. result = json.loads(res.content.decode('utf-8', 'ignore'), strict = False)
  74. if 'errcode' in result and result['errcode'] != 0:
  75. errcode = result['errcode']
  76. errmsg = result['errmsg']
  77. raise WeChatOAuthException(
  78. errCode = errcode,
  79. errMsg = errmsg,
  80. client = self,
  81. request = res.request,
  82. response = res
  83. )
  84. return result
  85. def _get(self, url, **kwargs):
  86. return self._request(
  87. method = 'get',
  88. url_or_endpoint = url,
  89. **kwargs
  90. )
  91. def authorize_url(self, redirect_uri):
  92. """获取授权跳转地址
  93. :return: URL 地址
  94. """
  95. redirect_uri = quote(redirect_uri, safe = b'')
  96. url_list = [
  97. self.OAUTH_BASE_URL,
  98. 'oauth2/authorize?appid=',
  99. self.app_id,
  100. '&redirect_uri=',
  101. redirect_uri,
  102. '&response_type=code&scope=',
  103. self.scope
  104. ]
  105. if self.state:
  106. url_list.extend(['&state=', self.state])
  107. else:
  108. url_list.extend(['&state=', ''])
  109. url_list.extend(['&connect_redirect=', '1'])
  110. url_list.append('#wechat_redirect')
  111. return ''.join(url_list)
  112. def qrconnect_url(self, redirect_uri):
  113. """生成扫码登录地址
  114. :return: URL 地址
  115. """
  116. redirect_uri = quote(redirect_uri, safe = b'')
  117. url_list = [
  118. self.OAUTH_BASE_URL,
  119. 'qrconnect?appid=',
  120. self.app_id,
  121. '&redirect_uri=',
  122. redirect_uri,
  123. '&response_type=code&scope=',
  124. 'snsapi_login' # scope
  125. ]
  126. if self.state:
  127. url_list.extend(['&state=', self.state])
  128. url_list.append('#wechat_redirect')
  129. return ''.join(url_list)
  130. def get_oauth_token(self, auth_code):
  131. """获取网页授权access token
  132. :param auth_code: 授权完成跳转回来后 URL 中的 code 参数
  133. :return: JSON 数据包
  134. """
  135. res = self._get(
  136. 'sns/oauth2/access_token',
  137. params = {
  138. 'appid': self.app_id,
  139. 'secret': self.secret,
  140. 'code': auth_code,
  141. 'grant_type': 'authorization_code'
  142. }
  143. )
  144. return res
  145. def refresh_access_token(self, refresh_token):
  146. """刷新 access token
  147. :param refresh_token: OAuth2 refresh token
  148. :return: JSON 数据包
  149. """
  150. res = self._get(
  151. 'sns/oauth2/refresh_token',
  152. params = {
  153. 'appid': self.app_id,
  154. 'grant_type': 'refresh_token',
  155. 'refresh_token': refresh_token
  156. }
  157. )
  158. return res
  159. def get_user_info(self, openid, access_token, lang = 'zh_CN'):
  160. """获取用户信息
  161. :param openid: 微信 openid,默认获取当前授权用户信息
  162. :param access_token: 网页授权access token
  163. :param lang: 可选,语言偏好, 默认为 ``zh_CN``
  164. :return: JSON 数据包
  165. """
  166. return self._get(
  167. 'sns/userinfo',
  168. params = {
  169. 'access_token': access_token,
  170. 'openid': openid,
  171. 'lang': lang
  172. }
  173. )
  174. def check_access_token(self, openid, access_token):
  175. """检查 access_token 有效性
  176. :param openid: 微信 openid,默认获取当前授权用户信息
  177. :param access_token: 网页授权access token
  178. :return: 有效返回 True,否则 False
  179. """
  180. res = self._get(
  181. 'sns/auth',
  182. params = {
  183. 'access_token': access_token,
  184. 'openid': openid
  185. }
  186. )
  187. if res['errcode'] == 0:
  188. return True
  189. return False
  190. def jscode2session(self, js_code):
  191. """
  192. 小程序获取 session_key 和 openid
  193. """
  194. url = "https://api.weixin.qq.com/sns/jscode2session"
  195. args = dict()
  196. args.setdefault("appid", self.app_id)
  197. args.setdefault("secret", self.secret)
  198. args.setdefault("js_code", js_code)
  199. args.setdefault("grant_type", "authorization_code")
  200. return self._get('sns/jscode2session', params = args)