django_jwt_session_auth.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. from django.contrib.auth.models import AnonymousUser
  6. import jwt
  7. from django.conf import settings as django_settings
  8. from django.core.cache import cache
  9. from django.dispatch.dispatcher import Signal
  10. from django.utils.module_loading import import_string
  11. from six import text_type
  12. import user_agents
  13. user_jwt_logged_in = Signal(['request', 'user'])
  14. logger = logging.getLogger(__name__)
  15. DEFAULT_JWT_AUTH_SETTING = {
  16. 'ALGORITHM': 'HS256',
  17. 'DECODER': 'django_jwt_session_auth.jwt_decoder',
  18. 'ENCODER': 'django_jwt_session_auth.jwt_encoder',
  19. 'PAYLOAD_TO_USER': None,
  20. 'USER_TO_PAYLOAD': None,
  21. 'USER_KEY': 'pk',
  22. 'USER_DOMAIN': None,
  23. 'TEST_USER_GETTER': None,
  24. 'SESSION': {
  25. 'EXPIRE': 3600 * 24 * 2,
  26. 'PREFIX': 'JWT_AUTH_CACHE:'
  27. }
  28. }
  29. def jwt_session_key(auth_domain): return '{auth_domain}_session_id'.format(auth_domain = auth_domain)
  30. # def jwt_session_key(auth_domain): return '{auth_domain}_SESSION_ID'.format(auth_domain = auth_domain.upper())
  31. class LazySettings(object):
  32. _settings = {}
  33. def __getitem__(self, item):
  34. if not self._settings:
  35. self._load_settings()
  36. if item in self._settings:
  37. return self._settings[item]
  38. else:
  39. return None
  40. def __contains__(self, key):
  41. if not self._settings:
  42. self._load_settings()
  43. if key in self._settings:
  44. return True
  45. else:
  46. return False
  47. def _load_settings(self):
  48. self._settings = {}
  49. # check settings
  50. user_defined_settings = getattr(django_settings, 'JWT_AUTH', {'default': DEFAULT_JWT_AUTH_SETTING})
  51. for auth_domain, user_defined_setting in user_defined_settings.iteritems():
  52. self._settings[auth_domain] = DEFAULT_JWT_AUTH_SETTING.copy()
  53. self._settings[auth_domain]['SESSION'].update(user_defined_setting.pop('SESSION', {}))
  54. self._settings[auth_domain].update(user_defined_setting)
  55. assert self._settings[auth_domain].get('PAYLOAD_TO_USER'), (
  56. u'JWT_AUTH (auth_domain=%s) settings\' `PAYLOAD_TO_USER` must be settled.\n'
  57. u'And it should return a user instance(whither it user-defined or not) '
  58. u'or None.' % (auth_domain,))
  59. assert self._settings[auth_domain].get('USER_TO_PAYLOAD'), (
  60. u'JWT_AUTH (auth_domain=%s) settings\' `USER_TO_PAYLOAD` must be settled.\n'
  61. u'And it should return a serializable dict payload' % (auth_domain,))
  62. jwt_settings = LazySettings()
  63. def get_authorization_header(request):
  64. """
  65. Return request's 'Authorization:' header, as a byte-string.
  66. Hide some test client ickyness where the header can be unicode.
  67. """
  68. # 先查看headers
  69. auth_domain = request.META.get(django_settings.JWT_AUTH_DOMAIN_DJANGO, None)
  70. if auth_domain:
  71. token = request.META.get(django_settings.JWT_TOKEN_DJANGO, '')
  72. return auth_domain, token
  73. if str(request.path) in django_settings.JWT_SERVICE_DOMAIN_MAP:
  74. auth_domain_dict = django_settings.JWT_SERVICE_DOMAIN_MAP[str(request.path)]
  75. auth_domain = auth_domain_dict['authDomain']
  76. if not auth_domain_dict['pre']:
  77. return auth_domain, None
  78. else:
  79. auth_domain = request.COOKIES.get(django_settings.JWT_AUTH_DOMAIN_COOKIE_NAME, None)
  80. if not auth_domain:
  81. return None, None
  82. token = request.COOKIES.get(jwt_session_key(auth_domain)) or b''
  83. if isinstance(token, text_type):
  84. token = token.encode('utf-8')
  85. return auth_domain, token
  86. def jwt_decoder(token, secret, algorithm):
  87. try:
  88. return jwt.decode(token, secret, algorithms = algorithm)
  89. except:
  90. return None
  91. def jwt_encoder(payload, secret, algorithm):
  92. return jwt.encode(payload, secret, algorithm = algorithm)
  93. class JwtSession(dict):
  94. """ jwt authenticated session
  95. """
  96. __slot__ = ('__storage__', '__key__', '__expire__')
  97. @classmethod
  98. def from_key(cls, key, expire):
  99. """ get jwt session corresponding to the key.
  100. :param key: session cache key
  101. :param expire: default expire time(Seconds)
  102. :return: JwtSession instance
  103. """
  104. data = cache.get(key, {})
  105. default_expire = expire
  106. return cls(key, default_expire, storage = data)
  107. def save(self):
  108. """ save session to cache
  109. """
  110. if self.__storage__:
  111. cache.set(self.__key__, self.__storage__, self.__expire__)
  112. def __init__(self, key, expire, storage = None):
  113. self.__dict__['__key__'] = key
  114. self.__dict__['__expire__'] = expire
  115. self.__dict__['__storage__'] = storage or {}
  116. def __getitem__(self, item):
  117. return self.__storage__[item]
  118. def __setitem__(self, key, value):
  119. self.__storage__[key] = value
  120. def __str__(self):
  121. return repr(self.__storage__)
  122. def __repr__(self):
  123. return '<JwtSession %s>' % self.__dict__['__key__']
  124. def get(self, k, d = None):
  125. return self.__storage__.get(k, d)
  126. class JwtAuthMiddleware(object):
  127. """ jwt auth middleware
  128. Check the `AUTHORIZATION` header in request, and add the correspond `user` and `jwt_session` to request.
  129. if the authentication failed, the `request.user` will be None, and `jwt_session` will be a empty JwtSession.
  130. """
  131. _user_key_prefix = ''
  132. _test_user = None
  133. def __init__(self, get_response = None):
  134. self.get_response = get_response
  135. def process_request(self, request):
  136. try:
  137. auth_domain, token = get_authorization_header(request)
  138. if django_settings.DEBUG_JWT:
  139. logger.debug('jwt session auth. url = %s; domain = %s; token = %s' % (request.path, auth_domain, token))
  140. if not auth_domain or auth_domain not in jwt_settings:
  141. return
  142. user = None
  143. if token:
  144. decoder = self._get_decoder(auth_domain)
  145. payload = decoder(token, jwt_settings[auth_domain]['SECRET'], (jwt_settings[auth_domain]['ALGORITHM'],))
  146. payload_to_user_func = self._get_payload_to_user_handler(auth_domain)
  147. user = payload_to_user_func(payload)
  148. if not user:
  149. user = AnonymousUser()
  150. if django_settings.DEBUG_JWT:
  151. logger.debug('login user = %s' % repr(user))
  152. jwt_session = self.get_jwt_session(auth_domain, user)
  153. setattr(request, 'user', user)
  154. setattr(request, 'jwt_session', jwt_session)
  155. request.jwt_session.save()
  156. except Exception as e:
  157. logger.exception(e)
  158. def process_response(self, request, response):
  159. #: 将授权域下达给访问了入口页的请求
  160. if str(request.path) in django_settings.JWT_SERVICE_DOMAIN_MAP:
  161. auth_domain_dict = django_settings.JWT_SERVICE_DOMAIN_MAP[str(request.path)]
  162. auth_domain = auth_domain_dict['authDomain']
  163. if auth_domain_dict['post']:
  164. cookie = {
  165. 'key': django_settings.JWT_AUTH_DOMAIN_COOKIE_NAME,
  166. 'value': auth_domain,
  167. 'max_age': 30 * 24 * 3600,
  168. 'secure': False,
  169. 'httponly': False
  170. }
  171. if 'localhost' not in django_settings.COOKIE_DOMAIN:
  172. cookie['domain'] = django_settings.COOKIE_DOMAIN
  173. response.set_cookie(**cookie)
  174. return response
  175. @classmethod
  176. def user_key_prefix(cls, auth_domain):
  177. if not cls._user_key_prefix:
  178. cls._user_key_prefix = jwt_settings[auth_domain]['SESSION']['PREFIX']
  179. return cls._user_key_prefix
  180. @classmethod
  181. def test_user(cls, auth_domain):
  182. """ load test user if TEST_USER_GETTER is exists
  183. """
  184. test_user_getter = jwt_settings[auth_domain]['TEST_USER_GETTER']
  185. if not cls._test_user and test_user_getter:
  186. test_user_getter = import_string(test_user_getter)
  187. cls._test_user = test_user_getter()
  188. return cls._test_user
  189. @classmethod
  190. def get_jwt_session(cls, auth_domain, user, expire = None):
  191. """ get specific user jwt_session
  192. :param auth_domain:
  193. :param user: user
  194. :param expire: expire time in seconds.
  195. :return: JwtSession instance
  196. """
  197. user_key = jwt_settings[auth_domain]['USER_KEY']
  198. if not user or isinstance(user, AnonymousUser):
  199. return cls._get_empty_jwt_session(auth_domain)
  200. session_key = cls.user_key_prefix(auth_domain) + str(getattr(user, user_key))
  201. session = JwtSession.from_key(session_key, expire = expire or jwt_settings[auth_domain]['SESSION']['EXPIRE'])
  202. return session
  203. @classmethod
  204. def jwt_login(cls, auth_domain, user, request, expire = None):
  205. """ jwt login user function
  206. :param auth_domain: auth domain
  207. :param expire: expire time
  208. :param user_object user: user instance
  209. :param request request: request instance
  210. :return str: token
  211. """
  212. user_to_payload_handler = import_string(jwt_settings[auth_domain]['USER_TO_PAYLOAD'])
  213. payload = user_to_payload_handler(user)
  214. encoder = cls._get_encoder(auth_domain)
  215. token = encoder(payload, jwt_settings[auth_domain]['SECRET'], jwt_settings[auth_domain]['ALGORITHM'])
  216. user_agent_string = request.META.get('HTTP_USER_AGENT', '')
  217. phoneOS = user_agents.parse(user_agent_string).os.family
  218. user.update(last_login=datetime.datetime.now(), phoneOS=phoneOS, lastLoginUserAgent=user_agent_string)
  219. request.user = user
  220. request.jwt_session = cls.get_jwt_session(auth_domain, user, expire = expire)
  221. user_jwt_logged_in.send(user.__class__, request = request, user = user)
  222. return token
  223. @classmethod
  224. def _get_empty_jwt_session(cls, auth_domain):
  225. """ get empty jwt session
  226. """
  227. return JwtSession(cls.user_key_prefix(auth_domain) + 'empty', None, {})
  228. @classmethod
  229. def _get_decoder(cls, auth_domain):
  230. """ get jwt token decoder
  231. """
  232. decoder_entry = jwt_settings[auth_domain]['DECODER']
  233. if decoder_entry == 'django_jwt_session_auth.jwt_decoder':
  234. return jwt_decoder
  235. elif isinstance(decoder_entry, basestring):
  236. return import_string(decoder_entry)
  237. else:
  238. return decoder_entry
  239. @classmethod
  240. def _get_encoder(cls, auth_domain):
  241. """ get jwt token decoder
  242. """
  243. encoder_entry = jwt_settings[auth_domain]['ENCODER']
  244. if encoder_entry == 'django_jwt_session_auth.jwt_encoder':
  245. return jwt_encoder
  246. elif isinstance(encoder_entry, basestring):
  247. return import_string(encoder_entry)
  248. else:
  249. return encoder_entry
  250. def _get_payload_to_user_handler(self, auth_domain):
  251. return import_string(jwt_settings[auth_domain]['PAYLOAD_TO_USER'])
  252. jwt_login = JwtAuthMiddleware.jwt_login