horizon登录过程分析(juno)

来源:互联网 发布:general yoast seo 编辑:程序博客网 时间:2024/06/06 03:13

下面主要记录下horizon登录和django框架的认证分析

运行服务器后,进入 http://0.0.0.0:8080后,首先进入setting.py中
这里写图片描述
根据url映射进入openstack_dashboard/urls.py
这里写图片描述
根据url匹配知 http:0.0.0.0:8080 交由openstack_dashboard/views.py.splash函数处理。查看views::splash函数

/openstack_dashboard/views.py
这里写图片描述

1 表单处理

在splash函数中,首先根据djanmgo session会话判断用户是否登录,如果登录,则转至用户主界面,否则就进入登录界面。此处分析登录流程。首先根据forms生成表单项,并将表单项传入splash.html模板中
进入 horizon/templates/splash.html
这里写图片描述
splash.html主要加载auth/_login.html,查看_login.html文件
进入horizon/templates/auth/_login.html
这里写图片描述
显示登录主界面后,对于用户输入用户名与密码,提交表单时,表单的action属性对应为url ‘login’,此时,django搜寻urls中 name=’login’的url,(ctrl+单击url,跳转至openstack_auth.urls)即对应为 openstack_auth.urls,也就是action为/auth/login.
_login.html表单继承自表单_modal_form.html模板,并重写了form_action属性
_modal_form.html:
这里写图片描述
这部分定义了action为form_action,methosd=”POST”,并定义了modal-body块用来填写用户名与密码。

_login.html文件重定义了action属性,modal-body内容。

django中{%url ‘login’%} 指代urls中name为’login’对应的地址并替代;解析出来的地址为0.0.0.0:8080/auth/login
参考地址

  1. https://docs.djangoproject.com/es/1.9/ref/templates/builtins/#url
  2. http://www.cnblogs.com/no13bus/p/3767521.html
  3. http://www.liujiangblog.com/course/django/89

2 表单url分级处理过程

当表单接受数据,并根据action=’auth/login’及django分级匹配:
这里写图片描述

django中include将截断匹配的url并将剩余的字符串发至包含的urls中再做处理。

进入/openstack_auth/urls.py
这里写图片描述
根据上图/login的地址对应openstack_auth.views:login函数处理。

3 表单内容处理

在本机安装部署horizon时,按照上一篇horizon二次开发部署,在 requirements.txt中会默认安装openstack_auth认证模块

进入/openstack_auth.views.py文件
整个login函数如下:

.........from django.contrib.auth import views as django_auth_views.........# This is historic and is added back in to not break older versions of# Horizon, fix to Horizon to remove this requirement was committed in# Junofrom openstack_auth.forms import Login  # noqafrom openstack_auth import user as auth_userfrom openstack_auth import utils@sensitive_post_parameters()@csrf_protect@never_cachedef login(request, template_name=None, extra_context=None, **kwargs):    """Logs a user in using the :class:`~openstack_auth.forms.Login` form."""    if not request.is_ajax():        # If the user is already authenticated, redirect them to the        # dashboard straight away, unless the 'next' parameter is set as it        # usually indicates requesting access to a page that requires different        # permissions.        if (request.user.is_authenticated() and                auth.REDIRECT_FIELD_NAME not in request.GET and                auth.REDIRECT_FIELD_NAME not in request.POST):            return shortcuts.redirect(settings.LOGIN_REDIRECT_URL)    # Get our initial region for the form.    initial = {}    current_region = request.session.get('region_endpoint', None)    requested_region = request.GET.get('region', None)    regions = dict(getattr(settings, "AVAILABLE_REGIONS", []))    if requested_region in regions and requested_region != current_region:        initial.update({'region': requested_region})    if request.method == "POST":        # NOTE(saschpe): Since https://code.djangoproject.com/ticket/15198,        # the 'request' object is passed directly to AuthenticationForm in        # django.contrib.auth.views#login:        if django.VERSION >= (1, 6):            form = functional.curry(forms.Login)        else:            form = functional.curry(forms.Login, request)    else:        form = functional.curry(forms.Login, initial=initial)    if extra_context is None:        extra_context = {'redirect_field_name': auth.REDIRECT_FIELD_NAME}    if not template_name:        if request.is_ajax():            template_name = 'auth/_login.html'            extra_context['hide'] = True        else:            template_name = 'auth/login.html'    res = django_auth_views.login(request,                                  template_name=template_name,                                  authentication_form=form,                                  extra_context=extra_context,                                  **kwargs)    # Save the region in the cookie, this is used as the default    # selected region next time the Login form loads.    if request.method == "POST":        utils.set_response_cookie(res, 'login_region',                                  request.POST.get('region', ''))    # Set the session data here because django's session key rotation    # will erase it if we set it earlier.    if request.user.is_authenticated():        auth_user.set_session_from_user(request, request.user)        regions = dict(forms.Login.get_region_choices())        region = request.user.endpoint        region_name = regions.get(region)        request.session['region_endpoint'] = region        request.session['region_name'] = region_name        request.session['last_activity'] = int(time.time())    return res

