在Django项目中使用Joomla帐户

假设您的用户使用的网站是用Joomla编写的,但是要为您的受众创建新产品,您选择了Python / Django捆绑包。


因此,您需要使用Django中Joomla数据库中的用户帐户。


但是,问题在于Joomla和Django使用不同的密码哈希算法,因此仅复制帐户会失败。


阅读Django文档,堆栈溢出并花一些时间后,我得到了下面描述的解决方案,该解决方案最大限度地使用了Django的推荐开发实践。


警告事项


此体系结构解决方案可能不适合您,请参阅注释中讨论


要了解以下示例中发生的情况,您必须对Django体系结构有所了解。


我还假设您知道如何部署Django项目,因此我不描述此过程。


该代码是从一个正在运行的项目中复制的,但是只需很少的更改就可以轻松地适应您的项目。


可能在Django的下一个主要版本中,此代码可能会中断,但是解决方案的原理将保持不变。


在本指南中,我不会描述授权系统的前端,因为:


  • 您拥有的前端将取决于项目的需求(例如,它甚至可能是Json API端点)
  • 官方Django教程和各种入门文章中已经描述了此信息

演算法


  • 将Joomla数据库(DB)连接到Django项目
  • 从Joomla数据库创建一个代表用户的JoomlaUser模型
  • 编写一个check_joomla_password()函数,以验证输入的密码与用户的原始密码匹配。
  • 向项目添加新的授权后端“ Joomla Auth Backend”,当在Django中授权客户端时,它将从Joomla数据库获取用户帐户

