LDAP活动目录认证插件 [复制链接]

moqu8 2019-1-21

3278 0
由于公司业务发展需要,在原认证基础上增加LDAP认证。实现统一账户认证。
目前公司已经把邮件、网盘、GIT、禅道、一些不能使用认证的应用比如(elasticsearch,netdata)通过nginx 的auth_request 与活动目录认证集成。

现在分享下:这个插件,程序简单粗暴。
至于 活动目录用户则是通过 shell脚本定时写入到数据库,保证用户信息同步。 这里就不提供了。
欢迎大家一起交流。。。。
624375c452dd09116a.png93685c452dd3d23b1.png402105c452dd82e8c0.png

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<[email]benma9@qq.com[/email]> 获得。

// $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);

最新回复 (0)
返回
支持中心
邮箱:winkill2012@qqcom
新站优化中!部分功能尚未完善,敬请谅解!
支持中心