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
参考地址
- https://docs.djangoproject.com/es/1.9/ref/templates/builtins/#url
- http://www.cnblogs.com/no13bus/p/3767521.html
- 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()中:
此时用户与密码验证成功。
整个用户的登录过程大概清晰了。
- horizon登录过程分析(juno)
- openstack学习记录(一) horizon二次开发部署(juno)
- OpenStack Horizon源代码分析-用户登录全过程
- horizon--登录流程
- Horizon 源码阅读(三)—— Horizon 用户登录流程
- Horizon 源码阅读(三)—— Horizon 用户登录流程
- shiro登录过程分析
- shiro登录过程分析
- Horizon Dashboard源码分析
- 微博登录过程分析(一)基本过程
- 新浪网站登录过程分析
- 微博登录过程分析
- openstack-horizon/novaclient源码分析
- openstack juno热迁移配置过程
- OpenStack 手动安装(juno)
- 分析腾讯微博登录过程
- 腾讯微博登录过程协议分析
- 分析腾讯微博登录过程
- C# StringBuider,字符串拼接工具类
- 为什么函数可以返回unique_ptr
- linux操作命令大全
- 【ORACLE】物化视图相关元数据视图字段说明
- 并发编程Future and Callable
- horizon登录过程分析(juno)
- 移动端web开发click touch tap区别
- spring的配置文件信息
- 数据结构--内排序
- java学习笔记
- 网络爬虫基本原理
- python argparse模块
- javaEE基础篇,day0:知识点归纳
- linux克隆