LDAP活动目录认证插件 功能增强 文档教程

1Bill 5月前 1182


由于公司业务发展需要,在原认证基础上增加LDAP认证。实现统一账户认证。

目前公司已经把邮件、网盘、GIT、禅道、一些不能使用认证的应用比如(elasticsearch,netdata)通过nginx 的auth_request 与活动目录认证集成。


现在分享下:这个插件,程序简单粗暴。

至于 活动目录用户则是通过 shell脚本定时写入到数据库,保证用户信息同步。 这里就不提供了。

欢迎大家一起交流。。。。






LDAP配置信息采用 cache。

通过 kv_get('ldap_settiings') 获取参数。


新增模型: ./model/ldap.func.php

<?php

/**
* Created by PhpStorm.
* User: bill <benma9@qq.com>
* Date: 2019/1/20
* Time: 2:43 AM
*/


/**
*  返回配置参数
* @param string $key
* @return
*/


function config_get($key)
{
   global $ldap_config;
   $ldap_config=kv_get('ldap_settings');
   if($ldap_config) {
       return isset($ldap_config[$key])?$ldap_config[$key] : $value;
   }else {
       return '';
   }
}

/**
* Return true if the parameter is an empty string or a string
* containing only whitespace, false otherwise
* @param string $p_var string to test
* @return bool
* @access public
*/
function is_blank( $p_var ) {
   $p_var = trim( $p_var );
   $str_len = strlen( $p_var );
   if( 0 == $str_len ) {
       return true;
   }
   return false;
}

/**
* Connect and bind to the LDAP directory
* @param string $p_binddn
* @param string $p_password
* @return resource or false
*/
function ldap_connect_bind( $p_binddn = '', $p_password = '' ) {
   if( !extension_loaded( 'ldap' ) ) {
       message(1,'请确认已经安装php-ldap?');
   }

   $t_ldap_server = config_get( 'ldap_server' );

   # Connect to LDAP server
   $t_ds = @ldap_connect( $t_ldap_server );

   if ( $t_ds !== false && $t_ds > 0 ) {
       $t_protocol_version = config_get( 'ldap_protocol_version' );

       if( $t_protocol_version > 0 ) {
           $t_result = @ldap_set_option( $t_ds, LDAP_OPT_PROTOCOL_VERSION, $t_protocol_version );
           if( !$t_result ) {
               echo ( $t_ds );
           }
       }

       # Set referrals flag.
       $t_follow_referrals = 0 == config_get( 'ldap_follow_referrals' );
       $t_result = @ldap_set_option( $t_ds, LDAP_OPT_REFERRALS, $t_follow_referrals );
       if( !$t_result ) {
           echo( $t_ds );
       }

       # If no Bind DN and Password is set, attempt to login as the configured
       #  Bind DN.
       if( is_blank( $p_binddn ) && is_blank( $p_password ) ) {
           $p_binddn = config_get( 'ldap_bind_dn', '' );
           $p_password = config_get( 'ldap_bind_passwd', '' );
       }



       if( !is_blank( $p_binddn ) && !is_blank( $p_password ) ) {
           $t_br = @ldap_bind( $t_ds, $p_binddn, $p_password );
       } else {
           # Either the Bind DN or the Password are empty, so attempt an anonymous bind.
           $t_br = @ldap_bind( $t_ds );
       }


   }

   return $t_ds;
}

/**
* Escapes the LDAP string to disallow injection.
*
* @param string $p_string The string to escape.
* @return string The escaped string.
*/
function ldap_escape_string( $p_string ) {
   $t_find = array( '\\', '*', '(', ')', '/', "\x00" );
   $t_replace = array( '\5c', '\2a', '\28', '\29', '\2f', '\00' );

   $t_string = str_replace( $t_find, $t_replace, $p_string );

   return $t_string;
}

