PHP 网站登录与权限管理系统搭建
1. 接口与返回类型
为了保证代码的一致性,将
$jokesTable
设为类变量,同时把
Authentication
对象移到构造函数中,并通过
getAuthentication
方法返回。在
EntryPoint
中添加身份验证检查之前,先在
Routes
接口里添加
getAuthentication
方法:
<?php
namespace Ninja;
interface Routes
{
public function getRoutes();
public function getAuthentication();
}
添加此方法后,实现该接口的类必须有
getAuthentication
方法。为增强安全性,可对返回值进行类型提示:
<?php
namespace Ninja;
interface Routes
{
public function getRoutes(): array;
public function getAuthentication(): \Ninja\Authentication;
}
修改接口后,
IjdbRoutes
类也需相应修改:
public function getRoutes(): array {
$jokeController =
new \Ijdb\Controllers\Joke($this->jokesTable,
$this->authorsTable);
// …
return $routes;
}
public function getAuthentication(): \Ninja\Authentication {
return $this->authentication;
}
这样的接口明确了使用该框架构建网站时,
Routes
类需包含
getRoutes
(返回数组)和
getAuthentication
(返回
Authentication
对象)方法,有助于代码共享和协作。
2. 使用身份验证类
在
EntryPoint.php
中添加检查,若路由数组里的
login
键存在且为
true
,而用户未登录,则重定向到登录页面;否则正常显示页面:
if (isset($routes[$this->route]['login']) &&
isset($routes[$this->route]['login']) &&
!$authentication->isLoggedIn()) {
header('location: /login/error');
}
else {
$controller = $routes[$this->route]
[$this->method]['controller'];
$action = $routes[$this->route][$this->method]
['action'];
$page = $controller->$action();
$title = $page['title'];
if (isset($page['variables'])) {
$output = $this->loadTemplate($page['template'],
$page['variables']);
}
else {
$output = $this->loadTemplate($page['template']);
}
include __DIR__ . '/../../templates/layout.html.php';
}
需注意,若没有
else
语句,即使重定向到登录页面,控制器动作仍会执行,可能导致数据库操作异常。
3. 登录错误信息
测试上述代码时,若尝试添加笑话,会被重定向到
/login/error
页面。可按以下步骤创建该页面显示有意义的错误信息:
1. 在
templates
目录添加
loginerror.html.php
:
<h2>You are not logged in</h2>
<p>You must be logged in to view this page. <a
href="/login">Click here to log in</a> or <a
href="/author/register">Click here to register an
account</a></p>
-
在
Ijdb\Controllers目录添加Login.php控制器:
<?php
namespace Ijdb\Controllers;
class Login
{
public function error()
{
return ['template' => 'loginerror.html.php', 'title'
=> 'You are not logged in'];
}
}
-
在
IjdbRoutes.php中实例化控制器并添加路由:
public function getRoutes(): array {
$jokeController = new \Ijdb\Controllers\Joke
($this->jokesTable, $this->authorsTable);
$authorController = new \Ijdb\Controllers\Register
($this->authorsTable);
$loginController = new \Ijdb\Controllers\Login();
$routes = [
'author/register' => [
'GET' => [
'controller' => $authorController,
'action' => 'registrationForm'
],
'POST' => [
'controller' => $authorController,
'action' => 'registerUser'
]
],
// …
'login/error' => [
'GET' => [
'controller' => $loginController,
'action' => 'error'
]
]
];
}
4. 创建登录表单
登录检查完成后,需创建登录表单。在
Login
控制器中添加构造函数和显示表单的方法:
<?php
namespace Ijdb\Controllers;
class Login
{
private $authentication;
public function __construct(\Ninja\Authentication
$authentication)
{
$this->authentication = $authentication;
}
public function error()
{
return ['template' => 'loginerror.html.php', 'title'
=> 'You are not logged in'];
}
public function loginForm() {
return ['template' => 'login.html.php',
'title' => 'Log In'];
}
}
添加
login.html.php
模板:
<?php
if (isset($error)):
echo '<div class="errors">' . $error . '</div>';
endif;
?>
<form method="post" action="">
<label for="email">Your email address</label>
<input type="text" id="email" name="email">
<label for="password">Your password</label>
<input type="password" id="password" name="password">
<input type="submit" name="login" value="Log in">
</form>
<p>Don't have an account? <a
href="/author/register">Click here to register an
account</a></p>
在
IjdbRoutes.php
中添加路由:
public function getRoutes(): array {
// …
$loginController = new \Ijdb\Controllers\
Login($this->authentication);
$routes = [
// …
'login' => [
'GET' => [
'controller' => $loginController,
'action' => 'loginForm'
]
],
// …
];
}
再添加
POST
动作和登录成功页面:
// IjdbRoutes.php
$routes = [
// …
'login' => [
'GET' => [
'controller' => $loginController,
'action' => 'loginForm'
],
'POST' => [
'controller' => $loginController,
'action' => 'processLogin'
]
],
'login/success' => [
'GET' => [
'controller' => $loginController,
'action' => 'success'
],
'login' => true
]
];
// Login.php
public function processLogin() {
if ($this->authentication->login($_POST['email'],
$_POST['password'])) {
header('location: /login/success');
}
else {
return ['template' => 'login.html.php',
'title' => 'Log In',
'variables' => [
'error' => 'Invalid username/password.'
]
];
}
}
public function success() {
return ['template' => 'loginsuccess.html.php',
'title' => 'Login Successful'];
}
// loginsuccess.html.php
<h2>Login Successful</h2>
<p>You are now logged in.</p>
5. 注销功能
在网站布局中添加登录/注销按钮,修改
layout.html.php
以根据用户登录状态显示不同链接。先修改
EntryPoint.php
中的
include
语句:
echo $this->loadTemplate('layout.html.php', ['loggedIn'
=>
$authentication->isLoggedIn(),
'output' => $output,
'title' => $title
]);
在
layout.html.php
中添加链接:
<ul>
<li><a href="/">Home</a></li>
<li><a href="/joke/list">Jokes List
</a></li>
<li><a href="/joke/edit">Add a new Joke
</a></li>
<?php if ($loggedIn): ?>
<li><a href="/logout">Log out</a>
</li>
<?php else: ?>
<li><a href="/login">Log in</a></li>
<?php endif; ?>
</ul>
创建注销页面和路由:
// Login.php
public function logout() {
unset($_SESSION);
return ['template' => 'logout.html.php',
'title' => 'You have been logged out'];
}
// IjdbRoutes.php
'logout' => [
'GET' => [
'controller' => $loginController,
'action' => 'logout'
]
];
// logout.html.php
<h2>Logged out</h2>
<p>You have been logged out</p>
6. 将添加的笑话关联到登录用户
用户可注册登录后,需将添加的笑话关联到登录用户。当前
Joke
控制器的
saveEdit
方法中
authorId
固定为 1,需修改。先在
Authentication
类中添加
getUser
方法:
public function getUser() {
if ($this->isLoggedIn()) {
return $this->users->find($this->usernameColumn,
strtolower($_SESSION['username']))[0];
}
else {
return false;
}
}
在
Joke
控制器中引入
Authentication
类:
<?php
namespace Ijdb\Controllers;
use \Ninja\DatabaseTable;
use \Ninja\Authentication;
class Joke {
private $authorsTable;
private $jokesTable;
public function __construct(DatabaseTable $jokesTable,
DatabaseTable $authorsTable,
Authentication $authentication) {
$this->jokesTable = $jokesTable;
$this->authorsTable = $authorsTable;
$this->authentication = $authentication;
}
}
修改
saveEdit
方法:
public function saveEdit() {
$author = $this->authentication->getUser();
$joke = $_POST['joke'];
$joke['jokedate'] = new \DateTime();
$joke['authorId'] = $author['id'];
$this->jokesTable->save($joke);
header('location: /joke/list');
}
7. 用户权限管理
测试登录系统时会发现,任何人都能编辑和删除他人的笑话,需添加检查来限制用户权限。
1.
隐藏笑话列表中的编辑和删除按钮
:
- 在
Joke
控制器的
list
方法中提供作者 ID:
public function list() {
$result = $this->jokesTable->findAll();
$jokes = [];
foreach ($result as $joke) {
$author = $this->authorsTable->
findById($joke['authorId']);
$jokes[] = [
'id' => $joke['id'],
'joketext' => $joke['joketext'],
'jokedate' => $joke['jokedate'],
'name' => $author['name'],
'email' => $author['email'],
'authorId' => $author['id']
];
}
// …
$totalJokes = $this->jokesTable->total();
$author = $this->authentication->getUser();
return ['template' => 'jokes.html.php',
'title' => $title,
'variables' => [
'totalJokes' => $totalJokes,
'jokes' => $jokes,
'userId' => $author['id'] ?? null
]
];
}
- 在 `jokes.html.php` 中添加 `if` 语句:
// …
echo $date->format('jS F Y');
?>)
<?php if ($userId == $joke['authorId']): ?>
<a href="/joke/edit?id=<?=$joke['id']?>">
Edit</a>
<form action="/joke/delete" method="post">
<input type="hidden" name="id"
value="<?=$joke['id']?>">
<input type="submit" value="Delete">
</form>
<?php endif; ?>
</p>
</blockquote>
<?php endforeach; ?>
-
防止直接访问编辑页面
:
-
在
Joke控制器的edit方法中传递用户 ID:
-
在
public function edit() {
$author = $this->authentication->getUser();
if (isset($_GET['id'])) {
$joke = $this->jokesTable->findById($_GET['id']);
}
$title = 'Edit joke';
return ['template' => 'editjoke.html.php',
'title' => $title,
'variables' => [
'joke' => $joke ?? null,
'userId' => $author['id'] ?? null
]
];
}
- 在 `editjoke.html.php` 中添加检查:
<?php if ($userId == $joke['authorId']): ?>
<form action="" method="post">
<input type="hidden" name="joke[id]"
value="<?=$joke['id'] ?? ''?>">
<label for="joketext">Type your joke here:
</label>
<textarea id="joketext" name="joke[joketext]" rows="3"
cols="40"><?=$joke['joketext'] ?? ''?>
</textarea>
<input type="submit" name="submit" value="Save">
</form>
<?php else: ?>
<p>You may only edit jokes that you posted.</p>
<?php endif; ?>
- 防止通过表单提交修改他人笑话 :
// Joke 控制器的 saveEdit 方法
public function saveEdit() {
$author = $this->authentication->getUser();
if (isset($_GET['id'])) {
$joke = $this->jokesTable->findById($_GET['id']);
if ($joke['authorId'] != $author['id']) {
return;
}
}
$joke = $_POST['joke'];
$joke['jokedate'] = new \DateTime();
$joke['authorId'] = $author['id'];
$this->jokesTable->save($joke);
header('location: /joke/list');
}
// Joke 控制器的 delete 方法
public function delete() {
$author = $this->authentication->getUser();
$joke = $this->jokesTable->findById($_POST['id']);
if ($joke['authorId'] != $author['id']) {
return;
}
$this->jokesTable->delete($_POST['id']);
}
通过以上步骤,可构建一个功能完善的登录系统,包括登录、注销、权限管理等功能,确保用户数据安全和操作的合理性。
以下是一个简单的流程图,展示用户登录和操作的基本流程:
graph TD;
A[用户访问页面] --> B{是否需要登录};
B -- 否 --> C[显示页面];
B -- 是 --> D{是否登录};
D -- 是 --> E[显示页面];
D -- 否 --> F[重定向到登录页面];
F --> G{输入登录信息};
G -- 成功 --> E;
G -- 失败 --> H[显示错误信息];
H --> G;
E --> I{是否有操作权限};
I -- 是 --> J[执行操作];
I -- 否 --> K[显示无权限信息];
表格总结各功能对应的文件和代码:
| 功能 | 文件 | 代码 |
| ---- | ---- | ---- |
| 接口定义 |
Routes.php
| 定义
Routes
接口及方法 |
| 身份验证检查 |
EntryPoint.php
| 检查用户登录状态并处理重定向 |
| 登录错误页面 |
loginerror.html.php
、
Login.php
、
IjdbRoutes.php
| 显示登录错误信息及路由配置 |
| 登录表单 |
login.html.php
、
Login.php
、
IjdbRoutes.php
| 显示登录表单及处理登录逻辑 |
| 注销功能 |
layout.html.php
、
Login.php
、
IjdbRoutes.php
| 显示注销按钮及处理注销逻辑 |
| 关联笑话作者 |
Joke.php
、
Authentication.php
| 将笑话与登录用户关联 |
| 用户权限管理 |
Joke.php
、
jokes.html.php
、
editjoke.html.php
| 限制用户对笑话的操作权限 |
PHP 网站登录与权限管理系统搭建(续)
8. 权限管理的进一步优化
虽然前面已经对用户权限进行了基本的管理,但仍有一些细节可以进一步优化,以提高系统的安全性和用户体验。
8.1 权限验证的封装
为了避免在多个方法中重复编写权限验证的代码,可以将权限验证逻辑封装到一个单独的方法中。例如,在
Joke
控制器中添加一个
checkPermission
方法:
private function checkPermission($jokeId) {
$author = $this->authentication->getUser();
$joke = $this->jokesTable->findById($jokeId);
return $joke['authorId'] == $author['id'];
}
然后在
saveEdit
和
delete
方法中调用这个方法:
public function saveEdit() {
if (isset($_GET['id']) && !$this->checkPermission($_GET['id'])) {
return;
}
$author = $this->authentication->getUser();
$joke = $_POST['joke'];
$joke['jokedate'] = new \DateTime();
$joke['authorId'] = $author['id'];
$this->jokesTable->save($joke);
header('location: /joke/list');
}
public function delete() {
if (!isset($_POST['id']) || !$this->checkPermission($_POST['id'])) {
return;
}
$this->jokesTable->delete($_POST['id']);
}
这样可以使代码更加简洁和易于维护。
8.2 错误信息的统一处理
当用户没有权限进行操作时,除了简单地返回,还可以统一处理错误信息,给用户更友好的提示。可以在
Joke
控制器中添加一个
showNoPermissionMessage
方法:
private function showNoPermissionMessage() {
return ['template' => 'nopermission.html.php',
'title' => 'No Permission',
'variables' => [
'message' => 'You do not have permission to perform this action.'
]
];
}
然后在权限验证失败时调用这个方法:
public function saveEdit() {
if (isset($_GET['id']) && !$this->checkPermission($_GET['id'])) {
return $this->showNoPermissionMessage();
}
$author = $this->authentication->getUser();
$joke = $_POST['joke'];
$joke['jokedate'] = new \DateTime();
$joke['authorId'] = $author['id'];
$this->jokesTable->save($joke);
header('location: /joke/list');
}
public function delete() {
if (!isset($_POST['id']) || !$this->checkPermission($_POST['id'])) {
return $this->showNoPermissionMessage();
}
$this->jokesTable->delete($_POST['id']);
}
同时,创建
nopermission.html.php
模板:
<h2>No Permission</h2>
<p>{{message}}</p>
9. 安全性考虑
在实现登录和权限管理系统时,安全性是至关重要的。以下是一些需要注意的安全问题及解决方法。
9.1 密码加密
在用户注册和登录过程中,密码应该进行加密存储,以防止密码泄露。可以使用 PHP 的
password_hash
和
password_verify
函数来实现密码的加密和验证。
在用户注册时,对密码进行加密:
// Register.php 中的 registerUser 方法
public function registerUser() {
$author = $_POST['author'];
$author['password'] = password_hash($author['password'], PASSWORD_DEFAULT);
$this->authorsTable->save($author);
header('location: /login');
}
在用户登录时,验证加密后的密码:
// Authentication.php 中的 login 方法
public function login($email, $password) {
$author = $this->users->find('email', $email)[0];
if ($author && password_verify($password, $author['password'])) {
$_SESSION['username'] = $author['email'];
return true;
}
return false;
}
9.2 防止 SQL 注入
在进行数据库查询时,要防止 SQL 注入攻击。可以使用预处理语句来避免这个问题。例如,在
DatabaseTable
类中,使用 PDO 的预处理语句:
// DatabaseTable.php 中的 find 方法
public function find($column, $value) {
$query = 'SELECT * FROM ' . $this->table . ' WHERE ' . $column . ' = :value';
$stmt = $this->pdo->prepare($query);
$stmt->bindValue(':value', $value);
$stmt->execute();
return $stmt->fetchAll();
}
10. 性能优化
为了提高系统的性能,可以对一些操作进行优化。
10.1 缓存用户信息
在用户登录后,可以将用户信息缓存起来,避免每次操作都从数据库中查询用户信息。例如,在
Authentication
类中添加一个缓存变量:
class Authentication {
private $users;
private $usernameColumn;
private $cachedUser;
public function __construct($users, $usernameColumn) {
$this->users = $users;
$this->usernameColumn = $usernameColumn;
$this->cachedUser = null;
}
public function getUser() {
if ($this->isLoggedIn()) {
if ($this->cachedUser === null) {
$this->cachedUser = $this->users->find($this->usernameColumn, strtolower($_SESSION['username']))[0];
}
return $this->cachedUser;
}
return false;
}
}
10.2 数据库查询优化
可以对数据库查询进行优化,例如添加索引、优化查询语句等。在
joke
表的
authorId
列上添加索引,可以加快根据作者 ID 查询笑话的速度:
ALTER TABLE joke ADD INDEX idx_authorId (authorId);
11. 总结与展望
通过以上步骤,我们成功构建了一个功能完善的 PHP 网站登录与权限管理系统,包括登录、注销、权限管理、安全性和性能优化等方面。以下是一个简单的流程图,展示了系统的整体架构:
graph LR;
A[用户] --> B[登录系统];
B --> C{是否登录成功};
C -- 是 --> D[访问页面或执行操作];
C -- 否 --> E[显示登录错误信息];
D --> F{是否有操作权限};
F -- 是 --> G[执行操作];
F -- 否 --> H[显示无权限信息];
G --> I[数据库操作];
I --> J[更新或查询数据];
表格总结系统的主要功能和对应的优化措施:
| 功能 | 优化措施 |
| ---- | ---- |
| 登录与注销 | 密码加密、缓存用户信息 |
| 权限管理 | 封装权限验证逻辑、统一错误处理 |
| 数据库操作 | 防止 SQL 注入、数据库查询优化 |
未来,可以进一步扩展系统的功能,例如添加角色管理、多因素认证等,以提高系统的安全性和灵活性。同时,可以对系统进行性能测试和优化,确保系统在高并发情况下的稳定性和响应速度。
总之,一个完善的登录与权限管理系统是网站安全和用户体验的重要保障,通过合理的设计和优化,可以为用户提供一个安全、便捷的使用环境。
超级会员免费看
917

被折叠的 条评论
为什么被折叠?



