diff --git a/cmdb-api/api/app.py b/cmdb-api/api/app.py
index 757a5da..6c9d15f 100644
--- a/cmdb-api/api/app.py
+++ b/cmdb-api/api/app.py
@@ -21,7 +21,6 @@ from api.extensions import (bcrypt, cache, celery, cors, db, es, login_manager,
 from api.extensions import inner_secrets
 from api.lib.perm.authentication.cas import CAS
 from api.lib.perm.authentication.oauth2 import OAuth2
-from api.lib.perm.authentication.oidc import OIDC
 from api.lib.secrets.secrets import InnerKVManger
 from api.models.acl import User
 
@@ -98,7 +97,6 @@ def create_app(config_object="settings"):
     register_shell_context(app)
     register_commands(app)
     CAS(app)
-    OIDC(app)
     OAuth2(app)
     app.wsgi_app = ReverseProxy(app.wsgi_app)
     configure_upload_dir(app)
diff --git a/cmdb-api/api/lib/database.py b/cmdb-api/api/lib/database.py
index d991d1a..634a99f 100644
--- a/cmdb-api/api/lib/database.py
+++ b/cmdb-api/api/lib/database.py
@@ -94,7 +94,7 @@ class CRUDMixin(FormatMixin):
         if any((isinstance(_id, six.string_types) and _id.isdigit(),
                 isinstance(_id, (six.integer_types, float))), ):
             obj = getattr(cls, "query").get(int(_id))
-            if obj and not obj.deleted:
+            if obj and not getattr(obj, 'deleted', False):
                 return obj
 
     @classmethod
diff --git a/cmdb-api/api/lib/perm/acl/audit.py b/cmdb-api/api/lib/perm/acl/audit.py
index 4732b9c..bfce104 100644
--- a/cmdb-api/api/lib/perm/acl/audit.py
+++ b/cmdb-api/api/lib/perm/acl/audit.py
@@ -1,14 +1,19 @@
 # -*- coding:utf-8 -*-
+
+import datetime
 import itertools
 import json
 from enum import Enum
 from typing import List
 
-from flask import has_request_context, request
+from flask import has_request_context
+from flask import request
 from flask_login import current_user
 from sqlalchemy import func
 
+from api.extensions import db
 from api.lib.perm.acl import AppCache
+from api.models.acl import AuditLoginLog
 from api.models.acl import AuditPermissionLog
 from api.models.acl import AuditResourceLog
 from api.models.acl import AuditRoleLog
@@ -283,6 +288,27 @@ class AuditCRUD(object):
 
         return data
 
+    @staticmethod
+    def search_login(_, q=None, page=1, page_size=10, start=None, end=None):
+        query = db.session.query(AuditLoginLog)
+
+        if start:
+            query = query.filter(AuditLoginLog.login_at >= start)
+        if end:
+            query = query.filter(AuditLoginLog.login_at <= end)
+
+        if q:
+            query = query.filter(AuditLoginLog.username == q)
+
+        records = query.order_by(
+            AuditLoginLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
+
+        data = {
+            'data': [r.to_dict() for r in records],
+        }
+
+        return data
+
     @classmethod
     def add_role_log(cls, app_id, operate_type: AuditOperateType,
                      scope: AuditScope, link_id: int, origin: dict, current: dict, extra: dict,
@@ -348,3 +374,24 @@ class AuditCRUD(object):
         AuditTriggerLog.create(app_id=app_id, trigger_id=trigger_id, operate_uid=user_id,
                                operate_type=operate_type.value,
                                origin=origin, current=current, extra=extra, source=source.value)
+
+    @classmethod
+    def add_login_log(cls, username, is_ok, description, _id=None, logout_at=None):
+        if _id is not None:
+            existed = AuditLoginLog.get_by_id(_id)
+            if existed is not None:
+                existed.update(logout_at=logout_at)
+                return
+
+        payload = dict(username=username,
+                       is_ok=is_ok,
+                       description=description,
+                       logout_at=logout_at,
+                       ip=request.headers.get('X-Real-IP') or request.remote_addr,
+                       browser=request.headers.get('User-Agent'),
+                       )
+
+        if logout_at is None:
+            payload['login_at'] = datetime.datetime.now()
+
+        return AuditLoginLog.create(**payload).id
diff --git a/cmdb-api/api/lib/perm/acl/resp_format.py b/cmdb-api/api/lib/perm/acl/resp_format.py
index 25f6bdc..5767716 100644
--- a/cmdb-api/api/lib/perm/acl/resp_format.py
+++ b/cmdb-api/api/lib/perm/acl/resp_format.py
@@ -4,6 +4,8 @@ from api.lib.resp_format import CommonErrFormat
 
 
 class ErrFormat(CommonErrFormat):
+    login_succeed = "登录成功"
+    invalid_password = "密码验证失败"
     auth_only_with_app_token_failed = "应用 Token验证失败"
     session_invalid = "您不是应用管理员 或者 session失效(尝试一下退出重新登录)"
 
@@ -17,11 +19,11 @@ class ErrFormat(CommonErrFormat):
     role_exists = "角色 {} 已经存在!"
     global_role_not_found = "全局角色 {} 不存在!"
     global_role_exists = "全局角色 {} 已经存在!"
-    user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
 
     resource_no_permission = "您没有资源: {} 的 {} 权限"
     admin_required = "需要管理员权限"
     role_required = "需要角色: {}"
+    user_role_delete_invalid = "删除用户角色, 请在 用户管理 页面操作!"
 
     app_is_ready_existed = "应用 {} 已经存在"
     app_not_found = "应用 {} 不存在!"
diff --git a/cmdb-api/api/lib/perm/authentication/cas/routing.py b/cmdb-api/api/lib/perm/authentication/cas/routing.py
index 27fc635..906d9e8 100644
--- a/cmdb-api/api/lib/perm/authentication/cas/routing.py
+++ b/cmdb-api/api/lib/perm/authentication/cas/routing.py
@@ -1,4 +1,5 @@
 # -*- coding:utf-8 -*-
+import datetime
 import uuid
 
 import bs4
@@ -12,7 +13,11 @@ from flask_login import login_user
 from flask_login import logout_user
 from six.moves.urllib_request import urlopen
 
+from api.lib.common_setting.common_data import AuthenticateDataCRUD
+from api.lib.common_setting.const import AuthenticateType
+from api.lib.perm.acl.audit import AuditCRUD
 from api.lib.perm.acl.cache import UserCache
+from api.lib.perm.acl.resp_format import ErrFormat
 from .cas_urls import create_cas_login_url
 from .cas_urls import create_cas_logout_url
 from .cas_urls import create_cas_validate_url
@@ -21,7 +26,7 @@ blueprint = Blueprint('cas', __name__)
 
 
 @blueprint.route('/api/cas/login')
-# @blueprint.route('/api/sso/login')
+@blueprint.route('/api/sso/login')
 def login():
     """
     This route has two purposes. First, it is used by the user
@@ -34,6 +39,7 @@ def login():
     If validation was successful the logged in username is saved in
     the user's session under the key `CAS_USERNAME_SESSION_KEY`.
     """
+    config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
 
     cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
     if request.values.get("next"):
@@ -41,8 +47,8 @@ def login():
 
     _service = url_for('cas.login', _external=True)
     redirect_url = create_cas_login_url(
-        current_app.config['CAS_SERVER'],
-        current_app.config['CAS_LOGIN_ROUTE'],
+        config['cas_server'],
+        config['cas_login_route'],
         _service)
 
     if 'ticket' in request.args:
@@ -51,30 +57,38 @@ def login():
     if request.args.get('ticket'):
 
         if validate(request.args['ticket']):
-            redirect_url = session.get("next") or current_app.config.get("CAS_AFTER_LOGIN")
+            redirect_url = session.get("next") or config.get("cas_after_login")
             username = session.get("CAS_USERNAME")
             user = UserCache.get(username)
             login_user(user)
 
             session.permanent = True
 
+            _id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
+            session['LOGIN_ID'] = _id
+
         else:
             del session[cas_token_session_key]
             redirect_url = create_cas_login_url(
-                current_app.config['CAS_SERVER'],
-                current_app.config['CAS_LOGIN_ROUTE'],
+                config['cas_server'],
+                config['cas_login_route'],
                 url_for('cas.login', _external=True),
                 renew=True)
+
+            AuditCRUD.add_login_log(session.get("CAS_USERNAME"), False, ErrFormat.invalid_password)
+
     current_app.logger.info("redirect to: {0}".format(redirect_url))
     return redirect(redirect_url)
 
 
 @blueprint.route('/api/cas/logout')
-# @blueprint.route('/api/sso/logout')
+@blueprint.route('/api/sso/logout')
 def logout():
     """
     When the user accesses this route they are logged out.
     """
+    config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
+    current_app.logger.info(config)
 
     cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
     cas_token_session_key = current_app.config['CAS_TOKEN_SESSION_KEY']
@@ -86,12 +100,14 @@ def logout():
     "next" in session and session.pop("next")
 
     redirect_url = create_cas_logout_url(
-        current_app.config['CAS_SERVER'],
-        current_app.config['CAS_LOGOUT_ROUTE'],
+        config['cas_server'],
+        config['cas_logout_route'],
         url_for('cas.login', _external=True, next=request.referrer))
 
     logout_user()
 
+    AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
+
     current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
 
     return redirect(redirect_url)
@@ -104,14 +120,15 @@ def validate(ticket):
     and the validated username is saved in the session under the
     key `CAS_USERNAME_SESSION_KEY`.
     """
+    config = AuthenticateDataCRUD(AuthenticateType.CAS).get()
 
     cas_username_session_key = current_app.config['CAS_USERNAME_SESSION_KEY']
 
     current_app.logger.debug("validating token {0}".format(ticket))
 
     cas_validate_url = create_cas_validate_url(
-        current_app.config['CAS_VALIDATE_SERVER'],
-        current_app.config['CAS_VALIDATE_ROUTE'],
+        config['cas_validate_server'],
+        config['cas_validate_route'],
         url_for('cas.login', _external=True),
         ticket)
 
@@ -138,14 +155,12 @@ def validate(ticket):
             current_app.logger.info("create user: {}".format(username))
             from api.lib.perm.acl.user import UserCRUD
             soup = bs4.BeautifulSoup(response)
-            cas_user_map = current_app.config.get('CAS_USER_MAP')
-
+            cas_user_map = config.get('cas_user_map')
             user_dict = dict()
             for k in cas_user_map:
                 v = soup.find(cas_user_map[k]['tag'], cas_user_map[k].get('attrs', {}))
                 user_dict[k] = v and v.text or None
             user_dict['password'] = uuid.uuid4().hex
-
             UserCRUD.add(**user_dict)
 
         from api.lib.perm.acl.acl import ACLManager
diff --git a/cmdb-api/api/lib/perm/authentication/ldap.py b/cmdb-api/api/lib/perm/authentication/ldap.py
new file mode 100644
index 0000000..2d6da88
--- /dev/null
+++ b/cmdb-api/api/lib/perm/authentication/ldap.py
@@ -0,0 +1,56 @@
+# -*- coding:utf-8 -*-
+
+import uuid
+
+from flask import current_app
+from flask import session
+from ldap3 import ALL
+from ldap3 import AUTO_BIND_NO_TLS
+from ldap3 import Connection
+from ldap3 import Server
+from ldap3.core.exceptions import LDAPBindError
+from ldap3.core.exceptions import LDAPCertificateError
+
+from api.lib.perm.acl.audit import AuditCRUD
+from api.lib.perm.acl.resp_format import ErrFormat
+from api.models.acl import User
+
+
+def authenticate_with_ldap(username, password):
+    server = Server(current_app.config.get('LDAP').get('ldap_server'), get_info=ALL, connect_timeout=3)
+    if '@' in username:
+        email = username
+        who = current_app.config['LDAP'].get('ldap_user_dn').format(username.split('@')[0])
+    else:
+        who = current_app.config['LDAP'].get('ldap_user_dn').format(username)
+        email = "{}@{}".format(who, current_app.config['LDAP'].get('ldap_domain'))
+
+    username = username.split('@')[0]
+    user = User.query.get_by_username(username)
+    try:
+        if not password:
+            raise LDAPCertificateError
+
+        try:
+            conn = Connection(server, user=who, password=password, auto_bind=AUTO_BIND_NO_TLS)
+        except LDAPBindError:
+            conn = Connection(server,
+                              user=f"{username}@{current_app.config['LDAP'].get('ldap_domain')}",
+                              password=password,
+                              auto_bind=AUTO_BIND_NO_TLS)
+
+        if conn.result['result'] != 0:
+            AuditCRUD.add_login_log(username, False, ErrFormat.invalid_password)
+            raise LDAPBindError
+        else:
+            _id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
+            session['LOGIN_ID'] = _id
+
+        if not user:
+            from api.lib.perm.acl.user import UserCRUD
+            user = UserCRUD.add(username=username, email=email, password=uuid.uuid4().hex)
+
+        return user, True
+    except LDAPBindError as e:
+        current_app.logger.info(e)
+        return user, False
diff --git a/cmdb-api/api/lib/perm/authentication/oauth2/__init__.py b/cmdb-api/api/lib/perm/authentication/oauth2/__init__.py
index cb45334..1b7d02e 100644
--- a/cmdb-api/api/lib/perm/authentication/oauth2/__init__.py
+++ b/cmdb-api/api/lib/perm/authentication/oauth2/__init__.py
@@ -18,6 +18,10 @@ class OAuth2(object):
         app.config.setdefault('OAUTH2_RESPONSE_TYPE', 'code')
         app.config.setdefault('OAUTH2_AFTER_LOGIN', '/')
 
+        app.config.setdefault('OIDC_GRANT_TYPE', 'authorization_code')
+        app.config.setdefault('OIDC_RESPONSE_TYPE', 'code')
+        app.config.setdefault('OIDC_AFTER_LOGIN', '/')
+
         # Register Blueprint
         app.register_blueprint(routing.blueprint, url_prefix=url_prefix)
 
diff --git a/cmdb-api/api/lib/perm/authentication/oauth2/routing.py b/cmdb-api/api/lib/perm/authentication/oauth2/routing.py
index ca5c0f7..0d1c518 100644
--- a/cmdb-api/api/lib/perm/authentication/oauth2/routing.py
+++ b/cmdb-api/api/lib/perm/authentication/oauth2/routing.py
@@ -1,5 +1,6 @@
 # -*- coding:utf-8 -*-
 
+import datetime
 import secrets
 import uuid
 
@@ -14,69 +15,80 @@ from flask import url_for
 from flask_login import login_user, logout_user
 from six.moves.urllib.parse import urlencode
 
+from api.lib.common_setting.common_data import AuthenticateDataCRUD
+from api.lib.perm.acl.audit import AuditCRUD
 from api.lib.perm.acl.cache import UserCache
+from api.lib.perm.acl.resp_format import ErrFormat
 
 blueprint = Blueprint('oauth2', __name__)
 
 
-@blueprint.route('/api/oauth2/login')
-@blueprint.route('/api/sso/login')
-def login():
+@blueprint.route('/api/<string:auth_type>/login')
+def login(auth_type):
+    config = AuthenticateDataCRUD(auth_type.upper()).get()
+
     if request.values.get("next"):
         session["next"] = request.values.get("next")
 
-    session['oauth2_state'] = secrets.token_urlsafe(16)
+    session[f'{auth_type}_state'] = secrets.token_urlsafe(16)
+
+    auth_type = auth_type.upper()
 
     qs = urlencode({
-        'client_id': current_app.config['OAUTH2_CLIENT_ID'],
+        'client_id': config['client_id'],
         'redirect_uri': url_for('oauth2.callback', _external=True),
-        'response_type': current_app.config['OAUTH2_RESPONSE_TYPE'],
-        'scope': ' '.join(current_app.config['OAUTH2_SCOPES'] or []),
-        'state': session['oauth2_state'],
+        'response_type': current_app.config[f'{auth_type}_RESPONSE_TYPE'],
+        'scope': ' '.join(config['scopes'] or []),
+        'state': session[f'{auth_type.lower()}_state'],
     })
 
-    return redirect("{}?{}".format(current_app.config['OAUTH2_AUTHORIZE_URL'].split('?')[0], qs))
+    return redirect("{}?{}".format(config['authorize_url'].split('?')[0], qs))
 
 
-@blueprint.route('/api/oauth2/callback')
-def callback():
-    redirect_url = session.get("next") or current_app.config.get("OAUTH2_AFTER_LOGIN")
+@blueprint.route('/api/<string:auth_type>/callback')
+def callback(auth_type):
+    auth_type = auth_type.upper()
+    config = AuthenticateDataCRUD(auth_type).get()
 
-    if request.values['state'] != session.get('oauth2_state'):
+    redirect_url = session.get("next") or config.get('after_login')
+
+    if request.values['state'] != session.get(f'{auth_type.lower()}_state'):
         return abort(401, "state is invalid")
 
     if 'code' not in request.values:
         return abort(401, 'code is invalid')
 
-    response = requests.post(current_app.config['OAUTH2_TOKEN_URL'], data={
-        'client_id': current_app.config['OAUTH2_CLIENT_ID'],
-        'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'],
+    response = requests.post(config['token_url'], data={
+        'client_id': config['client_id'],
+        'client_secret': config['client_secret'],
         'code': request.values['code'],
-        'grant_type': current_app.config['OAUTH2_GRANT_TYPE'],
+        'grant_type': current_app.config[f'{auth_type}_GRANT_TYPE'],
         'redirect_uri': url_for('oauth2.callback', _external=True),
     }, headers={'Accept': 'application/json'})
     if response.status_code != 200:
         current_app.logger.error(response.text)
         return abort(401)
-    oauth2_token = response.json().get('access_token')
-    if not oauth2_token:
+    access_token = response.json().get('access_token')
+    if not access_token:
         return abort(401)
 
-    response = requests.get(current_app.config['OAUTH2_USER_INFO']['url'], headers={
-        'Authorization': 'Bearer {}'.format(oauth2_token),
+    response = requests.get(config['user_info']['url'], headers={
+        'Authorization': 'Bearer {}'.format(access_token),
         'Accept': 'application/json',
     })
     if response.status_code != 200:
         return abort(401)
 
-    email = current_app.config['OAUTH2_USER_INFO']['email'](response.json())
-    username = current_app.config['OAUTH2_USER_INFO']['username'](response.json())
+    res = response.json()
+    email = res.get(config['user_info']['email'])
+    username = res.get(config['user_info']['username'])
+    avatar = res.get(config['user_info'].get('avatar'))
     user = UserCache.get(username)
     if user is None:
         current_app.logger.info("create user: {}".format(username))
         from api.lib.perm.acl.user import UserCRUD
 
-        user_dict = dict(username=username, email=email)
+        user_dict = dict(username=username, email=email, avatar=avatar)
         user_dict['password'] = uuid.uuid4().hex
 
         user = UserCRUD.add(**user_dict)
@@ -98,15 +110,17 @@ def callback():
                           roleName=user_info.get("role"))
     session["uid"] = user_info.get("uid")
 
+    _id = AuditCRUD.add_login_log(username, True, ErrFormat.login_succeed)
+    session['LOGIN_ID'] = _id
+
     return redirect(redirect_url)
 
 
-@blueprint.route('/api/oauth2/logout')
-@blueprint.route('/api/sso/logout')
-def logout():
+@blueprint.route('/api/<string:auth_type>/logout')
+def logout(auth_type):
     "acl" in session and session.pop("acl")
     "uid" in session and session.pop("uid")
-    'oauth2_state' in session and session.pop('oauth2_state')
+    f'{auth_type}_state' in session and session.pop(f'{auth_type}_state')
     "next" in session and session.pop("next")
 
     redirect_url = url_for('oauth2.login', _external=True, next=request.referrer)
@@ -115,4 +129,6 @@ def logout():
 
     current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
 
+    AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
+
     return redirect(redirect_url)
diff --git a/cmdb-api/api/lib/perm/authentication/oidc/__init__.py b/cmdb-api/api/lib/perm/authentication/oidc/__init__.py
deleted file mode 100644
index 67a5925..0000000
--- a/cmdb-api/api/lib/perm/authentication/oidc/__init__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding:utf-8 -*-
-
-from flask import current_app
-
-from . import routing
-
-
-class OIDC(object):
-    def __init__(self, app=None, url_prefix=None):
-        self._app = app
-        if app is not None:
-            self.init_app(app, url_prefix)
-
-    @staticmethod
-    def init_app(app, url_prefix=None):
-        # Configuration defaults
-        app.config.setdefault('OIDC_GRANT_TYPE', 'authorization_code')
-        app.config.setdefault('OIDC_RESPONSE_TYPE', 'code')
-        app.config.setdefault('OIDC_AFTER_LOGIN', '/')
-        # Register Blueprint
-        app.register_blueprint(routing.blueprint, url_prefix=url_prefix)
-
-    @property
-    def app(self):
-        return self._app or current_app
diff --git a/cmdb-api/api/lib/perm/authentication/oidc/routing.py b/cmdb-api/api/lib/perm/authentication/oidc/routing.py
deleted file mode 100644
index fce284a..0000000
--- a/cmdb-api/api/lib/perm/authentication/oidc/routing.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# -*- coding:utf-8 -*-
-
-import secrets
-import uuid
-
-import requests
-from flask import Blueprint
-from flask import abort
-from flask import current_app
-from flask import redirect
-from flask import request
-from flask import session
-from flask import url_for
-from flask_login import login_user, logout_user
-from six.moves.urllib.parse import urlencode
-
-from api.lib.perm.acl.cache import UserCache
-
-blueprint = Blueprint('oidc', __name__)
-
-
-@blueprint.route('/api/oidc/login')
-@blueprint.route('/api/sso/login')
-def login():
-    if request.values.get("next"):
-        session["next"] = request.values.get("next")
-
-    session['oidc_state'] = secrets.token_urlsafe(16)
-
-    qs = urlencode({
-        'client_id': current_app.config['OIDC_CLIENT_ID'],
-        'redirect_uri': url_for('oidc.callback', _external=True),
-        'response_type': current_app.config['OIDC_RESPONSE_TYPE'],
-        'scope': ' '.join(current_app.config['OIDC_SCOPES'] or []),
-        'state': session['oidc_state'],
-    })
-
-    return redirect("{}?{}".format(current_app.config['OIDC_AUTHORIZE_URL'].split('?')[0], qs))
-
-
-@blueprint.route('/api/oidc/callback')
-def callback():
-    redirect_url = session.get("next") or current_app.config.get("OIDC_AFTER_LOGIN")
-
-    if request.values['state'] != session.get('oidc_state'):
-        return abort(401, "state is invalid")
-
-    if 'code' not in request.values:
-        return abort(401, 'code is invalid')
-
-    response = requests.post(current_app.config['OIDC_TOKEN_URL'], data={
-        'client_id': current_app.config['OIDC_CLIENT_ID'],
-        'client_secret': current_app.config['OIDC_CLIENT_SECRET'],
-        'code': request.values['code'],
-        'grant_type': current_app.config['OIDC_GRANT_TYPE'],
-        'redirect_uri': url_for('oidc.callback', _external=True),
-    }, headers={'Accept': 'application/json'})
-    if response.status_code != 200:
-        current_app.logger.error(response.text)
-        return abort(401)
-    oidc_token = response.json().get('access_token')
-    if not oidc_token:
-        return abort(401)
-
-    response = requests.get(current_app.config['OIDC_USER_INFO']['url'], headers={
-        'Authorization': 'Bearer {}'.format(oidc_token),
-        'Accept': 'application/json',
-    })
-    if response.status_code != 200:
-        return abort(401)
-
-    email = current_app.config['OIDC_USER_INFO']['email'](response.json())
-    username = current_app.config['OIDC_USER_INFO']['username'](response.json())
-    user = UserCache.get(username)
-    if user is None:
-        current_app.logger.info("create user: {}".format(username))
-        from api.lib.perm.acl.user import UserCRUD
-
-        user_dict = dict(username=username, email=email)
-        user_dict['password'] = uuid.uuid4().hex
-
-        user = UserCRUD.add(**user_dict)
-
-    # log the user in
-    login_user(user)
-
-    from api.lib.perm.acl.acl import ACLManager
-    user_info = ACLManager.get_user_info(username)
-
-    session["acl"] = dict(uid=user_info.get("uid"),
-                          avatar=user.avatar if user else user_info.get("avatar"),
-                          userId=user_info.get("uid"),
-                          rid=user_info.get("rid"),
-                          userName=user_info.get("username"),
-                          nickName=user_info.get("nickname") or user_info.get("username"),
-                          parentRoles=user_info.get("parents"),
-                          childRoles=user_info.get("children"),
-                          roleName=user_info.get("role"))
-    session["uid"] = user_info.get("uid")
-
-    return redirect(redirect_url)
-
-
-@blueprint.route('/api/oidc/logout')
-@blueprint.route('/api/sso/logout')
-def logout():
-    "acl" in session and session.pop("acl")
-    "uid" in session and session.pop("uid")
-    'oidc_state' in session and session.pop('oidc_state')
-    "next" in session and session.pop("next")
-
-    redirect_url = url_for('oidc.login', _external=True, next=request.referrer)
-
-    logout_user()
-
-    current_app.logger.debug('Redirecting to: {0}'.format(redirect_url))
-
-    return redirect(redirect_url)
diff --git a/cmdb-api/api/models/acl.py b/cmdb-api/api/models/acl.py
index b0683ec..d4c9efa 100644
--- a/cmdb-api/api/models/acl.py
+++ b/cmdb-api/api/models/acl.py
@@ -5,17 +5,18 @@ import copy
 import hashlib
 from datetime import datetime
 
-from ldap3 import Server, Connection, ALL
-from ldap3.core.exceptions import LDAPBindError, LDAPCertificateError
 from flask import current_app
+from flask import session
 from flask_sqlalchemy import BaseQuery
 
 from api.extensions import db
 from api.lib.database import CRUDModel
 from api.lib.database import Model
+from api.lib.database import Model2
 from api.lib.database import SoftDeleteMixin
 from api.lib.perm.acl.const import ACL_QUEUE
 from api.lib.perm.acl.const import OperateType
+from api.lib.perm.acl.resp_format import ErrFormat
 
 
 class App(Model):
@@ -28,21 +29,26 @@ class App(Model):
 
 
 class UserQuery(BaseQuery):
-    def _join(self, *args, **kwargs):
-        super(UserQuery, self)._join(*args, **kwargs)
 
     def authenticate(self, login, password):
+        from api.lib.perm.acl.audit import AuditCRUD
+
         user = self.filter(db.or_(User.username == login,
                                   User.email == login)).filter(User.deleted.is_(False)).filter(User.block == 0).first()
         if user:
-            current_app.logger.info(user)
             authenticated = user.check_password(password)
             if authenticated:
-                from api.tasks.acl import op_record
-                op_record.apply_async(args=(None, login, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
+                _id = AuditCRUD.add_login_log(login, True, ErrFormat.login_succeed)
+                session['LOGIN_ID'] = _id
+            else:
+                AuditCRUD.add_login_log(login, False, ErrFormat.invalid_password)
         else:
             authenticated = False
 
+            AuditCRUD.add_login_log(login, False, ErrFormat.user_not_found.format(login))
+
+        current_app.logger.info(("login", login, user, authenticated))
+
         return user, authenticated
 
     def authenticate_with_key(self, key, secret, args, path):
@@ -57,38 +63,6 @@ class UserQuery(BaseQuery):
 
         return user, authenticated
 
-    def authenticate_with_ldap(self, username, password):
-        server = Server(current_app.config.get('LDAP_SERVER'), get_info=ALL)
-        if '@' in username:
-            email = username
-            who = current_app.config.get('LDAP_USER_DN').format(username.split('@')[0])
-        else:
-            who = current_app.config.get('LDAP_USER_DN').format(username)
-            email = "{}@{}".format(who, current_app.config.get('LDAP_DOMAIN'))
-
-        username = username.split('@')[0]
-        user = self.get_by_username(username)
-        try:
-            if not password:
-                raise LDAPCertificateError
-
-            conn = Connection(server, user=who, password=password)
-            conn.bind()
-            if conn.result['result'] != 0:
-                raise LDAPBindError
-            conn.unbind()
-
-            if not user:
-                from api.lib.perm.acl.user import UserCRUD
-                user = UserCRUD.add(username=username, email=email)
-
-            from api.tasks.acl import op_record
-            op_record.apply_async(args=(None, username, OperateType.LOGIN, ["ACL"]), queue=ACL_QUEUE)
-
-            return user, True
-        except LDAPBindError:
-            return user, False
-
     def search(self, key):
         query = self.filter(db.or_(User.email == key,
                                    User.nickname.ilike('%' + key + '%'),
@@ -138,6 +112,7 @@ class User(CRUDModel, SoftDeleteMixin):
     wx_id = db.Column(db.String(32))
     employee_id = db.Column(db.String(16), index=True)
     avatar = db.Column(db.String(128))
+
     # apps = db.Column(db.JSON)
 
     def __str__(self):
@@ -168,8 +143,6 @@ class User(CRUDModel, SoftDeleteMixin):
 
 
 class RoleQuery(BaseQuery):
-    def _join(self, *args, **kwargs):
-        super(RoleQuery, self)._join(*args, **kwargs)
 
     def authenticate(self, login, password):
         role = self.filter(Role.name == login).first()
@@ -377,3 +350,16 @@ class AuditTriggerLog(Model):
     current = db.Column(db.JSON, default=dict(), comment='当前数据')
     extra = db.Column(db.JSON, default=dict(), comment='权限名')
     source = db.Column(db.String(16), default='', comment='来源')
+
+
+class AuditLoginLog(Model2):
+    __tablename__ = "acl_audit_login_logs"
+
+    username = db.Column(db.String(64), index=True)
+    channel = db.Column(db.Enum('web', 'api'), default="web")
+    ip = db.Column(db.String(15))
+    browser = db.Column(db.String(256))
+    description = db.Column(db.String(128))
+    is_ok = db.Column(db.Boolean)
+    login_at = db.Column(db.DateTime)
+    logout_at = db.Column(db.DateTime)
diff --git a/cmdb-api/api/views/acl/audit.py b/cmdb-api/api/views/acl/audit.py
index ae4c20e..9826bb9 100644
--- a/cmdb-api/api/views/acl/audit.py
+++ b/cmdb-api/api/views/acl/audit.py
@@ -24,6 +24,7 @@ class AuditLogView(APIView):
             'role': AuditCRUD.search_role,
             'trigger': AuditCRUD.search_trigger,
             'resource': AuditCRUD.search_resource,
+            'login': AuditCRUD.search_login,
         }
         if name not in func_map:
             abort(400, f'wrong {name}, please use {func_map.keys()}')
diff --git a/cmdb-api/api/views/acl/login.py b/cmdb-api/api/views/acl/login.py
index 09ee89a..ca18e23 100644
--- a/cmdb-api/api/views/acl/login.py
+++ b/cmdb-api/api/views/acl/login.py
@@ -8,11 +8,13 @@ from flask import abort
 from flask import current_app
 from flask import request
 from flask import session
-from flask_login import login_user, logout_user
+from flask_login import login_user
+from flask_login import logout_user
 
 from api.lib.decorator import args_required
 from api.lib.decorator import args_validate
 from api.lib.perm.acl.acl import ACLManager
+from api.lib.perm.acl.audit import AuditCRUD
 from api.lib.perm.acl.cache import RoleCache
 from api.lib.perm.acl.cache import User
 from api.lib.perm.acl.cache import UserCache
@@ -34,8 +36,9 @@ class LoginView(APIView):
         username = request.values.get("username") or request.values.get("email")
         password = request.values.get("password")
         _role = None
-        if current_app.config.get('AUTH_WITH_LDAP'):
-            user, authenticated = User.query.authenticate_with_ldap(username, password)
+        if current_app.config.get('LDAP', {}).get('enabled'):
+            from api.lib.perm.authentication.ldap import authenticate_with_ldap
+            user, authenticated = authenticate_with_ldap(username, password)
         else:
             user, authenticated = User.query.authenticate(username, password)
             if not user:
@@ -176,4 +179,7 @@ class LogoutView(APIView):
     @auth_abandoned
     def post(self):
         logout_user()
+
+        AuditCRUD.add_login_log(None, None, None, _id=session.get('LOGIN_ID'), logout_at=datetime.datetime.now())
+
         self.jsonify(code=200)
diff --git a/cmdb-api/settings.example.py b/cmdb-api/settings.example.py
index e125e3d..4887092 100644
--- a/cmdb-api/settings.example.py
+++ b/cmdb-api/settings.example.py
@@ -11,10 +11,10 @@ from environs import Env
 env = Env()
 env.read_env()
 
-ENV = env.str("FLASK_ENV", default="production")
-DEBUG = ENV == "development"
-SECRET_KEY = env.str("SECRET_KEY")
-BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13)
+ENV = env.str('FLASK_ENV', default='production')
+DEBUG = ENV == 'development'
+SECRET_KEY = env.str('SECRET_KEY')
+BCRYPT_LOG_ROUNDS = env.int('BCRYPT_LOG_ROUNDS', default=13)
 DEBUG_TB_ENABLED = DEBUG
 DEBUG_TB_INTERCEPT_REDIRECTS = False
 
@@ -23,7 +23,7 @@ ERROR_CODES = [400, 401, 403, 404, 405, 500, 502]
 # # database
 SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
 SQLALCHEMY_BINDS = {
-    "user": 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
+    'user': 'mysql+pymysql://{user}:{password}@127.0.0.1:3306/{db}?charset=utf8'
 }
 SQLALCHEMY_ECHO = False
 SQLALCHEMY_TRACK_MODIFICATIONS = False
@@ -32,11 +32,11 @@ SQLALCHEMY_ENGINE_OPTIONS = {
 }
 
 # # cache
-CACHE_TYPE = "redis"
-CACHE_REDIS_HOST = "127.0.0.1"
+CACHE_TYPE = 'redis'
+CACHE_REDIS_HOST = '127.0.0.1'
 CACHE_REDIS_PORT = 6379
-CACHE_REDIS_PASSWORD = ""
-CACHE_KEY_PREFIX = "CMDB::"
+CACHE_REDIS_PASSWORD = ''
+CACHE_KEY_PREFIX = 'CMDB::'
 CACHE_DEFAULT_TIMEOUT = 3000
 
 # # log
@@ -55,10 +55,10 @@ DEFAULT_MAIL_SENDER = ''
 
 # # queue
 CELERY = {
-    "broker_url": 'redis://127.0.0.1:6379/2',
-    "result_backend": "redis://127.0.0.1:6379/2",
-    "broker_vhost": "/",
-    "broker_connection_retry_on_startup": True
+    'broker_url': 'redis://127.0.0.1:6379/2',
+    'result_backend': 'redis://127.0.0.1:6379/2',
+    'broker_vhost': '/',
+    'broker_connection_retry_on_startup': True
 }
 ONCE = {
     'backend': 'celery_once.backends.Redis',
@@ -70,68 +70,78 @@ ONCE = {
 # =============================== Authentication ===========================================================
 
 # # CAS
-AUTH_WITH_CAS = False
-CAS_SERVER = "https://{your-casdoor-hostname}"
-CAS_VALIDATE_SERVER = "https://{your-casdoor-hostname}"
-CAS_LOGIN_ROUTE = "/cas/built-in/cas/login"
-CAS_LOGOUT_ROUTE = "/cas/built-in/cas/logout"
-CAS_VALIDATE_ROUTE = "/cas/built-in/cas/serviceValidate"
-CAS_AFTER_LOGIN = "/"
-CAS_USER_MAP = {
-    "username": {"tag": "cas:user"},
-    "nickname": {"tag": "cas:attribute", "attrs": {"name": "displayName"}},
-    "email": {"tag": "cas:attribute", "attrs": {"name": "email"}},
-    "mobile": {"tag": "cas:attribute", "attrs": {"name": "phone"}},
-    "avatar": {"tag": "cas:attribute", "attrs": {"name": "avatar"}},
-}
+CAS = dict(
+    enabled=False,
+    cas_server='https://{your-CASServer-hostname}',
+    cas_validate_server='https://{your-CASServer-hostname}',
+    cas_login_route='/cas/built-in/cas/login',
+    cas_logout_route='/cas/built-in/cas/logout',
+    cas_validate_route='/cas/built-in/cas/serviceValidate',
+    cas_after_login='/',
+    cas_user_map={
+        'username': {'tag': 'cas:user'},
+        'nickname': {'tag': 'cas:attribute', 'attrs': {'name': 'displayName'}},
+        'email': {'tag': 'cas:attribute', 'attrs': {'name': 'email'}},
+        'mobile': {'tag': 'cas:attribute', 'attrs': {'name': 'phone'}},
+        'avatar': {'tag': 'cas:attribute', 'attrs': {'name': 'avatar'}},
+    }
+)
 
 # # OAuth2.0
-AUTH_WITH_OAUTH2 = False
-OAUTH2_CLIENT_ID = ""
-OAUTH2_CLIENT_SECRET = ""
-OAUTH2_AUTHORIZE_URL = "https://{your-casdoor-hostname}/login/oauth/authorize"
-OAUTH2_TOKEN_URL = "https://{your-casdoor-hostname}/api/login/oauth/access_token"
-OAUTH2_USER_INFO = {
-    "url": "https://{your-casdoor-hostname}/api/userinfo",
-    "email": lambda x: x['email'],
-    "username": lambda x: x['name']
-}
-OAUTH2_SCOPES = ["profile email"]
-OAUTH2_AFTER_LOGIN = "/"
+OAUTH2 = dict(
+    enabled=False,
+    client_id='',
+    client_secret='',
+    authorize_url='https://{your-OAuth2Server-hostname}/login/oauth/authorize',
+    token_url='https://{your-OAuth2Server-hostname}/api/login/oauth/access_token',
+    scopes=['profile', 'email'],
+    user_info={
+        'url': 'https://{your-OAuth2Server-hostname}/api/userinfo',
+        'email': 'email',
+        'username': 'name',
+        'avatar': 'picture'
+    },
+    after_login='/'
+)
 
 # # OIDC
-AUTH_WITH_OIDC = False
-OIDC_CLIENT_ID = ""
-OIDC_CLIENT_SECRET = ""
-OIDC_AUTHORIZE_URL = "https://{your-casdoor-hostname}/login/oauth/authorize"
-OIDC_TOKEN_URL = "https://{your-casdoor-hostname}/api/login/oauth/access_token"
-OIDC_USER_INFO = {
-    "url": "https://{your-casdoor-hostname}/api/userinfo",
-    "email": lambda x: x['email'],
-    "username": lambda x: x['name']
-}
-OIDC_SCOPES = ["openid profile email"]
-OIDC_AFTER_LOGIN = "/"
+OIDC = dict(
+    enabled=False,
+    client_id='',
+    client_secret='',
+    authorize_url='https://{your-OIDCServer-hostname}/login/oauth/authorize',
+    token_url='https://{your-OIDCServer-hostname}/api/login/oauth/access_token',
+    scopes=['openid', 'profile', 'email'],
+    user_info={
+        'url': 'https://{your-OIDCServer-hostname}/api/userinfo',
+        'email': 'email',
+        'username': 'name',
+        'avatar': 'picture'
+    },
+    after_login='/'
+)
 
 # # LDAP
-AUTH_WITH_LDAP = False
-LDAP_SERVER = ''
-LDAP_DOMAIN = ''
-LDAP_USER_DN = 'cn={},ou=users,dc=xxx,dc=com'
+LDAP = dict(
+    enabled=False,
+    ldap_server='',
+    ldap_domain='',
+    ldap_user_dn='cn={},ou=users,dc=xxx,dc=com'
+)
 # ==========================================================================================================
 
 # # pagination
 DEFAULT_PAGE_COUNT = 50
 
 # # permission
-WHITE_LIST = ["127.0.0.1"]
+WHITE_LIST = ['127.0.0.1']
 USE_ACL = True
 
 # # elastic search
 ES_HOST = '127.0.0.1'
 USE_ES = False
 
-BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y']
+BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, 'Yes', 'YES', 'yes', 'Y', 'y']
 
 # # messenger
 USE_MESSENGER = True