意见箱
恒创运营部门将仔细参阅您的意见和建议,必要时将通过预留邮箱与您保持联络。感谢您的支持!
意见/建议
提交建议

Keystone 用户认证(keystone国际学校)

来源:恒创科技 编辑:恒创科技编辑部
2024-02-02 12:42:59


Keystone用户认证



Keystone 用户认证(keystone国际学校)

1、keystone简介

keystone的认证,因为版本原因,同样也分为version2与version3,并且version2与3分属不同模块(下述所指模块均基于keystone),version2的代码集中于keystone.token模块,version3的代码集中于keystone.auth模块。

keystone各部件的代码组成主要包括routers、controllers、core、backends(包括sql、kvs等),其中routers主要负责url与action映射,core负责managerapi的操作,通俗来讲就是为controller提供api接口,为backends各种后端驱动封装接口,是一种适配器的功能,backends主要提供各种后端存储等操作。

认证的路径URL分别如下:

version2:/tokens对应操作:authenticate
/tokens/revoked对应操作:revocation_list
/tokens/{token_id} 对应validation_token,validate_token_head,delete_token
/tokens/{token_id}/endpoints 对应操作:token_controller
/certificates/ca对应操作ca_cert
/certificates/signing对应操作signing_cert
version3: /auth/tokens
对应操作:validate_token,checl_token,authenticate_for_token,revoke_token
/auth/tokens/OS-PKI/revoked
对应操作:revocation_list
/auth/catalog
对应操作:get_auth_catalog
/auth/domains
对应操作:get_auth_domains

上述URL定义以及对应操作的映射分别在keystone.auth.routers和Keystone.token.routers,所映射的操作分别在keystone.auth.controllers和keystone.token.controllers.

其中,auth部件,主要负责各种认证,token部件负责是token的认证,持久化等操作,所以才有了auth与token功能交叉的部分,也就是V2与V3在token认证上的差异.

2、keystone认证

keystone默认配置的认证方法主要有external、password、token、oauth1.

而auth.plugins中目前支持的增加了saml2,也就是liberty版本中的WEBSSO.

下面我们讨论的主要包括password、token、以及external,在对单个认证方式分析时,会同时分析v2与v3的差异。

V3中涉及的概念:project,domain,group,user,trust等,其中domain管理project,group,group管理user,因此,在检测group是否enabled的时候,需要首先检测其所归属集合是否enabled,这个编程涉及思路贯彻始终。

接下来,我们首先介绍version3的认证流程。

2.1keystonev3认证

处理函数:authenticate_for_token

defanthenticate_for_token(self,context,auth=None):

#context就是上下环境,auth是输入的字典

#auth的类型有如下几种,这些都是来至官网,对照代码理解无误,请放心!

第一种是username配合password,因为username不是主键,所以要搭domain,domain可以id或者name方式

{    "auth":
{ "identity": { "methods": [ "password" ], "password": { "user": { "domain": { "name": "example.com" }, "name": "Joe", "password": "secretsecret" } } } }}
第二种是username 配合password domain id
{ "auth": { "identity": { "methods": [ "password" ], "password": { "user": { "domain": { "id": "1789d1" }, "name": "Joe", "password": "secretsecret" } } } }}

第三种是userid配合password

{    "auth": {        "identity": {            "methods": [                "password"            ],            "password": {                "user": {                    "id": "0ca8f6",                    "password": "secretsecret"                }            }        }    }}

第四种是tokenid

{    "auth": {        "identity": {            "methods": [                "token"            ],            "token": {                "id": "e80b74"            }        }    }}

第五种是获取scopedtoken是scopedproject

{    "auth": {        "identity": {            "methods": [                "password"            ],            "password": {                "user": {                    "id": "0ca8f6",                    "password": "secretsecret"                }            }        },        "scope": {            "project": {                "id": "263fd9"            }        }    }}

第六种获取scopedtoken是scopeddomain

{    "auth": {        "identity": {            "methods": [                "password"            ],            "password": {                "user": {                    "id": "0ca8f6",                    "password": "secretsecret"                }            }        },        "scope": {            "domain": {                "id": "263fd9"            }        }    }}

第七种是获取scopedtoken是提供projectname,因为是name,所以需要提供domain,还是因为不是主键,不好辨别

{    "auth": {        "identity": {            "methods": [                "password"            ],            "password": {                "user": {                    "id": "0ca8f6",                    "password": "secretsecret"                }            }        },        "scope": {            "project": {                "domain": {                    "id": "1789d1"                },                "name": "project-x"            }        }    }}

第八种是scopedtoken,提供projectname以及domainname

{    "auth": {        "identity": {            "methods": [                "password"            ],            "password": {                "user": {                    "id": "0ca8f6",                    "password": "secretsecret"                }            }        },        "scope": {            "project": {                "domain": {                    "name": "example.com"                },                "name": "project-x"            }        }    }}

下面我们列出代码,一一道来。我的讲解是一代码加注释的形式进行的,谢谢!

