PHP CodeIgniter项目个人开发小功能分享--学习心得①

本文介绍了在PHPCodeIgniter框架中实现用户登录功能时的会话初始化和验证过程,包括生成随机会话密钥、存储和验证密钥,以及前端与后端的交互。作者分享了如何处理初次请求和后续请求,以及防止会话劫持和数据泄露的安全措施。
摘要由CSDN通过智能技术生成

PHP CodeIgniter 项目个人开发小功能分享–学习心得①

前言

本人碰到一个维护项目,在技术选型上已经不具备可能性,只能在原有的基础上继续开发,和用新技术来重构。重构由于风险太大,因此在这种情况下,继续在原有项目基础上迭代开发成为了不可避免的选择,为什么要选择CI,不选择TP,laravel,也是没得选择啦!

虽然在框架选择上并没有太多的选择余地,但PHP CodeIgniter框架作为一种服务端渲染框架,具有一些优点,如减少客户端负担、提高网站的安全性和可维护性,以及更易于搜索引擎索引和识别网站内容等。当然,服务端渲染也有一些局限性,例如无法实现实时更新和动态交互等功能,需要使用客户端渲染来实现。

首先我分享下关于完整用户登录功能的完善的两个要素:
     ①通信保密性--会话初始化功能
     ②身份鉴别-登录失败处理功能
针对用户安全性和使用体验的两点完善措施:
     ③对单个帐户的多重并发会话进行限制
     ④设置超过一定时间未连接自动结束会话

在本文中,我将分享我在实现 PHP CodeIgniter 框架会话初始化功能时的经验和总结,希望能够对其他开发人员有所帮助。同时,也希望能够借此机会向其他开发者学习和交流,共同进步,请大佬们轻拍!

通信保密性–会话初始化功能

在通信双方建立连接之前,应用系统利用密码技术进行会话初始化验证。用户第一次访问网页时,由于没有session的存在,所以服务器会随机分配一个新的sessionid给他,会话初始化验证通常用于确保用户的身份和凭据有效。在会话初始化期间,可以对用户身份进行多重验证,如登录口令、验证码、第三方身份验证等等。这有助于确保用户的身份得到有效的保护,防止账户被盗用、欺诈等恶意行为。相比于仅验证账户密码的方式,会话初始化验证可以提高用户账户的安全性。

该功能是在login.html中输入完用户名密码验证码后,form表单提交三者元素给后端。 在后端对应的controller类中的Login撰写一个verify函数,这个函数就是我们所说的接口,该函数的实现主要包括两个部分:
会话初始化和验证密钥。
①在第一次访问时,会话初始化会生成一个随机的会话密钥,并将其发送到客户端保存。同时,服务端设置会话标志以表示会话已经初始化过了。如果后续的请求没有带着该密钥,就无法通过后续的验证。
②对于每个请求,服务端会从POST请求中获取客户端传来的密钥,并与服务端保存的密钥进行比较。如果两者一致,则认为验证通过,否则会话会被终止。

接下来让我们一步一步实现这个重要的verify函数:
①我们因为需要发两次请求到verify函数,因此需要一个标识来判断是否是初次进入,我们通过session机制存储一个标识is_session_initiated来进行判断

 $this->session->set_userdata('is_session_initiated', true);

②那么就需要用一个if判断来分割首次和第二次的请求,将首次请求包裹在if (!$this->session->userdata(‘is_session_initiated’))中,紧接着就得返回我们随机生成的session_key(随机生成的具有安全性的十六进制数)

 
 if (!$this->session->userdata('is_session_initiated')) 
 {
    $session_key = bin2hex(random_bytes(16));
    $response = array('session_key' => $session_key,'msg'=>true);
    echo json_encode($response);
    return;
}

③因为这是首次请求,在此之前都没有存储is_session_initiated标识,所有得在后面加点代码,不然每次请求都是进入这个判断里,顺便将生成session_key也保存到session里。

	// 会话初始化
  if (!$this->session->userdata('is_session_initiated')){//检测是否是第一次请求,产生一个 session_key给前端,以后访问,此用户都带着这个key
      $session_key = bin2hex(random_bytes(16));
      $response = array('session_key' => $session_key,'msg'=>true);
      header('Content-Type: application/json');
      echo json_encode($response);
      $this->session->set_userdata('session_key', $session_key);
      $this->session->set_userdata('is_session_initiated', true);
      return;
 }

