密码
Pin 的 Password 模块用于处理应用中的密码转换、存储和校验。
它提供密码编码、请求密码解析、密码哈希和密码验证等能力,用于规范应用中的密码处理方式。
密码处理
Pin 将密码处理分为密码编码、请求转换和密码存储三个阶段。
encode()
encode() 用于将用户输入转换为标准密码编码。
当前使用 MD5 对密码进行编码,并将结果转换为大写:
use Pin\Support\Facades\Password;
$encoded = Password::encode('secret');返回值为 32 位大写编码字符串。
标准密码编码用于后续的请求转换、密码存储和密码校验。
INFO
encode() 用于统一密码处理格式,不用于密码存储。密码存使用 hash()。
encodeToRequest()
encodeToRequest() 用于将标准密码编码转换为接口请求格式。
请求密码使用 AES 加密,并采用随机密钥生成请求值:
use Pin\Support\Facades\Password;
$requestPassword = Password::encodeToRequest('secret');服务端可通过 decodeFromRequest() 将请求密码解析为标准密码编码。
前端实现
前端提交密码时,需要生成与 Password::encodeToRequest() 一致的请求密码格式。
import { encrypt, md5 } from "./crypt";
export function encodePassword(plain: string) {
const encoded = md5(md5(plain)).toUpperCase();
return encrypt(encoded);
}import AES from "crypto-js/aes";
import { parse } from "crypto-js/enc-utf8";
import MD5 from "crypto-js/md5";
import pkcs7 from "crypto-js/pad-pkcs7";
import { random } from "./string";
export function encrypt(encoded: string) {
const key = random();
const s = parse(key);
return (
random(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") + // 表示随机密钥
key +
AES.encrypt(encoded, s, {
iv: s,
padding: pkcs7,
}).toString()
);
}
export function md5(plain: string) {
return MD5(plain).toString();
}const CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
export function random(n = 16, chars = CHARS) {
const len = chars.length;
let result = "";
for (let i = 0; i < n; i++) {
result += chars.charAt(Math.floor(Math.random() * len));
}
return result;
}登录接口示例:
await request.post("/api/login", {
account: form.account,
password: encrypt(form.password),
});修改密码接口中,密码相关字段也需要转换:
await request.post("/api/profile/password", {
current_password: encodePassword(form.currentPassword),
new_password: encodePassword(form.newPassword),
new_password_confirmation: encodePassword(form.confirmPassword),
});decodeFromRequest()
decodeFromRequest() 用于解析请求密码,将请求中的密码还原为标准密码编码。
解析过程与 encodeToRequest() 相反:
use Pin\Support\Facades\Password;
$encoded = Password::decodeFromRequest($requestPassword);hash() 和 check()
hash() 用于生成密码存储值,check() 用于验证密码是否匹配。
use Pin\Support\Facades\Password;
$encoded = Password::decodeFromRequest(
$request->input('password')
);
$hash = Password::hash($encoded, $user->salt);
$valid = Password::check(
$encoded,
$user->salt,
$user->password
);自定义密码处理
业务系统可以继承 Pin\Password\Password,根据现有密码处理规则覆盖默认实现。
例如,使用 MD5 作为密码编码,并直接将编码结果作为请求密码:
<?php
declare(strict_types=1);
namespace App;
use Override;
use Pin\Errors\Errors;
use Pin\Password\PasswordException;
class Password extends \Pin\Password\Password
{
#[Override]
public function decodeFromRequest(string $requestPassword): string
{
if ($this->isValid($requestPassword)) {
return $requestPassword;
}
throw new PasswordException(
"请求密码异常[{$requestPassword}]",
Errors::PasswordInvalid->code(),
);
}
#[Override]
public function encode(string $plain): string
{
return md5($plain);
}
#[Override]
public function encodeToRequest(string $plain): string
{
return md5($plain);
}
#[Override]
protected function isValid(string $password): bool
{
return strlen($password) === 32 && $password === strtolower($password);
}
}然后将业务实现绑定到 Pin 的 Password 服务,以覆盖默认实现:
namespace App\Providers;
use App\Password;
use Pin\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$this->app->singleton(\Pin\Password\Password::class, Password::class);
//
}
}完成绑定后,仍通过 Pin 提供的 Password Facade 使用密码服务,无需修改业务代码:
use Pin\Support\Facades\Password;
$encoded = Password::decodeFromRequest(
$request->input('password')
);
$hash = Password::hash($encoded, $user->salt);
$valid = Password::check(
$encoded,
$user->salt,
$user->password
);前端也需要按照业务实现的密码处理规则生成请求密码,确保与后端的 encodeToRequest() 和 decodeFromRequest() 保持一致。
例如,上述实现使用 MD5 作为请求密码:
import md5 from "crypto-js/md5";
export function encrypt(plain: string): string {
return md5(plain).toString();
}中间件
Pin 提供 DecodePassword 中间件,用于自动解析请求中的密码字段。
use Pin\Password\Middleware\DecodePassword;
#[Middleware(DecodePassword::class)]
case Create = 'POST:/api/users';默认处理以下字段:
passwordcurrent_passwordnew_passwordpassword_confirmation
中间件会将请求密码解析为标准密码编码,并将结果传递给后续业务逻辑。
例如:
{
"password": "request_password"
}经过中间件处理后,业务层获取的是标准密码编码。
本地调试
在非生产环境 或 API 来源 中,可以使用 plain:password 格式提交密码,用于接口调试。
例如:
plain:secret该格式会通过 Password::encode() 转换为标准密码编码。
密码规则验证
PasswordRule 用于验证密码是否符合指定的密码策略。
密码规则主要从以下几个方面进行限制:
- 长度:限制密码的最小和最大长度;
- 字符组成:要求密码包含数字、字母、大小写字母或特殊字符等指定类型;
- 连续字符:限制连续递增或递减字符的数量,例如
123456、abcdef; - 重复字符:限制连续重复字符的数量,例如
aaaaaa、111111; - 空白字符:默认禁止密码包含空格、制表符等空白字符。
默认规则
不指定额外规则时,PasswordRule 使用以下默认策略:
| 规则 | 默认值 |
|---|---|
| 最小长度 | 8 |
| 最大长度 | 32 |
| 空白字符 | 禁止 |
| 连续字符 | 最多 5 个 |
| 重复字符 | 最多 5 个 |
| 必须包含的字符类型 | 无 |
因此:
use Pin\Password\PasswordRule;
'password' => [
'required',
new PasswordRule(),
],表示密码至少 8 位、最多 32 位,不能包含空白字符,并限制连续字符和重复字符;默认不要求密码必须同时包含数字、字母或特殊字符。
例如:
12345678不会因为缺少字母而被 PasswordRule 拒绝。
如果应用需要更严格的密码策略,可以进一步指定字符类型。
字符类型
PasswordRule 支持以下字符类型:
| 方法 | 要求 |
|---|---|
numbers() | 必须包含数字 |
letters() | 必须包含字母 |
lowers() | 必须包含小写字母 |
uppers() | 必须包含大写字母 |
mixedCase() | 必须同时包含大小写字母 |
symbols() | 必须包含特殊字符 |
requiredCharacterTypes(2) | 至少包含 2 种字符类型 |
例如:
use Pin\Password\PasswordRule;
'password' => [
'required',
(new PasswordRule())
->numbers()
->letters()
->symbols(),
],要求密码同时包含:
- 数字;
- 字母;
- 特殊字符。
长度
使用 min() 和 max() 设置密码长度:
(new PasswordRule())
->min(10)
->max(32)例如:
1234567890满足最小长度为 10 的要求。
连续字符
maxSequentialCharacters() 用于限制连续字符。
例如:
(new PasswordRule())
->maxSequentialCharacters(4)用于限制类似:
12345
abcde这样的连续字符。
重复字符
maxRepeatedCharacters() 用于限制连续重复字符。
例如:
(new PasswordRule())
->maxRepeatedCharacters(3)用于限制类似:
aaaa
1111这样的重复字符。
空白字符
默认情况下,密码不允许包含空白字符。
如果需要允许空格或其他空白字符,可以使用:
(new PasswordRule())
->allowWhitespace()策略组合
可以组合多个规则,形成应用所需的密码策略:
use Pin\Password\PasswordRule;
'password' => [
'required',
(new PasswordRule())
->min(10)
->max(32)
->requiredCharacterTypes(2)
->maxSequentialCharacters(4)
->maxRepeatedCharacters(3),
],也可以直接指定字符类型:
use Pin\Password\PasswordRule;
'password' => [
'required',
(new PasswordRule())
->numbers()
->letters()
->symbols(),
],错误码
PasswordRule 默认返回带错误码前缀的验证消息,以便与 Pin 的错误处理机制保持一致。
如果只需要返回验证文本,可以关闭错误码:
use Pin\Password\PasswordRule;
new PasswordRule(false);