/**
* Gets the value of a specific field from LDAP given the user name
* and LDAP field name.
*
* @todo Implement caching by retrieving all needed information in one query.
* @todo Implement logging to LDAP queries same way like DB queries.
*
* @param string $p_username The user name.
* @param string $p_field The LDAP field name.
* @return string The field value or null if not found.
*/
function ldap_get_field_from_username( $p_username, $p_field ) {

   $t_ldap_organization    = config_get( 'ldap_organization' );
   $t_ldap_root_dn         = config_get( 'ldap_root_dn' );
   $t_ldap_uid_field     = config_get( 'ldap_uid_field' );

   $c_username = ldap_escape_string( $p_username );


   # Bind
   $t_ds = @ldap_connect_bind();
   if ( $t_ds === false ) {
       return null;
   }

   # Search
   $t_search_filter        = "(&$t_ldap_organization($t_ldap_uid_field=$c_username))";
   $t_search_attrs         = array( $t_ldap_uid_field, $p_field, 'dn' );

   $t_sr = @ldap_search( $t_ds, $t_ldap_root_dn, $t_search_filter, $t_search_attrs );
   if ( $t_sr === false ) {
       ldap_unbind( $t_ds );
       return null;
   }

   # Get results
   $t_info = ldap_get_entries( $t_ds, $t_sr );
   if ( $t_info === false ) {
       return null;
   }

   # Free results / unbind
   ldap_free_result( $t_sr );
   ldap_unbind( $t_ds );

   # If no matches, return null.
   if ( count( $t_info ) == 0 ) {
       return null;
   }

   # Make sure the requested field exists
   if( is_array($t_info[0]) && array_key_exists( $p_field, $t_info[0] ) ) {
       $t_value = $t_info[0][$p_field][0];
   } else {
       return null;
   }

   return $t_value;
}


/**
* Authenticates an user via LDAP given the username and password.
*
* @param string $p_username The user name.
* @param string $p_password The password.
* @return true: authenticated, false: failed to authenticate.
*/
function ldap_authenticate_by_username( $p_username, $p_password ) {

       $c_username = ldap_escape_string( $p_username );

       $t_ldap_organization = config_get( 'ldap_organization' );
       $t_ldap_root_dn = config_get( 'ldap_root_dn' );

       $t_ldap_uid_field = config_get( 'ldap_uid_field', 'uid' );
       $t_search_filter = "(&$t_ldap_organization($t_ldap_uid_field=$c_username))";
       $t_search_attrs = array(
           $t_ldap_uid_field,
           'dn',
       );

       # Bind
       try {
           $t_ds = ldap_connect_bind();
       } catch  (Exception $e) {
           message(1,'活动目录设置参数可能不对,请检查?');
       }


       # Search for the user id
       $t_sr = ldap_search( $t_ds, $t_ldap_root_dn, $t_search_filter, $t_search_attrs );
       if ( $t_sr === false ) {
           ldap_unbind( $t_ds );
       }

       $t_info = @ldap_get_entries( $t_ds, $t_sr );
       if ( $t_info === false ) {
           ldap_free_result( $t_sr );
           ldap_unbind( $t_ds );
       }

       $t_authenticated = false;

       if ( $t_info['count'] > 0 ) {
           # Try to authenticate to each until we get a match
           for ( $i = 0; $i < $t_info['count']; $i++ ) {
               $t_dn = $t_info[$i]['dn'];
               # Attempt to bind with the DN and password
               if ( @ldap_bind( $t_ds, $t_dn, $p_password ) ) {

                   $t_authenticated = true;
                   break;
               }
           }
       }

       ldap_free_result( $t_sr );
       ldap_unbind( $t_ds );


   return $t_authenticated;
}



控制器 

覆盖 ./route/user.php  (login部分重写,增加LDAP认证方法,LDAP认证不通过,则继续使用原认证),此模版会与 xn_mobile冲突,如有手机登录需求,请自行合并。