④后面就是第二次请求verify的时候,要验证session_key和用户名,密码了。我们先将这从前端传来的三个参数获取到先.

$username = $this->input->post('username'); // Get form data
$password = $this->input->post('password');
$received_key = $this->input->post('received_key');

⑤随后从session的userdata中取出先前这个用户专属的session_key

$session_key = $this->session->userdata('session_key');

⑥随后很容易了,就是做比对,received_key是前端传来的,两者一比对,正确的就进行下一步,错误了就抛出错误

if ($received_key != $session_key)
{
   // 密钥比对不一致,传错误到前端
   error('Session key validation failed.');
   return;
}

那么简易的verify函数就实现了,以下是完整的代码:

public function verify(){
  if($this->input->post('init'))
	{
	// 会话初始化
      if (!$this->session->userdata('is_session_initiated')) {//检测是否是第一次请求,产生一个session_key给前端,以后访问,此用户都带着这个key
       // Generate session key
        $session_key = bin2hex(random_bytes(16));
	// Send session key to client (e.g. via HTTPS)
	    $response = array('session_key' => $session_key,'msg'=>true);
	    header('Content-Type: application/json');
	    echo json_encode($response);
	        
	        // Set flag in session
		$this->session->set_userdata('session_key', $session_key);
	    $this->session->set_userdata('is_session_initiated', true);
	    return;
	    }
	    // 验证session key
		$username = $this->input->post('username'); // Get form data
		$password = $this->input->post('password');
	    $received_key = $this->input->post('received_key');
	    $session_key = $this->session->userdata('session_key');
	    if ($received_key != $session_key) {
	        // 密钥比对不一致,传错误到前端
	        error('Session key validation failed.');
	        return;
			}
	}
		
}

如果不写 verify 函数,那么在后端就没有进行会话初始化和密钥验证的功能。这将导致以下后果:
会话安全性降低:没有进行会话初始化,就不能保证会话的安全性。未经初始化的会话可能容易受到各种攻击,如会话劫持、会话固化等。
数据泄露:没有进行密钥验证,攻击者可以使用任意密钥进行请求,这可能导致敏感数据泄露,如用户信息、密码等。无法追踪用户:未初始化的会话无法被跟踪,无法知道哪些请求是由哪个用户发出的。
网站不可用性:如果网站没有进行会话初始化,那么可能会出现一些问题,如用户无法登录、无法进行某些操作等,这可能会导致网站不可用。
综上所述,如果不进行会话初始化和密钥验证,那么会话的安全性无法得到保障,同时可能会导致用户数据泄露和网站不可用等问题。因此,编写 verify 函数是确保会话安全性的重要一步

接下来,我们需要在前端的Login.html中添加JavaScript代码,使用JQuery库和Ajax方法来向服务端发送请求。

<form action="<?php echo site_url('login/verify')?>" onsubmit="onSub(event)" method="post" class="form-signin loginform" style="width: 250px;padding-left: 15px;">
        <button class="btn btn-large btn-primary" id="form" type="submit" style="width: 230px;margin-left: 5px;" >登录</button>
