认证用户名密码,并返回token(id)
验证机制的后端是在identity/backends/ldap/core.py中Identity类的一个方法authenticate(user_id,password)
def authenticate(self, user_id, password):
try:
user_ref = self._get_user(user_id)
except exception.UserNotFound:
raise AssertionError(_('Invalid user / password'))
if not user_id or not password:
raise AssertionError(_('Invalid user / password'))
conn = None
try:
conn = self.user.get_connection(user_ref['dn'],
password, end_user_auth=True)
if not conn:
raise AssertionError(_('Invalid user / password'))
except Exception:
raise AssertionError(_('Invalid user / password'))
finally:
if conn:
conn.unbind_s()
return self.user.filter_attributes(user_ref)
它先尝试获取user_id所示user,如果不成功或user_id和password存在空值,则报错’Invalid user / password’;如果成功,则用password尝试连接LDAP服务器,这里调用的是ldap/common.py中BaseLdap类的方法get_connection(…)
def get_connection(self, user=None, password=None, end_user_auth=False):
use_pool = self.use_pool
pool_size = self.pool_size
pool_conn_lifetime = self.pool_conn_lifetime
if end_user_auth:
if not self.use_auth_pool:
use_pool = False
else:
pool_size = self.auth_pool_size
pool_conn_lifetime = self.auth_pool_conn_lifetime
conn = _get_connection(self.LDAP_URL, use_pool,
use_auth_pool=end_user_auth)
仔细读这段代码,可以看到它里面又调用了_get_connection(…)方法来获取Handler,如果给出的conn_url的前缀对应的handler已经在注册表中了,则直接返回该handler,如果use_pool选项为true,则返回PooledLDAPHandler,否则,返回PythonLDAPHandler。
def _get_connection(conn_url, use_pool=False, use_auth_pool=False):
for prefix, handler in _HANDLERS.items():
if conn_url.startswith(prefix):
return handler()
if use_pool:
return PooledLDAPHandler(use_auth_pool=use_auth_pool)
else:
return PythonLDAPHandler()
无论那种类型的handler,在下一句中都转换为KeystoneLDAPHandler
conn = KeystoneLDAPHandler(conn=conn)
然后,调用conn.connect(…)尝试连接LDAP服务器
try:
conn.connect(self.LDAP_URL,
page_size=self.page_size,
alias_dereferencing=self.alias_dereferencing,
use_tls=self.use_tls,
tls_cacertfile=self.tls_cacertfile,
tls_cacertdir=self.tls_cacertdir,
tls_req_cert=self.tls_req_cert,
chase_referrals=self.chase_referrals,
debug_level=self.debug_level,
conn_timeout=self.conn_timeout,
use_pool=use_pool,
pool_size=pool_size,
pool_retry_max=self.pool_retry_max,
pool_retry_delay=self.pool_retry_delay,
pool_conn_timeout=self.pool_conn_timeout,
pool_conn_lifetime=pool_conn_lifetime)
然后判断,如果user是空的,则使用conf中的user,如果password是空的,则使用conf中的password,然后把user和password打包放入conn,返回conn。最后处理一些异常。
if user is None:
user = self.LDAP_USER
if password is None:
password = self.LDAP_PASSWORD
if user and password:
conn.simple_bind_s(user, password)
else:
conn.simple_bind_s()
return conn
except ldap.INVALID_CREDENTIALS:
raise exception.LDAPInvalidCredentialsError()
except ldap.SERVER_DOWN:
raise exception.LDAPServerConnectionError(
url=self.LDAP_URL)
回到Ldap/core.py,正确拿到conn表示password正确,则将user和password解包,返回user除了password、tenant、groups的其它属性(在identity/backends/base.py中定义)。
验证token,并响应token携带的请求
验证token的代码在auth/plugins/token.py中
def authenticate(self, auth_payload):
if 'id' not in auth_payload:
raise exception.ValidationError(attribute='id',
target='token')
token = self._get_token_ref(auth_payload)
if token.is_federated and PROVIDERS.federation_api:
response_data = mapped.handle_scoped_token(
token, PROVIDERS.federation_api,
PROVIDERS.identity_api
)
else:
response_data = token_authenticate(token)
response_data.setdefault('method_names', []).extend(token.methods)
return base.AuthHandlerResponse(status=True, response_body=None,
response_data=response_data)
其中,核心验证过程是调用token_authenticate(token)完成的。
def token_authenticate(token):
response_data = {}
try:
json_body = flask.request.get_json(silent=True, force=True) or {}
project_scoped = 'project' in json_body['auth'].get(
'scope', {}
)
domain_scoped = 'domain' in json_body['auth'].get(
'scope', {}
)
if token.oauth_scoped:
raise exception.ForbiddenAction(
action=_(
'Using OAuth-scoped token to create another token. '
'Create a new OAuth-scoped token instead'))
elif token.trust_scoped:
raise exception.ForbiddenAction(
action=_(
'Using trust-scoped token to create another token. '
'Create a new trust-scoped token instead'))
elif token.system_scoped and (project_scoped or domain_scoped):
raise exception.ForbiddenAction(
action=_(
'Using a system-scoped token to create a project-scoped '
'or domain-scoped token is not allowed.'
)
)
if not CONF.token.allow_rescope_scoped_token:
if token.project_scoped or token.domain_scoped:
raise exception.ForbiddenAction(
action=_('rescope a scoped token'))
try:
token_audit_id = token.parent_audit_id or token.audit_id
except IndexError:
token_audit_id = None
response_data.setdefault('expires_at', token.expires_at)
response_data['audit_id'] = token_audit_id
response_data.setdefault('user_id', token.user_id)
return response_data
except AssertionError as e:
LOG.error(e)
raise exception.Unauthorized(e)