elseif($action == 'login') {

  // hook user_login_get_post.php

  if($method == 'GET') {

     // hook user_login_get_start.php

     $referer = user_http_referer();

     $header['title'] = lang('user_login');

     // hook user_login_get_end.php

     include _include(APP_PATH.'view/htm/user_login.htm');

  } else if($method == 'POST') {

     // hook user_login_post_start.php
// LDAP认证部分
include APP_PATH.'plugin/socialbird_ldap/model/ldap.func.php';

$email = param('email');         // 邮箱或者手机号 / email or mobile
$password = param('password');
empty($email) AND message('email', lang('email_is_empty'));

if(is_email($email, $err)) {
$_user = user_read_by_email($email);

empty($_user) AND message('email', lang('email_not_exists'));
} else {
$_user = user_read_by_username($email);
empty($_user) AND message('email', lang('username_not_exists'));
}


if (ldap_authenticate_by_username($_user['username'],$password))
{
user_update($_user['uid'], array('login_ip' => $longip, 'login_date' => $time, 'logins+' => 1));
$uid = $_user['uid'];
$_SESSION['uid'] = $uid;
user_token_set($_user['uid']);
message(0, lang('user_login_successfully'));
}

$password = md5($password);

// LDAP认证结束,注释掉以下代码,
// ****同时覆盖 view/htm/user_login.html文件,****
//  注释掉  70行代码       //postdata.password = $.md5(postdata.password);
//  防止密码 md5加密,活动目录认证则失败!
// 账户同步脚本 可以联系作者 bill<benma9@qq.com> 获得。

//    $email = param('email');         // 邮箱或者手机号 / email or mobile
//    $password = param('password');
//    empty($email) AND message('email', lang('email_is_empty'));
//    if(is_email($email, $err)) {
//       $_user = user_read_by_email($email);
//       empty($_user) AND message('email', lang('email_not_exists'));
//    } else {
//       $_user = user_read_by_username($email);
//       empty($_user) AND message('email', lang('username_not_exists'));
//    }

     !is_password($password, $err) AND message('password', $err);
     $check = (md5($password.$_user['salt']) == $_user['password']);
     // hook user_login_post_password_check_after.php
     !$check AND message('password', lang('password_incorrect'));

     // 更新登录时间和次数
     // update login times
     user_update($_user['uid'], array('login_ip'=>$longip, 'login_date' =>$time , 'logins+'=>1));

     // 全局变量 $uid 会在结束后,在函数 register_shutdown_function() 中存入 session (文件: model/session.func.php)
     // global variable $uid will save to session in register_shutdown_function() (file: model/session.func.php)
     $uid = $_user['uid'];

     $_SESSION['uid'] = $uid;

     user_token_set($_user['uid']);

     // hook user_login_post_end.php

     // 设置 token,下次自动登陆。

     message(0, lang('user_login_successfully'));

  }

管理: 覆盖./admin/index.php (login部分重写)


if($action == 'login') {

   // hook admin_index_login_get_post.php

   if($method == 'GET') {

       // hook admin_index_login_get_start.php

       $header['title'] = lang('admin_login');

       include _include(ADMIN_PATH."view/htm/index_login.htm");

   } else if($method == 'POST') {

       // hook admin_index_login_post_start.php

       include APP_PATH.'plugin/socialbird_ldap/model/ldap.func.php';

       $password = param('password');

       if (!ldap_authenticate_by_username($user['username'],$password))
       {
           $password = md5($password);


           if(md5($password.$user['salt']) != $user['password']) {
               xn_log('password error. uid:'.$user['uid'].' - ******'.substr($password, -6), 'admin_login_error');
               message('password', lang('password_incorrect'));
           }
       }


       admin_token_set();

       xn_log('login successed. uid:'.$user['uid'], 'admin_login');

       // hook admin_index_login_post_end.php

       message(0, jump(lang('login_successfully'), '.'));

   }

}

模版view:

覆盖 ./view/htm/user_login.htm  ,注释掉70行,防止md5加密导致ldap认证无法通过

覆盖 ./admin/view/htm/index_login.htm, 注释 

//postdata.password = $.md5(postdata.password);


最新回复 (5)
返回
发新帖