</form>
<script type="text/javascript">
		function onSub(event){
			document.getElementById('password').value = window.btoa(document.getElementById('password').value);			
			
		}
		
		var submit = document.getElementById('form');
		
		submit.onclick = function firstReq(){
			// Get username and password
			var username = document.getElementById('username').value;
			var password = document.getElementById('password').value;
			// var received_key = document.getElementById('received_key').value;
			// Send request to server to initiate session and get session key
			$.ajax({
			    url: "<?php echo site_url('Login/verify'); ?>",
			    type: 'POST',
			    dataType: 'json',
			    data:{received_key: null,init:true},
			    success: function(response) {
			        // Use session key to encrypt password
					if(response.msg='true'){
			        var session_key = response.session_key;
			        var encrypted_password = CryptoJS.AES.encrypt(password, session_key).toString();
					console.log(session_key)
			        // Send request to server to verify login
			        SecondReq(session_key);
					}
			    },
			    error: function() {
			        // Error occurred, display error message
			        var error_msg = document.getElementById('error-msg');
			        error_msg.innerHTML = 'An error occurred while initiating session.';
			        error_msg.style.display = 'block';
			    },
				//其中第一个 AJAX 请求发送到 /login/verify 以初始化会话并获取会话密钥,
				//第二个 AJAX 请求也发送到 /login/verify,以使用会话密钥对加密密码进行验证。如果登录验证成功,将重定向到主页,否则将显示错误消息
			});
		}
		
		function SecondReq(session_key){
			// var received_key = document.getElementById('received_key').value;
			console.log(session_key)
			received_key = session_key;
			console.log(session_key,'已传')
			
			$.ajax({
			    url: "<?php echo site_url('Login/verify'); ?>",
			    type: 'POST',
			    dataType: 'json',
				data:{received_key:session_key,init:true},
			    success: function(response) {
					console.log(response,'接收成功')
					
					$('form').unbind('submit').submit()
			    },
			    error: function() {
					console.log('第二个error')
			        // Error occurred, display error message
			        var error_msg = document.getElementById('error-msg');
			        error_msg.innerHTML = 'An error occurred while verifying login.';
			        error_msg.style.display = 'block';
			    }
			});
		}

    </script>

login表单绑定的是onSub事件,事件提交后会首先通过post请求发送FormData给后端写好的verify函数中进行判断。那么起初我的思路是发送两个嵌套的ajax请求,第一次是获取初始会话密钥,赋值received_key = session_key之后,作为附加参数跟账号密码,验证码一起发送第二次请求来进行验证。第一次遇到问题是ajax中的data参数传到后端是空的,我百思不得其解,打印之后发现是和Form表单默认提交行为冲突了。第一次解决尝试是在提交一开始给一个关闭默认行为,event.preventDefault();发送完两次请求再在success中开启提交,发现这是不行的,在控制台中发送在循环提交请求,差点死机。
那么我第二次解决方案是在表单中写入一个新的按钮,表单默认提交是通过type为submit的按钮来的,我可以用新的button来控制这个默认提交行为。

<form action="<?php echo site_url('login/verify')?>" onsubmit="onSub(event)" method="post" class="form-signin loginform" style="width: 250px;padding-left: 15px;">
        <button class="btn btn-large btn-primary" id="submit" style="width: 230px;margin-left: 5px;" >登录</button>
        <button class="btn btn-large btn-primary" id="form" type="submit" style="width: 230px;margin-left: 5px;display:none;" >登录</button>
      </form>

那么在js中也改一下


	var submit = document.getElementById('submit');
		
		submit.onclick=...

这样子就可以实现先让ajax两次请求先发送,密钥验证成功后,再开启一次form表单提交,$(‘form’).unbind(‘submit’).submit()

在此这是我的经验和解决方案!这是一个很好的教训,前端和后端之间的交互需要精心协调,以避免冲突和错误。我使用的新按钮来控制默认提交行为的方法算是一个技巧

最后通过了密钥的验证后,就会对用户名,密码,验证码开始验证,我使用的是密码加密技术,在前端中传来的密码是经过base64加密的,得在后端解密。

$password = base64_decode($password);

想要保证安全性,我存入密码到数据库也是经过了md5加盐

// 将密码进行md5加盐加密
 function do_hash($psw) {
        $salt = 'aFXBxYmk@lsw46y7b8C5qN5!2s'; // 定义一个salt值,最好够长,或者随机
        return md5(md5($psw).$salt); // 返回加salt后的散列
}
 $password = do_hash($password);

那么在这里分享了这么一个小功能,有需要的朋友很荣幸能帮到你们,如果有什么更好的解决方案欢迎来我这里一起讨论!~ 我也是第一次发表文章,感谢杰佬对我这个小菜鸟的重视给我舞台撰写的机会。PHP虽然已经大势已去,相关资料也非常稀少,但是其轻量便利开发还是值得使用的~

那么很快我也会更新一下第二个觉得挺有意思的功能

身份鉴别-登录失败处理功能

采取结束会话、限制非法登录次数和自动退出等措施

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值