feat(用户安全): 支持要求更改密码并强制登录后改密
- 新增 users.force_password_change 字段与迁移 - 用户新增/编辑/批量导入支持要求更改密码 - 登录后未改密用户仅允许访问改密相关接口
This commit is contained in:
parent
e10f050dfc
commit
777c682a4e
@ -16,6 +16,29 @@ class AuthController extends Controller
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('auth:api')->except('login');
|
$this->middleware('auth:api')->except('login');
|
||||||
|
$this->middleware(function (Request $request, \Closure $next) {
|
||||||
|
/** @var User|null $user */
|
||||||
|
$user = Auth::guard('api')->user();
|
||||||
|
if (! $user || ! $user->force_password_change) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowed = [
|
||||||
|
'api/auth/me',
|
||||||
|
'api/auth/password',
|
||||||
|
'api/auth/logout',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (in_array($request->path(), $allowed, true)) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'code' => 423,
|
||||||
|
'message' => '请先修改密码后再继续操作',
|
||||||
|
'data' => ['force_password_change' => true],
|
||||||
|
], 423);
|
||||||
|
})->except('login');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Apidoc\Title('登录'), Apidoc\Method('POST'), Apidoc\Url('/auth/login')]
|
#[Apidoc\Title('登录'), Apidoc\Method('POST'), Apidoc\Url('/auth/login')]
|
||||||
@ -70,6 +93,7 @@ class AuthController extends Controller
|
|||||||
'data' => [
|
'data' => [
|
||||||
'user' => $user?->load('roles'),
|
'user' => $user?->load('roles'),
|
||||||
'permissions' => $user?->getAllPermissions()->pluck('name')->values(),
|
'permissions' => $user?->getAllPermissions()->pluck('name')->values(),
|
||||||
|
'force_password_change' => (bool) ($user?->force_password_change ?? false),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -104,6 +128,7 @@ class AuthController extends Controller
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$user->password = $validated['password'];
|
$user->password = $validated['password'];
|
||||||
|
$user->force_password_change = false;
|
||||||
$user->save();
|
$user->save();
|
||||||
$this->auditLog($request, 'password_update');
|
$this->auditLog($request, 'password_update');
|
||||||
|
|
||||||
|
|||||||
@ -148,6 +148,7 @@ class UserController extends Controller
|
|||||||
'email' => trim($this->firstMatchedValue($normalizedRow, ['email', 'mail', '邮箱'])),
|
'email' => trim($this->firstMatchedValue($normalizedRow, ['email', 'mail', '邮箱'])),
|
||||||
'phone' => trim($this->firstMatchedValue($normalizedRow, ['phone', 'mobile', '手机号', '手机', '电话'])),
|
'phone' => trim($this->firstMatchedValue($normalizedRow, ['phone', 'mobile', '手机号', '手机', '电话'])),
|
||||||
'password' => trim($this->firstMatchedValue($normalizedRow, ['password', 'passwd', 'pwd', '密码'])),
|
'password' => trim($this->firstMatchedValue($normalizedRow, ['password', 'passwd', 'pwd', '密码'])),
|
||||||
|
'force_password_change' => $this->parseBooleanValue($this->firstMatchedValue($normalizedRow, ['force_password_change', 'require_password_change', '要求更改密码', 'force_change_password'])),
|
||||||
];
|
];
|
||||||
|
|
||||||
$validator = Validator::make($payload, [
|
$validator = Validator::make($payload, [
|
||||||
@ -387,6 +388,16 @@ class UserController extends Controller
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function parseBooleanValue(string $value): bool
|
||||||
|
{
|
||||||
|
$normalized = strtolower(trim($value));
|
||||||
|
if ($normalized === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array($normalized, ['1', 'true', 'yes', 'y', 'on', '是', '真'], true);
|
||||||
|
}
|
||||||
|
|
||||||
private function buildUsersImportTemplateXlsx(string $xlsxPath): void
|
private function buildUsersImportTemplateXlsx(string $xlsxPath): void
|
||||||
{
|
{
|
||||||
$zip = new \ZipArchive;
|
$zip = new \ZipArchive;
|
||||||
@ -418,9 +429,9 @@ class UserController extends Controller
|
|||||||
.'</Relationships>';
|
.'</Relationships>';
|
||||||
|
|
||||||
$rows = [
|
$rows = [
|
||||||
['nickname', 'email', 'phone', 'password', 'role_ids'],
|
['nickname', 'email', 'phone', 'password', 'role_ids', 'force_password_change'],
|
||||||
['张三', 'zhangsan@example.com', '13800138000', 'Pass@123456', '1'],
|
['张三', 'zhangsan@example.com', '13800138000', 'Pass@123456', '1', '0'],
|
||||||
['李四', 'lisi@example.com', '13900139000', 'Pass@123456', '1,2'],
|
['李四', 'lisi@example.com', '13900139000', 'Pass@123456', '1,2', '1'],
|
||||||
];
|
];
|
||||||
|
|
||||||
$sheetData = '';
|
$sheetData = '';
|
||||||
|
|||||||
@ -18,6 +18,7 @@ class StoreUserRequest extends FormRequest
|
|||||||
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
||||||
'phone' => ['nullable', 'string', 'max:32', 'unique:users,phone'],
|
'phone' => ['nullable', 'string', 'max:32', 'unique:users,phone'],
|
||||||
'password' => ['required', 'string', 'min:6'],
|
'password' => ['required', 'string', 'min:6'],
|
||||||
|
'force_password_change' => ['sometimes', 'boolean'],
|
||||||
'role_ids' => ['sometimes', 'array'],
|
'role_ids' => ['sometimes', 'array'],
|
||||||
'role_ids.*' => ['integer', 'exists:roles,id'],
|
'role_ids.*' => ['integer', 'exists:roles,id'],
|
||||||
];
|
];
|
||||||
|
|||||||
@ -21,6 +21,7 @@ class UpdateUserRequest extends FormRequest
|
|||||||
'email' => ['sometimes', 'required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($userId)],
|
'email' => ['sometimes', 'required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($userId)],
|
||||||
'phone' => ['nullable', 'string', 'max:32', Rule::unique('users', 'phone')->ignore($userId)],
|
'phone' => ['nullable', 'string', 'max:32', Rule::unique('users', 'phone')->ignore($userId)],
|
||||||
'password' => ['sometimes', 'required', 'string', 'min:6'],
|
'password' => ['sometimes', 'required', 'string', 'min:6'],
|
||||||
|
'force_password_change' => ['sometimes', 'boolean'],
|
||||||
'role_ids' => ['sometimes', 'array'],
|
'role_ids' => ['sometimes', 'array'],
|
||||||
'role_ids.*' => ['integer', 'exists:roles,id'],
|
'role_ids.*' => ['integer', 'exists:roles,id'],
|
||||||
];
|
];
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Spatie\Permission\Traits\HasRoles;
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
@ -23,6 +23,7 @@ class User extends Authenticatable implements JWTSubject
|
|||||||
'email',
|
'email',
|
||||||
'phone',
|
'phone',
|
||||||
'password',
|
'password',
|
||||||
|
'force_password_change',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
@ -62,6 +63,7 @@ class User extends Authenticatable implements JWTSubject
|
|||||||
return [
|
return [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
|
'force_password_change' => 'boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->boolean('force_password_change')
|
||||||
|
->default(false)
|
||||||
|
->after('password')
|
||||||
|
->comment('是否要求下次登录后必须修改密码');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('force_password_change');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user