1.连接到Joomla数据库:


  • 阅读Django如何与多个数据库一起使用
  • 要将Joomla数据库连接到我们的Django项目,请将以下代码添加到项目设置文件/project_name/settings.py


     DATABASES = { #    'default': { ... }, 'joomla_db': { 'ENGINE': 'django.db.backends.mysql', 'OPTIONS': {}, 'NAME': 'joomla_database_name', # Don't store passwords in the code, instead use env vars: 'USER': os.environ['joomla_db_user'], 'PASSWORD': os.environ['joomla_db_pass'], 'HOST': 'joomla_db_host, can be localhost or remote IP', 'PORT': '3306', } } 


如有必要,可以在与项目设置相同的文件中,启用数据库查询的日志记录:


 # add logging to see DB requests: LOGGING = { 'version': 1, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'level': 'DEBUG', 'handlers': ['console'], }, }, } 

2.创建一个JoomlaUser模型


  • 阅读Django模型如何使用现有数据库
  • 考虑在何处放置新的JoomlaUser。
    在我的项目中,我创建了一个名为“用户”的应用程序( manage.py startapp users )。 它将包含授权后端和Joomla用户模型。
  • 使用inspectdb自动生成模型:
    python manage.py inspectdb live_users --database="joomla_db"
    joomla_db-您在settings.py/DATABASES指定的数据库的名称;
    live_users-带有帐户的表的名称。
  • 将模型添加到users/models.py


     class JoomlaUser(models.Model): """ Represents our customer from the legacy Joomla database. """ username = models.CharField(max_length=150, primary_key=True) email = models.CharField(max_length=100) password = models.CharField(max_length=100) # you can copy more fields from `inspectdb` output, # but it's enough for the example class Meta: # joomla db user table. WARNING, your case can differs. db_table = 'live_users' # readonly managed = False # tip for the database router app_label = "joomla_users" 


接下来,我们需要确保模型将访问正确的数据库。 为此,请在项目中添加一个路由器,以查询不同的数据库 ,这会将请求从JoomlaUser模型重定向到其本机数据库。


  1. 在项目的主文件夹中创建文件“ db_routers.py”(在“ settings.py”所在的位置):


     # project_name/db_routers.py class DbRouter: """this router makes sure that django uses legacy 'Joomla' database for models, that are stored there (JoomlaUser)""" def db_for_read(self, model, **kwargs): if model._meta.app_label == 'joomla_user': return 'joomla_db' return None def db_for_write(self, model, **kwargs): if model._meta.app_label == 'joomla_user': return 'joomla_db' return None 

  2. settings.py注册一个新路由器:


     # ensure that Joomla users are populated from the right database: DATABASE_ROUTERS = ['project_name.db_routers.DbRouter'] 


现在您可以从旧数据库中获得一个帐户。
启动Django终端并尝试拉取现有用户: python manage.py shell


 >>> from users.models import JoomlaUser >>> print(JoomlaUser.objects.get(username='someuser')) JoomlaUser object (someusername) >>> 

如果一切正常(您看到用户),请转到下一步。 否则,请查看错误输出并更正设置。


3.验证Joomla帐户密码


Joomla不存储用户密码,但存储其哈希,例如
$2y$10$aoZ4/bA7pe.QvjTU0R5.IeFGYrGag/THGvgKpoTk6bTz6XNkY0F2e


从Joomla v3.2开始,使用BLOWFISH算法对用户密码进行加密。


所以我用以下算法下载了python代码:


 pip install bcrypt echo bcrypt >> requirements.txt 

并创建了一个功能来检查users/backend.py中的密码:


 def check_joomla_password(password, hashed): """ Check if password matches the hashed password, using same hashing method (Blowfish) as Joomla >= 3.2 If you get wrong results with this function, check that the Hash starts from prefix "$2y", otherwise it is probably not a blowfish hash :return: True/False """ import bcrypt if password is None: return False # bcrypt requires byte strings password = password.encode('utf-8') hashed = hashed.encode('utf-8') return hashed == bcrypt.hashpw(password, hashed) 

注意! 低于3.2的Joomla版本使用不同的哈希方法(md5 + salt),因此此功能将不起作用。 在这种情况下,请阅读
关于Stackoverflow的讨论,并创建一个看起来像这样的哈希检查函数:


 # WARNING - THIS FUNCTION WAS NOT TESTED WITH REAL JOOMLA USERS # and definitely has some errors def check_old_joomla_password(password, hashed): from hashlib import md5 password = password.encode('utf-8') hashed = hashed.encode('utf-8') if password is None: return False # check carefully this part: hash, salt = hashed.split(':') return hash == md5(password+salt).hexdigest() 

不幸的是,我手头没有旧版Joomla的用户群,因此无法为您测试此功能。


4.后端用户授权Joomla


现在,您可以创建一个Django后端,以授权来自Joomla项目的用户。


  1. 阅读如何修改Django授权系统


  2. project/settings.py注册一个新的后端(尚不存在):


     AUTHENTICATION_BACKENDS = [ # Check if user already in the local DB # by using default django users backend 'django.contrib.auth.backends.ModelBackend', # If user was not found among django users, # use Joomla backend, which: # - search for user in Joomla DB # - check joomla user password # - copy joomla user into Django user. 'users.backend.JoomlaBackend', ] 

  3. users/backend.py创建一个Joomla用户授权后端



 from django.contrib.auth.models import User from .models import JoomlaUser def check_joomla_password(password, hashed): # this is a fuction, that we wrote before ... class JoomlaBackend: """ authorize users against Joomla user records """ def authenticate(self, request, username=None, password=None): """ IF joomla user exists AND password is correct: create django user return user object ELSE: return None """ try: joomla_user = JoomlaUser.objects.get(username=username) except JoomlaUser.DoesNotExist: return None if check_joomla_password(password, joomla_user.password): # Password is correct, let's create and return Django user, # identical to Joomla user: # but before let's ensure there is no same username # in DB. That could happen, when user changed password # in Joomla, but Django doesn't know that User.objects.filter(username=username).delete() return User.objects.create_user( username=username, email=joomla_user.email, password=password, # any additional fields from the Joomla user: ... ) # this method is required to match Django Auth Backend interface def get_user(self, user_id): try: return User.objects.get(pk=user_id) except User.DoesNotExist: return None 

总结


恭喜-您现有的Joomla网站的用户现在可以在新网站/应用程序上使用其凭据。


作为通过新界面进行的活动用户的授权,会将活动用户一张一张地复制到新数据库中。


或者,您可能不想将用户实体从旧系统复制到新系统。


在这种情况下, 这里是指向文章的链接,该文章描述了如何在Django (上述的JoomlaUser模型) 中用您自己的默认用户模型替换默认用户模型


是否转移用户的最终决定是基于新项目和旧项目之间的关系而做出的。 例如,新用户的注册将在哪里进行,哪个站点/应用程序将是主要站点,等等。


测试和文件


现在,请添加有关新代码的适当测试和文档。 该解决方案的逻辑与Django体系结构紧密相关,并且不是很明显,因此,如果您现在不执行测试/文档,将来对该项目的支持将变得更加复杂。

Source: https://habr.com/ru/post/zh-CN442592/


All Articles