在函数中主要认证模块如下:
这里写图片描述
将表单内容作为django_auth_views.login的参数传递去:
django_auth_views对应django/contrib/auth中views.py文件,进入views:login函数
login函数如下:
这里写图片描述
在函数里面,获取request中表单的内容,然后用表单is_valid()方法认证数据,其中会调用表单clean方法

clean方法会在forms.is_valid()方法中调用,顺序是先验证后调用
https://docs.djangoproject.com/en/dev/ref/forms/validation/
https://www.douban.com/note/296298497/
clean()方法对表单中的数据进行验证

3.1 forms内容

此处的authentication_form接受自openstack_auth.views中传递的forms

.....from openstack_auth import formsfrom openstack_auth.forms import Login  # noqa.......

这里写图片描述
此时进入/openstack_auth.forms.py文件
整个代码如下:

from django.conf import settingsfrom django.contrib.auth import authenticate  # noqafrom django.contrib.auth import forms as django_auth_formsfrom django import forms........class Login(django_auth_forms.AuthenticationForm):    region = forms.ChoiceField(label=_("Region"), required=False)    username = forms.CharField(        label=_("User Name"),        widget=forms.TextInput(attrs={"autofocus": "autofocus"}))    password = forms.CharField(label=_("Password"),                               widget=forms.PasswordInput(render_value=False))..........    @sensitive_variables()    def clean(self):        default_domain = getattr(settings,                                 'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN',                                 'Default')        username = self.cleaned_data.get('username')        password = self.cleaned_data.get('password')        region = self.cleaned_data.get('region')        domain = self.cleaned_data.get('domain', default_domain)        if not (username and password):            # Don't authenticate, just let the other validators handle it.            return self.cleaned_data        try:            self.user_cache = authenticate(request=self.request,                                           username=username,                                           password=password,                                           user_domain_name=domain,                                           auth_url=region)            msg = 'Login successful for user "%(username)s".' % \                {'username': username}            LOG.info(msg)        except exceptions.KeystoneAuthException as exc:            msg = 'Login failed for user "%(username)s".' % \                {'username': username}            LOG.warning(msg)            raise forms.ValidationError(exc)        if hasattr(self, 'check_for_test_cookie'):  # Dropped in django 1.7            self.check_for_test_cookie()        return self.cleaned_data

表单中首先定义了用户与密码、region表项
表单中重写了clean()方法,先取出每一个表项(用户、密码等),并用authenticate()方法进行验证。
进入authenticate()方法: django.contrib.auth_ init_.py

def get_backends():    backends = []    for backend_path in settings.AUTHENTICATION_BACKENDS:        backends.append(load_backend(backend_path))    if not backends:        raise ImproperlyConfigured('No authentication backends have been defined. Does AUTHENTICATION_BACKENDS contain anything?')    return backendsdef authenticate(**credentials):    """    If the given credentials are valid, return a User object.    """    for backend in get_backends():        try:            user = backend.authenticate(**credentials)        except TypeError:            # This backend doesn't accept these credentials as arguments. Try the next one.            continue        except PermissionDenied:            # This backend says to stop in our tracks - this user should not be allowed in at all.            return None        if user is None:            continue        # Annotate the user object with the path of the backend.        user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)        return user    # The credentials supplied are invalid to all backends, fire signal    user_login_failed.send(sender=__name__,            credentials=_clean_credentials(credentials))

authenticate()方法从配置的后端认证,当找到认证成功的后端,该函数结束并返回认证结果。
如下图,对每一个后端的authenticate方法进行认证
这里写图片描述
认证的后端在配置文件settings.py:
这里写图片描述
进入openstack_auth/backend.py:KeystoneBackend:

class KeystoneBackend(object):    .........    def authenticate(self, request=None, username=None, password=None,                     user_domain_name=None, auth_url=None):        """Authenticates a user via the Keystone Identity API."""        LOG.debug('Beginning user authentication for user "%s".' % username)        insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)        ca_cert = getattr(settings, "OPENSTACK_SSL_CACERT", None)        endpoint_type = getattr(            settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL')        if auth_url is None:            auth_url = settings.OPENSTACK_KEYSTONE_URL        # keystone client v3 does not support logging in on the v2 url any more        if utils.get_keystone_version() >= 3:            if utils.has_in_url_path(auth_url, "/v2.0"):                LOG.warning("The settings.py file points to a v2.0 keystone "                            "endpoint, but v3 is specified as the API version "                            "to use. Using v3 endpoint for authentication.")                auth_url = utils.url_path_replace(auth_url, "/v2.0", "/v3", 1)        keystone_client = utils.get_keystone_client()        try:            client = keystone_client.Client(                user_domain_name=user_domain_name,                username=username,                password=password,                auth_url=auth_url,                insecure=insecure,                cacert=ca_cert,                debug=settings.DEBUG)            unscoped_auth_ref = client.auth_ref            unscoped_token = auth_user.Token(auth_ref=unscoped_auth_ref)        except (keystone_exceptions.Unauthorized,                keystone_exceptions.Forbidden,                keystone_exceptions.NotFound) as exc:            msg = _('Invalid user name or password.')            LOG.debug(str(exc))            raise exceptions.KeystoneAuthException(msg)        except (keystone_exceptions.ClientException,                keystone_exceptions.AuthorizationFailure) as exc:            msg = _("An error occurred authenticating. "                    "Please try again later.")            LOG.debug(str(exc))            raise exceptions.KeystoneAuthException(msg)        # Check expiry for our unscoped auth ref.        self.check_auth_expiry(unscoped_auth_ref)        # Check if token is automatically scoped to default_project        # grab the project from this token, to use as a default        # if no recent_project is found in the cookie        token_proj_id = None        if unscoped_auth_ref.project_scoped:            token_proj_id = unscoped_auth_ref.get('project',                                                  {}).get('id')        # We list all the user's projects        try:            if utils.get_keystone_version() < 3:                projects = client.tenants.list()            else:                client.management_url = auth_url                projects = client.projects.list(                    user=unscoped_auth_ref.user_id)        except (keystone_exceptions.ClientException,                keystone_exceptions.AuthorizationFailure) as exc:            msg = _('Unable to retrieve authorized projects.')            raise exceptions.KeystoneAuthException(msg)        # Abort if there are no projects for this user        if not projects:            msg = _('You are not authorized for any projects.')            raise exceptions.KeystoneAuthException(msg)        # the recent project id a user might have set in a cookie        recent_project = None        if request:            recent_project = request.COOKIES.get('recent_project',                                                 token_proj_id)        # if a most recent project was found, try using it first        for pos, project in enumerate(projects):            if project.id == recent_project:                # move recent project to the beginning                projects.pop(pos)                projects.insert(0, project)                break        for project in projects:            try:                client = keystone_client.Client(                    tenant_id=project.id,                    token=unscoped_auth_ref.auth_token,                    auth_url=auth_url,                    insecure=insecure,                    cacert=ca_cert,                    debug=settings.DEBUG)                auth_ref = client.auth_ref                break            except (keystone_exceptions.ClientException,                    keystone_exceptions.AuthorizationFailure):                auth_ref = None        if auth_ref is None:            msg = _("Unable to authenticate to any available projects.")            raise exceptions.KeystoneAuthException(msg)        # Check expiry for our new scoped token.        self.check_auth_expiry(auth_ref)        # If we made it here we succeeded. Create our User!        user = auth_user.create_user_from_token(            request,            auth_user.Token(auth_ref),            client.service_catalog.url_for(endpoint_type=endpoint_type))        if request is not None:            request.session['unscoped_token'] = unscoped_token.id            request.user = user            # Support client caching to save on auth calls.            setattr(request, KEYSTONE_CLIENT_ATTR, client)        LOG.debug('Authentication completed for user "%s".' % username)        return user

如上,当后端验证成功,回到表单forms::clean()中:
这里写图片描述
此时用户与密码验证成功。

这里写图片描述
整个用户的登录过程大概清晰了。