因为认证方法都是通过plug-ins这种形式就是适配,那么是怎么适配的呢?

我们通过前期stevedore.DriverManager加载指定模块,这里的模块来至于/etc/keystone.conf

[auth]选项下methods=external,password,token,oauth1,加载好之后,你在输入auth字典时候,就要自己选哪种认证方式了,后面会讲到,首先先看password,token实现的认证方法,之后看controller函数如何去调用这两种方法.

password认证方法

def authenticate(self, context, auth_payload, auth_context):
user_info = auth_plugins.UserAuthInfo.create(auth_payload, METHOD_NAME)try:
self.identity_api.authenticate(
context,
user_id=user_info.user_id,
password=user_info.password)
except AssertionError:msg = _('Invalid username or password')
raise exception.Unauthorized(msg)auth_context['user_id'] = user_info.user_id

首先调用UserInfo类,这个类它是plug-in的core,其create(auth_payload,METHOD_NAME)主要实现的功能就是先初始化UserInfo实例,然后调用本类的_validate_and_normalize_auth_data(auth_payload)方法,那么这个方法是干嘛的呢?先查看user字典在不在,不在就报错,

然后获取user字典,user_id,user_name,上面那么多auth格式已经讲到了有user_name必须有domain,domain不管是domainname还是domainid,如果username不为空,那么就要检测domain

看它的enabled字段是否为True,不是就报错,如果user_id不为空,那么获取user_ref,再获取domain_ref,检测domain是否enabled,最终检测user是否enabled,然后将user_ref,user_id,domain_id赋值,这样这个方法就调用完毕,最后最后将’password’赋值给AuthInfo实例的METHOD_NAME属性,这样create静态方法调用完毕.

接下来就是调用identifu_api的authenticate方法,填入参数,关于identity_api的分析,放在后面,如果认证通过,那么最后将user_id填充进auth_context[‘user_id’]字段.

password讲解完毕,接下来就是token的方法.

token的认证方法

def authenticate(self, context, auth_payload, user_context):
if 'id' not in auth_payload:
raise exception.ValidationError(attribute='id',
target='token')
token_ref = self._get_token_ref(auth_payload)
if token_ref.is_federated_user and self.federation_api:
mapped.handle_scoped_token(
context, auth_payload, user_context, token_ref,
self.federation_api, self.identity_api,
self.token_provider_api)
else:
token_authenticate(context, auth_payload, user_context, token_ref)

token插件实现Token类,首先,校验auth_payload是否存在id,没有就报错,然后调用本类_get_token_ref(auth_payload)方法,这方法做了什么呢?它将auth_payload[‘id’]赋值给token_id,然后调用token_provider_api的validate_token方法,返回token_data,最后返回的是KeystoneToken实例,这个实例继承dict,那么这个KeystoneToken是用来干嘛的?KeystoneToken是V2token与V3token都需要使用的中间类,内存里就是存储并使用的它.

token_provider_api.validate_token(self,token_id,belong_to=None)方法完成的功能:首先,调用utils.generate_unique_id(token_id),对token_id进行处理,如果是asn1或者pkiztoken,那么就要hash,否则直接返回即可,然后调用_validate_token(unique_id),这个方法会先判断是否需要持久化,如果不要持久化就直接返回self.driver.validate_v3_token(token_id),也就是说直接调用后端的validate_v3_token(token_id)函数,这个函数的功能等下介绍.如果需要持久化的话,那么就调用self._persistence,get_token(token_id)并将返回结果传入self.driver.get_token_version(token_ref)判断token的版本,如果是v2,那么就调用self.driver.validate_v3_token(token_ref)否则调用self.driver.validate_v2_token(token_ref),最后返回token_ref.

因为没有设federated,所以,对后面的判断直接略过,最后调用token_authenticate,这个函数的作用是:先判断是否为oauth_scoped或者trusted_scoped,如果是就报错,不允许它们转变token,如果不是,就继续检测配置项是否支持scopedtoken转换,如果不允许,就报错,如果允许,就调用wsgi.validate_token_bind,检测tokenbind类型,默认为permissive,所以,可以放心通过,(这个地方一直不懂audit_ids是用来干嘛的,现在略有点明白了),然后从调用

token_ref.get(‘audit_ids’,[])[-1]获取最后一列,然后就是设置user_context的各种参数.如下所示:

user_context.setdefault('expires_at', token_ref.expires)
user_context['audit_id'] = token_audit_id
user_context.setdefault('user_id', token_ref.user_id)
user_context['extras'].update(token_ref.get('extras', {}))
user_context['method_names'].extend(token_ref.methods)

到目前为止,我们把password与token简单介绍完毕,我们下面看controller类中的方法如果去完成认证过程,代码如下:

include_catalog = 'nocatalog' not in context['query_string']
try:
auth_info = AuthInfo.create(context, auth=auth)# 调用AuthInfo的create方法,先初始化赋值
# self.context = context
# self.auth = auth
# self._scope_data = (None, None, None, None)
# 其中self._scope_data = (domain_id, project_id, trust_ref, unscoped)
# 是用于标示auth到底是scoped还是unscoped,又是哪种scoped
# 调用_validate_and_normalize_auth(scoped_only)方法
# 其用于检测auth是否合规,如果scoped为false,那么就要获取self.auth[‘identity’]
# 查看认证方式是否在默认支持的选项中
# 其后就是检测是否为scoped
# self._scoped_data赋予新检测值
auth_context = AuthContext(extras={},
method_names=[],
bind={})# AuthContext是继承dict的类,用于重构context,防止与认证属性冲突
# 它重构了__setitem__方法,对属性进行检测,如果是IDENTITY_ATTRIBUTES属性
# 且不与原来值相等,那就赋值不成功,且报错,但是‘expires_at’是可以以最早的时间作为替换.
self.authenticate(context, auth_info, auth_context)# 认证在代码后,单独开辟出来讲解
if auth_context.get('access_token_id'):
auth_info.set_scope(None, auth_context['project_id'], None)# 根据分析,auth_context是没有access_token_id的,所以直接进入下面的方法
self._check_and_set_default_scoping(auth_info, auth_context)# 这个方法的目的是检测是否为scoped还是已知为unscoped
# 如果都不是,我们就需要设置为scoped project,当然user必须要默认project
(domain_id, project_id, trust, unscoped) = auth_info.get_scope()
# 这里就是获取scoped tuple
method_names = auth_info.get_method_names()
method_names += auth_context.get('method_names', [])
method_names = list(set(method_names))
expires_at = auth_context.get('expires_at')metadata_ref = None
token_audit_id = auth_context.get('audit_id')
# 这里就是调用token_provider_api处理成为v3 token
# 在此过程中如果需要持久化,还要调用方法创建token并存储
(token_id, token_data) = self.token_provider_api.issue_v3_token(
auth_context['user_id'], method_names, expires_at, project_id,
domain_id, auth_context, trust, metadata_ref, include_catalog,
parent_audit_id=token_audit_id)if trust:
self.trust_api.consume_use(trust['id'])
# 这里就是返回头’X--Subject-Token’:token_id了
return render_token_data_response(token_id, token_data,
created=True)
except exception.TrustNotFound as e:
raise exception.Unauthorized(e)

其中,我们对上述调用的authenticate进行分析,代码如下:

def authenticate(self, context, auth_info, auth_context):
# 如果context环境有remote_user可能就使用的external认证方法
# 这个以后讨论,我们主要关心password与token认证
if context['environment'].get('REMOTE_USER'):
try:
external = get_auth_method('external')
external.authenticate(context, auth_info, auth_context)
except exception.AuthMethodNotSupported:LOG.debug("No 'external' plugin is registered.")
except exception.Unauthorized:
LOG.debug("Authorization failed for 'external' auth method.")
# 设一个 认证方法的字典
auth_response = {'methods': []}
for method_name in auth_info.get_method_names():# auth_info是AuthInfo实例,调用get_method_names
# 即获取self.auth[‘identity’][‘methods’]的值
method = get_auth_method(method_name)# 查看代码你会发现,这是获取对应方法加载的实例对象
# 加载方式是namespace,通过stevedore.DriverManager进行加载
# 有人会问了我没看到调用controllers.load_auth_methods啊?
# 因为上面方法的调用是在keystone.server.backends里面进行的调用,
# 我们在调试的时候,也需要手动的import这些配置类的,昨晚刚搞明白
# 总结一下:我们通过load_auth_methods()加载所有默认提供的方法
# 通过get_auth_method获取需要的认证方法
resp = method.authenticate(context,
auth_info.get_method_data(method_name),
auth_context)# 这里就是我们获取的方法了,然后调用plug-ins里面相对应的方法
# 认证函数调用后resp应该是None,如果有就说明是有问题的,需要进行其他认证
if resp:
auth_response['methods'].append(method_name)
auth_response[method_name] = resp
# 上面就是将认证的信息进行储存
if auth_response["methods"]:
# 这边就是一个检测过程
raise exception.AdditionalAuthRequired(auth_response)if 'user_id' not in auth_context:
msg = _('User not found')
raise exception.Unauthorized(msg)# 在method.authenticaticate检测通过后,会将user_id填入auth_context中
# 如果没有,不就说明是有问题吗?

最后,关于token就是这些,还要就是version2的token如何认证,数据格式,我们后面会再进行分析.以后关于keystone的文章主要有policy(大体)、tokenversion2&3(包括格式分析),分层多租户,policy(check细节),以及keystone的整体架构,包括其他部件的分析。还有待提高!


上一篇: [Elastic-Job2.1.5源码]-7-线程安全的单例模式设计全局作业注册表JobRegistry(elastic beanstalk) 下一篇: 手机怎么远程登录云服务器?